1
0
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:
Sylvain 2022-09-21 17:36:45 +02:00
parent 57e3dda2cd
commit 6b7daade5f
9 changed files with 108 additions and 20 deletions

View File

@ -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;
}

View File

@ -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);

View File

@ -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,

View File

@ -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
*/

View File

@ -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;
};
}

View File

@ -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;

View File

@ -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'
}
}
})

View File

@ -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>

View File

@ -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,