mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-18 07:52:23 +01:00
(feat) save filters to the URL
This commit is contained in:
parent
57e3dda2cd
commit
6b7daade5f
@ -1,13 +1,12 @@
|
||||
import apiClient from './clients/api-client';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { serialize } from 'object-to-formdata';
|
||||
import { Product, ProductIndexFilter, ProductsIndex, ProductStockMovement } from '../models/product';
|
||||
import { Product, ProductIndexFilterIds, ProductsIndex, ProductStockMovement } from '../models/product';
|
||||
import ApiLib from '../lib/api';
|
||||
import ProductLib from '../lib/product';
|
||||
|
||||
export default class ProductAPI {
|
||||
static async index (filters?: ProductIndexFilter): Promise<ProductsIndex> {
|
||||
const res: AxiosResponse<ProductsIndex> = await apiClient.get(`/api/products${ApiLib.filtersToQuery(ProductLib.indexFiltersToIds(filters))}`);
|
||||
static async index (filters?: ProductIndexFilterIds): Promise<ProductsIndex> {
|
||||
const res: AxiosResponse<ProductsIndex> = await apiClient.get(`/api/products${ApiLib.filtersToQuery(filters)}`);
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,7 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
||||
*/
|
||||
const fetchProducts = async (): Promise<ProductsIndex> => {
|
||||
try {
|
||||
const data = await ProductAPI.index(filters);
|
||||
const data = await ProductAPI.index(ProductLib.indexFiltersToIds(filters));
|
||||
setCurrentPage(data.page);
|
||||
setProductList(data.data);
|
||||
setPageCount(data.total_pages);
|
||||
|
@ -20,6 +20,7 @@ import { Machine } from '../../models/machine';
|
||||
import { KeywordFilter } from './filters/keyword-filter';
|
||||
import { ActiveFiltersTags } from './filters/active-filters-tags';
|
||||
import ProductLib from '../../lib/product';
|
||||
import { UIRouter } from '@uirouter/angularjs';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
@ -27,6 +28,7 @@ interface StoreProps {
|
||||
onError: (message: string) => void,
|
||||
onSuccess: (message: string) => void,
|
||||
currentUser: User,
|
||||
uiRouter: UIRouter,
|
||||
}
|
||||
/**
|
||||
* Option format, expected by react-select
|
||||
@ -37,7 +39,7 @@ interface StoreProps {
|
||||
/**
|
||||
* This component shows public store
|
||||
*/
|
||||
const Store: React.FC<StoreProps> = ({ onError, onSuccess, currentUser }) => {
|
||||
const Store: React.FC<StoreProps> = ({ onError, onSuccess, currentUser, uiRouter }) => {
|
||||
const { t } = useTranslation('public');
|
||||
|
||||
const { cart, setCart } = useCart(currentUser);
|
||||
@ -51,6 +53,8 @@ const Store: React.FC<StoreProps> = ({ onError, onSuccess, currentUser }) => {
|
||||
const [filters, setFilters] = useImmer<ProductIndexFilter>(initFilters);
|
||||
|
||||
useEffect(() => {
|
||||
// TODO, set the filters in the state
|
||||
console.log(ProductLib.readFiltersFromUrl(location.href));
|
||||
fetchProducts().then(scrollToProducts);
|
||||
ProductCategoryAPI.index().then(data => {
|
||||
setProductCategories(data);
|
||||
@ -62,6 +66,7 @@ const Store: React.FC<StoreProps> = ({ onError, onSuccess, currentUser }) => {
|
||||
|
||||
useEffect(() => {
|
||||
fetchProducts().then(scrollToProducts);
|
||||
uiRouter.stateService.transitionTo(uiRouter.globals.current, ProductLib.indexFiltersToRouterParams(filters));
|
||||
}, [filters]);
|
||||
|
||||
/**
|
||||
@ -173,7 +178,7 @@ const Store: React.FC<StoreProps> = ({ onError, onSuccess, currentUser }) => {
|
||||
*/
|
||||
const fetchProducts = async (): Promise<ProductsIndex> => {
|
||||
try {
|
||||
const data = await ProductAPI.index(filters);
|
||||
const data = await ProductAPI.index(ProductLib.indexFiltersToIds(filters));
|
||||
setCurrentPage(data.page);
|
||||
setProducts(data.data);
|
||||
setPageCount(data.total_pages);
|
||||
@ -287,7 +292,7 @@ const StoreWrapper: React.FC<StoreProps> = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
Application.Components.component('store', react2angular(StoreWrapper, ['onError', 'onSuccess', 'currentUser']));
|
||||
Application.Components.component('store', react2angular(StoreWrapper, ['onError', 'onSuccess', 'currentUser', 'uiRouter']));
|
||||
|
||||
interface CategoryTree {
|
||||
parent: ProductCategory,
|
||||
|
@ -1,15 +1,12 @@
|
||||
/* eslint-disable
|
||||
no-return-assign,
|
||||
no-undef,
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
Application.Controllers.controller('StoreController', ['$scope', 'CSRF', 'growl', '$state',
|
||||
function ($scope, CSRF, growl, $state) {
|
||||
/* PRIVATE SCOPE */
|
||||
|
||||
Application.Controllers.controller('StoreController', ['$scope', 'CSRF', 'growl', '$uiRouter',
|
||||
function ($scope, CSRF, growl, $uiRouter) {
|
||||
/* PUBLIC SCOPE */
|
||||
|
||||
// the following item is used by the Store component to store the filters in te URL
|
||||
$scope.uiRouter = $uiRouter;
|
||||
|
||||
/**
|
||||
* Callback triggered in case of error
|
||||
*/
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { ProductCategory } from '../models/product-category';
|
||||
import {
|
||||
ProductIndexFilter, ProductIndexFilterIds,
|
||||
ProductIndexFilter,
|
||||
ProductIndexFilterIds, ProductIndexFilterUrl,
|
||||
stockMovementInReasons,
|
||||
stockMovementOutReasons,
|
||||
StockMovementReason
|
||||
@ -102,4 +103,46 @@ export default class ProductLib {
|
||||
machines: filters.machines?.map(m => m.id)
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepare the filtering data from the filters to pass them to the router URL
|
||||
*/
|
||||
static indexFiltersToRouterParams = (filters: ProductIndexFilter): ProductIndexFilterUrl => {
|
||||
let categoryTypeUrl = null;
|
||||
let category = null;
|
||||
if (filters.categories.length > 0) {
|
||||
categoryTypeUrl = filters.categories[0].parent_id === null ? 'c' : 'sc';
|
||||
category = filters.categories.map(c => c.slug)[0];
|
||||
}
|
||||
return {
|
||||
...filters,
|
||||
machines: filters.machines?.map(m => m.slug),
|
||||
category,
|
||||
categoryTypeUrl
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse the provided URL and return a ready-to-use filter object
|
||||
* FIXME
|
||||
*/
|
||||
static readFiltersFromUrl = (url: string): ProductIndexFilterIds => {
|
||||
const res: ProductIndexFilterIds = {};
|
||||
for (const [key, value] of new URLSearchParams(url.split('?')[1])) {
|
||||
let parsedValue: string|number|boolean = value;
|
||||
if (['true', 'false'].includes(value)) {
|
||||
parsedValue = (value === 'true');
|
||||
} else if (parseInt(value, 10).toString() === value) {
|
||||
parsedValue = parseInt(value, 10);
|
||||
}
|
||||
if (res[key] === undefined) {
|
||||
res[key] = parsedValue;
|
||||
} else if (Array.isArray(res[key])) {
|
||||
res[key] = [...res[key] as Array<unknown>, parsedValue];
|
||||
} else {
|
||||
res[key] = [res[key], parsedValue];
|
||||
}
|
||||
}
|
||||
return res;
|
||||
};
|
||||
}
|
||||
|
@ -22,6 +22,12 @@ export interface ProductIndexFilterIds extends Omit<Omit<ProductIndexFilter, 'ca
|
||||
machines?: Array<number>,
|
||||
}
|
||||
|
||||
export interface ProductIndexFilterUrl extends Omit<Omit<ProductIndexFilter, 'categories'>, 'machines'> {
|
||||
categoryTypeUrl?: 'c' | 'sc',
|
||||
category?: string,
|
||||
machines?: Array<string>,
|
||||
}
|
||||
|
||||
export type StockType = 'internal' | 'external' | 'all';
|
||||
|
||||
export const stockMovementInReasons = ['inward_stock', 'returned', 'cancelled', 'inventory_fix', 'other_in'] as const;
|
||||
|
@ -620,12 +620,50 @@ angular.module('application.router', ['ui.router'])
|
||||
|
||||
// store
|
||||
.state('app.public.store', {
|
||||
url: '/store',
|
||||
url: '/store/:categoryTypeUrl/:category?{machines:string}{keywords:string}{is_active:string}{page:string}{sort:string}',
|
||||
views: {
|
||||
'main@': {
|
||||
templateUrl: '/store/index.html',
|
||||
controller: 'StoreController'
|
||||
}
|
||||
},
|
||||
params: {
|
||||
categoryTypeUrl: {
|
||||
dynamic: true,
|
||||
raw: true,
|
||||
type: 'path',
|
||||
value: null,
|
||||
squash: true
|
||||
},
|
||||
category: {
|
||||
dynamic: true,
|
||||
type: 'path',
|
||||
raw: true,
|
||||
value: null,
|
||||
squash: true
|
||||
},
|
||||
machines: {
|
||||
array: true,
|
||||
dynamic: true,
|
||||
type: 'query',
|
||||
raw: true
|
||||
},
|
||||
keywords: {
|
||||
dynamic: true,
|
||||
type: 'query'
|
||||
},
|
||||
is_active: {
|
||||
dynamic: true,
|
||||
type: 'query'
|
||||
},
|
||||
page: {
|
||||
dynamic: true,
|
||||
type: 'query'
|
||||
},
|
||||
sort: {
|
||||
dynamic: true,
|
||||
type: 'query'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -13,5 +13,5 @@
|
||||
</div>
|
||||
|
||||
<section class="m-lg">
|
||||
<store current-user="currentUser" on-error="onError" on-success="onSuccess" />
|
||||
<store current-user="currentUser" on-error="onError" on-success="onSuccess" ui-router="uiRouter" />
|
||||
</section>
|
||||
|
@ -3,7 +3,7 @@
|
||||
"declaration": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"lib": ["es6", "dom", "es2015.collection", "es2015.iterable"],
|
||||
"lib": ["dom", "dom.iterable", "es2019"],
|
||||
"module": "ES2020",
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
|
Loading…
x
Reference in New Issue
Block a user