mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-20 14:54:15 +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 { useTranslation } from 'react-i18next';
|
||||
import { react2angular } from 'react2angular';
|
||||
import { Loader } from '../base/loader';
|
||||
import { IApplication } from '../../models/application';
|
||||
import { StoreListHeader } from './store-list-header';
|
||||
import { OrderItem } from './order-item';
|
||||
import { FabPagination } from '../base/fab-pagination';
|
||||
import OrderAPI from '../../api/order';
|
||||
import { Order } from '../../models/order';
|
||||
import { User } from '../../models/user';
|
||||
import { Loader } from '../../base/loader';
|
||||
import { IApplication } from '../../../models/application';
|
||||
import { StoreListHeader } from '../../store/store-list-header';
|
||||
import { OrderItem } from '../../store/order-item';
|
||||
import { FabPagination } from '../../base/fab-pagination';
|
||||
import OrderAPI from '../../../api/order';
|
||||
import { Order, OrderSortOption } from '../../../models/order';
|
||||
import { User } from '../../../models/user';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
@ -20,7 +20,7 @@ interface OrdersDashboardProps {
|
||||
* Option format, expected by 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
|
||||
@ -46,15 +46,15 @@ export const OrdersDashboard: React.FC<OrdersDashboardProps> = ({ currentUser, o
|
||||
*/
|
||||
const buildOptions = (): Array<selectOption> => {
|
||||
return [
|
||||
{ value: 0, label: t('app.public.orders_dashboard.sort.newest') },
|
||||
{ value: 1, label: t('app.public.orders_dashboard.sort.oldest') }
|
||||
{ value: 'created_at-desc', label: t('app.public.orders_dashboard.sort.newest') },
|
||||
{ value: 'created_at-asc', label: t('app.public.orders_dashboard.sort.oldest') }
|
||||
];
|
||||
};
|
||||
/**
|
||||
* Display option: sorting
|
||||
*/
|
||||
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);
|
||||
setOrders(res.data);
|
||||
setPageCount(res.total_pages);
|
@ -73,7 +73,7 @@ export const ManageProductCategory: React.FC<ManageProductCategoryProps> = ({ pr
|
||||
isOpen={isOpen}
|
||||
toggleModal={toggleModal}
|
||||
closeButton>
|
||||
{ (action === 'update' || action === 'delete') && <p className='subtitle'>{productCategory.name}</p>}
|
||||
{ action === 'update' && <p className='subtitle'>{productCategory.name}</p>}
|
||||
<ProductCategoryForm action={action}
|
||||
productCategories={productCategories}
|
||||
productCategory={productCategory}
|
||||
|
@ -6,8 +6,8 @@ import { FormInput } from '../../form/form-input';
|
||||
import { FormSelect } from '../../form/form-select';
|
||||
import { ProductCategory } from '../../../models/product-category';
|
||||
import { FabButton } from '../../base/fab-button';
|
||||
import { FabAlert } from '../../base/fab-alert';
|
||||
import ProductCategoryAPI from '../../../api/product-category';
|
||||
import { HtmlTranslate } from '../../base/html-translate';
|
||||
|
||||
interface ProductCategoryFormProps {
|
||||
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">
|
||||
{ action === 'delete'
|
||||
? <>
|
||||
<FabAlert level='danger'>
|
||||
{t('app.admin.store.product_category_form.delete.confirm')}
|
||||
</FabAlert>
|
||||
<FabButton type='submit'>{t('app.admin.store.product_category_form.save')}</FabButton>
|
||||
<HtmlTranslate trKey="app.admin.store.product_category_form.delete.confirm" options={{ CATEGORY: productCategory?.name }} />
|
||||
<FabButton type='submit'>{t('app.admin.store.product_category_form.delete.save')}</FabButton>
|
||||
</>
|
||||
: <>
|
||||
<FormInput id='name'
|
||||
|
@ -48,14 +48,16 @@ export const OrderItem: React.FC<OrderItemProps> = ({ order, currentUser }) => {
|
||||
}
|
||||
<div className="date">
|
||||
<span>{t('app.shared.store.order_item.created_at')}</span>
|
||||
<div>{FormatLib.date(order.created_at)}
|
||||
<div className="fab-tooltip">
|
||||
<span className="trigger"><PlusCircle size={16} weight="light" /></span>
|
||||
<div className="content">
|
||||
{t('app.shared.store.order_item.last_update')}<br />
|
||||
{FormatLib.date(order.updated_at)}
|
||||
<div>
|
||||
<p>{FormatLib.date(order.created_at)}
|
||||
<div className="fab-tooltip">
|
||||
<span className="trigger"><PlusCircle size={16} weight="light" /></span>
|
||||
<div className="content">
|
||||
{t('app.shared.store.order_item.last_update')}<br />
|
||||
{FormatLib.date(order.updated_at)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className='price'>
|
||||
|
@ -11,6 +11,8 @@ import { Order } from '../../models/order';
|
||||
import FormatLib from '../../lib/format';
|
||||
import OrderLib from '../../lib/order';
|
||||
import { OrderActions } from './order-actions';
|
||||
import SettingAPI from '../../api/setting';
|
||||
import { SettingName } from '../../models/setting';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
@ -28,11 +30,15 @@ export const ShowOrder: React.FC<ShowOrderProps> = ({ orderId, currentUser, onSu
|
||||
const { t } = useTranslation('shared');
|
||||
|
||||
const [order, setOrder] = useState<Order>();
|
||||
const [settings, setSettings] = useState<Map<SettingName, string>>(null);
|
||||
|
||||
useEffect(() => {
|
||||
OrderAPI.get(orderId).then(data => {
|
||||
setOrder(data);
|
||||
}).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;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@ -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>
|
||||
</div>
|
||||
<div className="withdrawal-instructions">
|
||||
<label>{t('app.shared.store.show_order.pickup')}</label>
|
||||
<p dangerouslySetInnerHTML={{ __html: withdrawalInstructions() }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -28,7 +28,6 @@ import { ActiveFiltersTags } from './filters/active-filters-tags';
|
||||
import ProductLib from '../../lib/product';
|
||||
import { UIRouter } from '@uirouter/angularjs';
|
||||
import SettingAPI from '../../api/setting';
|
||||
import { CaretDoubleDown } from 'phosphor-react';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
@ -70,13 +69,14 @@ const Store: React.FC<StoreProps> = ({ onError, onSuccess, currentUser, uiRouter
|
||||
const [resources, setResources] = useImmer<ProductResourcesFetching>(storeInitialResources);
|
||||
const [machinesModule, setMachinesModule] = useState<boolean>(false);
|
||||
const [categoriesTree, setCategoriesTree] = useState<CategoryTree[]>([]);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [filtersPanel, setFiltersPanel] = useState<boolean>(false);
|
||||
const [pageCount, setPageCount] = useState<number>(0);
|
||||
const [productsCount, setProductsCount] = useState<number>(0);
|
||||
const [currentPage, setCurrentPage] = useState<number>(1);
|
||||
|
||||
useEffect(() => {
|
||||
fetchProducts().then(scrollToProducts);
|
||||
fetchProducts();
|
||||
ProductLib.fetchInitialResources(setResources, onError, formatCategories);
|
||||
SettingAPI.get('machines_module').then(data => {
|
||||
setMachinesModule(data.value === 'true');
|
||||
@ -85,7 +85,7 @@ const Store: React.FC<StoreProps> = ({ onError, onSuccess, currentUser, uiRouter
|
||||
|
||||
useEffect(() => {
|
||||
if (resources.filters.ready) {
|
||||
fetchProducts().then(scrollToProducts);
|
||||
fetchProducts();
|
||||
uiRouter.stateService.transitionTo(uiRouter.globals.current, ProductLib.indexFiltersToRouterParams(resources.filters.data));
|
||||
}
|
||||
}, [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 parent = resources.categories.data.find(c => c.id === selectedCategory?.parent_id);
|
||||
return (
|
||||
@ -252,13 +245,6 @@ const Store: React.FC<StoreProps> = ({ onError, onSuccess, currentUser, uiRouter
|
||||
}
|
||||
</ul>
|
||||
<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="categories">
|
||||
<header>
|
||||
|
@ -99,6 +99,7 @@
|
||||
@import "modules/store/orders-dashboard";
|
||||
@import "modules/store/orders";
|
||||
@import "modules/store/product-categories";
|
||||
@import "modules/store/product-category-form";
|
||||
@import "modules/store/product-form";
|
||||
@import "modules/store/product-stock-form";
|
||||
@import "modules/store/product-stock-modal";
|
||||
|
@ -1,8 +1,8 @@
|
||||
.order-item {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-rows: repeat(2, min-content);
|
||||
grid-template-columns: 2fr 1fr 10ch;
|
||||
grid-template-rows: repeat(3, min-content);
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1.6rem 2.4rem;
|
||||
align-items: center;
|
||||
padding: 1.6rem;
|
||||
@ -52,7 +52,7 @@
|
||||
}
|
||||
}
|
||||
.price {
|
||||
grid-area: 1 / 3 / 3 / 4;
|
||||
grid-area: 3 / 1 / 4 / 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-self: flex-end;
|
||||
@ -62,7 +62,25 @@
|
||||
}
|
||||
p { @include text-base(600); }
|
||||
}
|
||||
button { grid-area: 1 / 4 / 3 / 5; }
|
||||
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; }
|
||||
}
|
||||
|
||||
@media (min-width: 1440px) {
|
||||
grid-auto-flow: column;
|
||||
|
@ -101,7 +101,8 @@
|
||||
align-items: flex-start;
|
||||
|
||||
.payment-info,
|
||||
.amount {
|
||||
.amount,
|
||||
.withdrawal-instructions {
|
||||
padding: 2.4rem;
|
||||
border: 1px solid var(--gray-soft);
|
||||
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: "
|
||||
success: "The category has been modified."
|
||||
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: "
|
||||
success: "The category has been successfully deleted"
|
||||
save: "Save"
|
||||
|
@ -592,6 +592,8 @@ en:
|
||||
gift_total: "Discount total"
|
||||
coupon: "Coupon"
|
||||
cart_total: "Cart total"
|
||||
pickup: "Pickup your products"
|
||||
please_contact_FABLAB: "Please contact {FABLAB, select, undefined{us} other{{FABLAB}}} for withdrawal instructions."
|
||||
state:
|
||||
cart: 'Cart'
|
||||
in_progress: 'Under preparation'
|
||||
|
Loading…
x
Reference in New Issue
Block a user