mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-06 01:08:21 +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 apiClient from './clients/api-client';
|
||||||
import { AxiosResponse } from 'axios';
|
import { AxiosResponse } from 'axios';
|
||||||
import { serialize } from 'object-to-formdata';
|
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 ApiLib from '../lib/api';
|
||||||
import ProductLib from '../lib/product';
|
|
||||||
|
|
||||||
export default class ProductAPI {
|
export default class ProductAPI {
|
||||||
static async index (filters?: ProductIndexFilter): Promise<ProductsIndex> {
|
static async index (filters?: ProductIndexFilterIds): Promise<ProductsIndex> {
|
||||||
const res: AxiosResponse<ProductsIndex> = await apiClient.get(`/api/products${ApiLib.filtersToQuery(ProductLib.indexFiltersToIds(filters))}`);
|
const res: AxiosResponse<ProductsIndex> = await apiClient.get(`/api/products${ApiLib.filtersToQuery(filters)}`);
|
||||||
return res?.data;
|
return res?.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
|||||||
*/
|
*/
|
||||||
const fetchProducts = async (): Promise<ProductsIndex> => {
|
const fetchProducts = async (): Promise<ProductsIndex> => {
|
||||||
try {
|
try {
|
||||||
const data = await ProductAPI.index(filters);
|
const data = await ProductAPI.index(ProductLib.indexFiltersToIds(filters));
|
||||||
setCurrentPage(data.page);
|
setCurrentPage(data.page);
|
||||||
setProductList(data.data);
|
setProductList(data.data);
|
||||||
setPageCount(data.total_pages);
|
setPageCount(data.total_pages);
|
||||||
|
@ -20,6 +20,7 @@ import { Machine } from '../../models/machine';
|
|||||||
import { KeywordFilter } from './filters/keyword-filter';
|
import { KeywordFilter } from './filters/keyword-filter';
|
||||||
import { ActiveFiltersTags } from './filters/active-filters-tags';
|
import { ActiveFiltersTags } from './filters/active-filters-tags';
|
||||||
import ProductLib from '../../lib/product';
|
import ProductLib from '../../lib/product';
|
||||||
|
import { UIRouter } from '@uirouter/angularjs';
|
||||||
|
|
||||||
declare const Application: IApplication;
|
declare const Application: IApplication;
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ interface StoreProps {
|
|||||||
onError: (message: string) => void,
|
onError: (message: string) => void,
|
||||||
onSuccess: (message: string) => void,
|
onSuccess: (message: string) => void,
|
||||||
currentUser: User,
|
currentUser: User,
|
||||||
|
uiRouter: UIRouter,
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Option format, expected by react-select
|
* Option format, expected by react-select
|
||||||
@ -37,7 +39,7 @@ interface StoreProps {
|
|||||||
/**
|
/**
|
||||||
* This component shows public store
|
* 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 { t } = useTranslation('public');
|
||||||
|
|
||||||
const { cart, setCart } = useCart(currentUser);
|
const { cart, setCart } = useCart(currentUser);
|
||||||
@ -51,6 +53,8 @@ const Store: React.FC<StoreProps> = ({ onError, onSuccess, currentUser }) => {
|
|||||||
const [filters, setFilters] = useImmer<ProductIndexFilter>(initFilters);
|
const [filters, setFilters] = useImmer<ProductIndexFilter>(initFilters);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// TODO, set the filters in the state
|
||||||
|
console.log(ProductLib.readFiltersFromUrl(location.href));
|
||||||
fetchProducts().then(scrollToProducts);
|
fetchProducts().then(scrollToProducts);
|
||||||
ProductCategoryAPI.index().then(data => {
|
ProductCategoryAPI.index().then(data => {
|
||||||
setProductCategories(data);
|
setProductCategories(data);
|
||||||
@ -62,6 +66,7 @@ const Store: React.FC<StoreProps> = ({ onError, onSuccess, currentUser }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchProducts().then(scrollToProducts);
|
fetchProducts().then(scrollToProducts);
|
||||||
|
uiRouter.stateService.transitionTo(uiRouter.globals.current, ProductLib.indexFiltersToRouterParams(filters));
|
||||||
}, [filters]);
|
}, [filters]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -173,7 +178,7 @@ const Store: React.FC<StoreProps> = ({ onError, onSuccess, currentUser }) => {
|
|||||||
*/
|
*/
|
||||||
const fetchProducts = async (): Promise<ProductsIndex> => {
|
const fetchProducts = async (): Promise<ProductsIndex> => {
|
||||||
try {
|
try {
|
||||||
const data = await ProductAPI.index(filters);
|
const data = await ProductAPI.index(ProductLib.indexFiltersToIds(filters));
|
||||||
setCurrentPage(data.page);
|
setCurrentPage(data.page);
|
||||||
setProducts(data.data);
|
setProducts(data.data);
|
||||||
setPageCount(data.total_pages);
|
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 {
|
interface CategoryTree {
|
||||||
parent: ProductCategory,
|
parent: ProductCategory,
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
/* eslint-disable
|
|
||||||
no-return-assign,
|
|
||||||
no-undef,
|
|
||||||
*/
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
Application.Controllers.controller('StoreController', ['$scope', 'CSRF', 'growl', '$state',
|
Application.Controllers.controller('StoreController', ['$scope', 'CSRF', 'growl', '$uiRouter',
|
||||||
function ($scope, CSRF, growl, $state) {
|
function ($scope, CSRF, growl, $uiRouter) {
|
||||||
/* PRIVATE SCOPE */
|
|
||||||
|
|
||||||
/* PUBLIC SCOPE */
|
/* 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
|
* Callback triggered in case of error
|
||||||
*/
|
*/
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { ProductCategory } from '../models/product-category';
|
import { ProductCategory } from '../models/product-category';
|
||||||
import {
|
import {
|
||||||
ProductIndexFilter, ProductIndexFilterIds,
|
ProductIndexFilter,
|
||||||
|
ProductIndexFilterIds, ProductIndexFilterUrl,
|
||||||
stockMovementInReasons,
|
stockMovementInReasons,
|
||||||
stockMovementOutReasons,
|
stockMovementOutReasons,
|
||||||
StockMovementReason
|
StockMovementReason
|
||||||
@ -102,4 +103,46 @@ export default class ProductLib {
|
|||||||
machines: filters.machines?.map(m => m.id)
|
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>,
|
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 type StockType = 'internal' | 'external' | 'all';
|
||||||
|
|
||||||
export const stockMovementInReasons = ['inward_stock', 'returned', 'cancelled', 'inventory_fix', 'other_in'] as const;
|
export const stockMovementInReasons = ['inward_stock', 'returned', 'cancelled', 'inventory_fix', 'other_in'] as const;
|
||||||
|
@ -620,12 +620,50 @@ angular.module('application.router', ['ui.router'])
|
|||||||
|
|
||||||
// store
|
// store
|
||||||
.state('app.public.store', {
|
.state('app.public.store', {
|
||||||
url: '/store',
|
url: '/store/:categoryTypeUrl/:category?{machines:string}{keywords:string}{is_active:string}{page:string}{sort:string}',
|
||||||
views: {
|
views: {
|
||||||
'main@': {
|
'main@': {
|
||||||
templateUrl: '/store/index.html',
|
templateUrl: '/store/index.html',
|
||||||
controller: 'StoreController'
|
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>
|
</div>
|
||||||
|
|
||||||
<section class="m-lg">
|
<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>
|
</section>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"declaration": false,
|
"declaration": false,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"lib": ["es6", "dom", "es2015.collection", "es2015.iterable"],
|
"lib": ["dom", "dom.iterable", "es2019"],
|
||||||
"module": "ES2020",
|
"module": "ES2020",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user