mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-21 15:54:22 +01:00
Merge branch 'product-store' of git.sleede.com:projets/fab-manager into product-store
This commit is contained in:
commit
a9dc7b4dd7
@ -1,14 +1,14 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { react2angular } from 'react2angular';
|
import { react2angular } from 'react2angular';
|
||||||
import { Loader } from '../base/loader';
|
import { Loader } from '../../base/loader';
|
||||||
import { IApplication } from '../../models/application';
|
import { IApplication } from '../../../models/application';
|
||||||
import { StoreListHeader } from './store-list-header';
|
import { StoreListHeader } from '../../store/store-list-header';
|
||||||
import { OrderItem } from './order-item';
|
import { OrderItem } from '../../store/order-item';
|
||||||
import { FabPagination } from '../base/fab-pagination';
|
import { FabPagination } from '../../base/fab-pagination';
|
||||||
import OrderAPI from '../../api/order';
|
import OrderAPI from '../../../api/order';
|
||||||
import { Order } from '../../models/order';
|
import { Order, OrderSortOption } from '../../../models/order';
|
||||||
import { User } from '../../models/user';
|
import { User } from '../../../models/user';
|
||||||
|
|
||||||
declare const Application: IApplication;
|
declare const Application: IApplication;
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ interface OrdersDashboardProps {
|
|||||||
* Option format, expected by react-select
|
* Option format, expected by react-select
|
||||||
* @see https://github.com/JedWatson/react-select
|
* @see https://github.com/JedWatson/react-select
|
||||||
*/
|
*/
|
||||||
type selectOption = { value: number, label: string };
|
type selectOption = { value: OrderSortOption, label: string };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component shows a list of all orders from the store for the current user
|
* This component shows a list of all orders from the store for the current user
|
||||||
@ -46,15 +46,15 @@ export const OrdersDashboard: React.FC<OrdersDashboardProps> = ({ currentUser, o
|
|||||||
*/
|
*/
|
||||||
const buildOptions = (): Array<selectOption> => {
|
const buildOptions = (): Array<selectOption> => {
|
||||||
return [
|
return [
|
||||||
{ value: 0, label: t('app.public.orders_dashboard.sort.newest') },
|
{ value: 'created_at-desc', label: t('app.public.orders_dashboard.sort.newest') },
|
||||||
{ value: 1, label: t('app.public.orders_dashboard.sort.oldest') }
|
{ value: 'created_at-asc', label: t('app.public.orders_dashboard.sort.oldest') }
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Display option: sorting
|
* Display option: sorting
|
||||||
*/
|
*/
|
||||||
const handleSorting = (option: selectOption) => {
|
const handleSorting = (option: selectOption) => {
|
||||||
OrderAPI.index({ page: 1, sort: option.value ? 'ASC' : 'DESC' }).then(res => {
|
OrderAPI.index({ page: 1, sort: option.value }).then(res => {
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
setOrders(res.data);
|
setOrders(res.data);
|
||||||
setPageCount(res.total_pages);
|
setPageCount(res.total_pages);
|
@ -73,7 +73,7 @@ export const ManageProductCategory: React.FC<ManageProductCategoryProps> = ({ pr
|
|||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
toggleModal={toggleModal}
|
toggleModal={toggleModal}
|
||||||
closeButton>
|
closeButton>
|
||||||
{ (action === 'update' || action === 'delete') && <p className='subtitle'>{productCategory.name}</p>}
|
{ action === 'update' && <p className='subtitle'>{productCategory.name}</p>}
|
||||||
<ProductCategoryForm action={action}
|
<ProductCategoryForm action={action}
|
||||||
productCategories={productCategories}
|
productCategories={productCategories}
|
||||||
productCategory={productCategory}
|
productCategory={productCategory}
|
||||||
|
@ -6,8 +6,8 @@ import { FormInput } from '../../form/form-input';
|
|||||||
import { FormSelect } from '../../form/form-select';
|
import { FormSelect } from '../../form/form-select';
|
||||||
import { ProductCategory } from '../../../models/product-category';
|
import { ProductCategory } from '../../../models/product-category';
|
||||||
import { FabButton } from '../../base/fab-button';
|
import { FabButton } from '../../base/fab-button';
|
||||||
import { FabAlert } from '../../base/fab-alert';
|
|
||||||
import ProductCategoryAPI from '../../../api/product-category';
|
import ProductCategoryAPI from '../../../api/product-category';
|
||||||
|
import { HtmlTranslate } from '../../base/html-translate';
|
||||||
|
|
||||||
interface ProductCategoryFormProps {
|
interface ProductCategoryFormProps {
|
||||||
action: 'create' | 'update' | 'delete',
|
action: 'create' | 'update' | 'delete',
|
||||||
@ -95,10 +95,8 @@ export const ProductCategoryForm: React.FC<ProductCategoryFormProps> = ({ action
|
|||||||
<form onSubmit={handleSubmit(onSubmit)} name="productCategoryForm" className="product-category-form">
|
<form onSubmit={handleSubmit(onSubmit)} name="productCategoryForm" className="product-category-form">
|
||||||
{ action === 'delete'
|
{ action === 'delete'
|
||||||
? <>
|
? <>
|
||||||
<FabAlert level='danger'>
|
<HtmlTranslate trKey="app.admin.store.product_category_form.delete.confirm" options={{ CATEGORY: productCategory?.name }} />
|
||||||
{t('app.admin.store.product_category_form.delete.confirm')}
|
<FabButton type='submit'>{t('app.admin.store.product_category_form.delete.save')}</FabButton>
|
||||||
</FabAlert>
|
|
||||||
<FabButton type='submit'>{t('app.admin.store.product_category_form.save')}</FabButton>
|
|
||||||
</>
|
</>
|
||||||
: <>
|
: <>
|
||||||
<FormInput id='name'
|
<FormInput id='name'
|
||||||
|
@ -48,7 +48,8 @@ export const OrderItem: React.FC<OrderItemProps> = ({ order, currentUser }) => {
|
|||||||
}
|
}
|
||||||
<div className="date">
|
<div className="date">
|
||||||
<span>{t('app.shared.store.order_item.created_at')}</span>
|
<span>{t('app.shared.store.order_item.created_at')}</span>
|
||||||
<div>{FormatLib.date(order.created_at)}
|
<div>
|
||||||
|
<p>{FormatLib.date(order.created_at)}
|
||||||
<div className="fab-tooltip">
|
<div className="fab-tooltip">
|
||||||
<span className="trigger"><PlusCircle size={16} weight="light" /></span>
|
<span className="trigger"><PlusCircle size={16} weight="light" /></span>
|
||||||
<div className="content">
|
<div className="content">
|
||||||
@ -56,6 +57,7 @@ export const OrderItem: React.FC<OrderItemProps> = ({ order, currentUser }) => {
|
|||||||
{FormatLib.date(order.updated_at)}
|
{FormatLib.date(order.updated_at)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='price'>
|
<div className='price'>
|
||||||
|
@ -11,6 +11,8 @@ import { Order } from '../../models/order';
|
|||||||
import FormatLib from '../../lib/format';
|
import FormatLib from '../../lib/format';
|
||||||
import OrderLib from '../../lib/order';
|
import OrderLib from '../../lib/order';
|
||||||
import { OrderActions } from './order-actions';
|
import { OrderActions } from './order-actions';
|
||||||
|
import SettingAPI from '../../api/setting';
|
||||||
|
import { SettingName } from '../../models/setting';
|
||||||
|
|
||||||
declare const Application: IApplication;
|
declare const Application: IApplication;
|
||||||
|
|
||||||
@ -28,11 +30,15 @@ export const ShowOrder: React.FC<ShowOrderProps> = ({ orderId, currentUser, onSu
|
|||||||
const { t } = useTranslation('shared');
|
const { t } = useTranslation('shared');
|
||||||
|
|
||||||
const [order, setOrder] = useState<Order>();
|
const [order, setOrder] = useState<Order>();
|
||||||
|
const [settings, setSettings] = useState<Map<SettingName, string>>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
OrderAPI.get(orderId).then(data => {
|
OrderAPI.get(orderId).then(data => {
|
||||||
setOrder(data);
|
setOrder(data);
|
||||||
}).catch(onError);
|
}).catch(onError);
|
||||||
|
SettingAPI.query(['store_withdrawal_instructions', 'fablab_name'])
|
||||||
|
.then(res => setSettings(res))
|
||||||
|
.catch(onError);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,6 +78,17 @@ export const ShowOrder: React.FC<ShowOrderProps> = ({ orderId, currentUser, onSu
|
|||||||
return paymentVerbose;
|
return paymentVerbose;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text instructions for the customer
|
||||||
|
*/
|
||||||
|
const withdrawalInstructions = (): string => {
|
||||||
|
const instructions = settings?.get('store_withdrawal_instructions');
|
||||||
|
if (instructions) {
|
||||||
|
return instructions;
|
||||||
|
}
|
||||||
|
return t('app.shared.store.show_order.please_contact_FABLAB', { FABLAB: settings?.get('fablab_name') });
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback after action success
|
* Callback after action success
|
||||||
*/
|
*/
|
||||||
@ -172,6 +189,10 @@ export const ShowOrder: React.FC<ShowOrderProps> = ({ orderId, currentUser, onSu
|
|||||||
}
|
}
|
||||||
<p className='total'>{t('app.shared.store.show_order.cart_total')} <span>{FormatLib.price(OrderLib.paidTotal(order))}</span></p>
|
<p className='total'>{t('app.shared.store.show_order.cart_total')} <span>{FormatLib.price(OrderLib.paidTotal(order))}</span></p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="withdrawal-instructions">
|
||||||
|
<label>{t('app.shared.store.show_order.pickup')}</label>
|
||||||
|
<p dangerouslySetInnerHTML={{ __html: withdrawalInstructions() }} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -28,7 +28,6 @@ import { ActiveFiltersTags } from './filters/active-filters-tags';
|
|||||||
import ProductLib from '../../lib/product';
|
import ProductLib from '../../lib/product';
|
||||||
import { UIRouter } from '@uirouter/angularjs';
|
import { UIRouter } from '@uirouter/angularjs';
|
||||||
import SettingAPI from '../../api/setting';
|
import SettingAPI from '../../api/setting';
|
||||||
import { CaretDoubleDown } from 'phosphor-react';
|
|
||||||
|
|
||||||
declare const Application: IApplication;
|
declare const Application: IApplication;
|
||||||
|
|
||||||
@ -70,13 +69,14 @@ const Store: React.FC<StoreProps> = ({ onError, onSuccess, currentUser, uiRouter
|
|||||||
const [resources, setResources] = useImmer<ProductResourcesFetching>(storeInitialResources);
|
const [resources, setResources] = useImmer<ProductResourcesFetching>(storeInitialResources);
|
||||||
const [machinesModule, setMachinesModule] = useState<boolean>(false);
|
const [machinesModule, setMachinesModule] = useState<boolean>(false);
|
||||||
const [categoriesTree, setCategoriesTree] = useState<CategoryTree[]>([]);
|
const [categoriesTree, setCategoriesTree] = useState<CategoryTree[]>([]);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const [filtersPanel, setFiltersPanel] = useState<boolean>(false);
|
const [filtersPanel, setFiltersPanel] = useState<boolean>(false);
|
||||||
const [pageCount, setPageCount] = useState<number>(0);
|
const [pageCount, setPageCount] = useState<number>(0);
|
||||||
const [productsCount, setProductsCount] = useState<number>(0);
|
const [productsCount, setProductsCount] = useState<number>(0);
|
||||||
const [currentPage, setCurrentPage] = useState<number>(1);
|
const [currentPage, setCurrentPage] = useState<number>(1);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchProducts().then(scrollToProducts);
|
fetchProducts();
|
||||||
ProductLib.fetchInitialResources(setResources, onError, formatCategories);
|
ProductLib.fetchInitialResources(setResources, onError, formatCategories);
|
||||||
SettingAPI.get('machines_module').then(data => {
|
SettingAPI.get('machines_module').then(data => {
|
||||||
setMachinesModule(data.value === 'true');
|
setMachinesModule(data.value === 'true');
|
||||||
@ -85,7 +85,7 @@ const Store: React.FC<StoreProps> = ({ onError, onSuccess, currentUser, uiRouter
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (resources.filters.ready) {
|
if (resources.filters.ready) {
|
||||||
fetchProducts().then(scrollToProducts);
|
fetchProducts();
|
||||||
uiRouter.stateService.transitionTo(uiRouter.globals.current, ProductLib.indexFiltersToRouterParams(resources.filters.data));
|
uiRouter.stateService.transitionTo(uiRouter.globals.current, ProductLib.indexFiltersToRouterParams(resources.filters.data));
|
||||||
}
|
}
|
||||||
}, [resources.filters]);
|
}, [resources.filters]);
|
||||||
@ -221,13 +221,6 @@ const Store: React.FC<StoreProps> = ({ onError, onSuccess, currentUser, uiRouter
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Scroll the view to the product list
|
|
||||||
*/
|
|
||||||
const scrollToProducts = () => {
|
|
||||||
window.document.getElementById('content-main').scrollTo({ top: 100, behavior: 'smooth' });
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectedCategory = resources.filters.data.categories[0];
|
const selectedCategory = resources.filters.data.categories[0];
|
||||||
const parent = resources.categories.data.find(c => c.id === selectedCategory?.parent_id);
|
const parent = resources.categories.data.find(c => c.id === selectedCategory?.parent_id);
|
||||||
return (
|
return (
|
||||||
@ -252,13 +245,6 @@ const Store: React.FC<StoreProps> = ({ onError, onSuccess, currentUser, uiRouter
|
|||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
<aside className={`store-filters ${filtersPanel ? '' : 'collapsed'}`}>
|
<aside className={`store-filters ${filtersPanel ? '' : 'collapsed'}`}>
|
||||||
<header>
|
|
||||||
<h3>{t('app.public.store.products.filter')}</h3>
|
|
||||||
<div className='grpBtn'>
|
|
||||||
<FabButton onClick={clearAllFilters} className="is-black">{t('app.public.store.products.filter_clear')}</FabButton>
|
|
||||||
<CaretDoubleDown className='filters-toggle' size={16} weight="bold" onClick={() => setFiltersPanel(!filtersPanel)} />
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<div className='grp'>
|
<div className='grp'>
|
||||||
<div className="categories">
|
<div className="categories">
|
||||||
<header>
|
<header>
|
||||||
|
@ -99,6 +99,7 @@
|
|||||||
@import "modules/store/orders-dashboard";
|
@import "modules/store/orders-dashboard";
|
||||||
@import "modules/store/orders";
|
@import "modules/store/orders";
|
||||||
@import "modules/store/product-categories";
|
@import "modules/store/product-categories";
|
||||||
|
@import "modules/store/product-category-form";
|
||||||
@import "modules/store/product-form";
|
@import "modules/store/product-form";
|
||||||
@import "modules/store/product-stock-form";
|
@import "modules/store/product-stock-form";
|
||||||
@import "modules/store/product-stock-modal";
|
@import "modules/store/product-stock-modal";
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
.order-item {
|
.order-item {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: repeat(2, min-content);
|
grid-template-rows: repeat(3, min-content);
|
||||||
grid-template-columns: 2fr 1fr 10ch;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 1.6rem 2.4rem;
|
gap: 1.6rem 2.4rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 1.6rem;
|
padding: 1.6rem;
|
||||||
@ -52,7 +52,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.price {
|
.price {
|
||||||
grid-area: 1 / 3 / 3 / 4;
|
grid-area: 3 / 1 / 4 / 2;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-self: flex-end;
|
justify-self: flex-end;
|
||||||
@ -62,7 +62,25 @@
|
|||||||
}
|
}
|
||||||
p { @include text-base(600); }
|
p { @include text-base(600); }
|
||||||
}
|
}
|
||||||
|
button {
|
||||||
|
grid-area: 3 / 2 / 4 / 3;
|
||||||
|
justify-self: flex-end;
|
||||||
|
width: 4rem;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
grid-template-rows: repeat(2, min-content);
|
||||||
|
grid-template-columns: 2fr 1fr 10ch;
|
||||||
|
.ref { grid-area: 1 / 1 / 2 / 2; }
|
||||||
|
.fab-state-label { grid-area: 1 / 2 / 2 / 3; }
|
||||||
|
.client { grid-area: 2 / 1 / 3 / 2; }
|
||||||
|
.date { grid-area: 2 / 2 / 3 / 3; }
|
||||||
|
.price { grid-area: 1 / 3 / 3 / 4; }
|
||||||
button { grid-area: 1 / 4 / 3 / 5; }
|
button { grid-area: 1 / 4 / 3 / 5; }
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 1440px) {
|
@media (min-width: 1440px) {
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
|
@ -101,7 +101,8 @@
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
|
||||||
.payment-info,
|
.payment-info,
|
||||||
.amount {
|
.amount,
|
||||||
|
.withdrawal-instructions {
|
||||||
padding: 2.4rem;
|
padding: 2.4rem;
|
||||||
border: 1px solid var(--gray-soft);
|
border: 1px solid var(--gray-soft);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
.product-category-form {
|
||||||
|
span {
|
||||||
|
margin-bottom: 2.4rem;
|
||||||
|
}
|
||||||
|
}
|
@ -1937,7 +1937,8 @@ en:
|
|||||||
error: "Unable to modify the category: "
|
error: "Unable to modify the category: "
|
||||||
success: "The category has been modified."
|
success: "The category has been modified."
|
||||||
delete:
|
delete:
|
||||||
confirm: "Do you really want to delete this product category?"
|
confirm: "Do you really want to delete <strong>{CATEGORY}</strong>?<br>If it has sub-categories, they will also be deleted."
|
||||||
|
save: "Delete"
|
||||||
error: "Unable to delete the category: "
|
error: "Unable to delete the category: "
|
||||||
success: "The category has been successfully deleted"
|
success: "The category has been successfully deleted"
|
||||||
save: "Save"
|
save: "Save"
|
||||||
|
@ -592,6 +592,8 @@ en:
|
|||||||
gift_total: "Discount total"
|
gift_total: "Discount total"
|
||||||
coupon: "Coupon"
|
coupon: "Coupon"
|
||||||
cart_total: "Cart total"
|
cart_total: "Cart total"
|
||||||
|
pickup: "Pickup your products"
|
||||||
|
please_contact_FABLAB: "Please contact {FABLAB, select, undefined{us} other{{FABLAB}}} for withdrawal instructions."
|
||||||
state:
|
state:
|
||||||
cart: 'Cart'
|
cart: 'Cart'
|
||||||
in_progress: 'Under preparation'
|
in_progress: 'Under preparation'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user