mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-20 14:54:15 +01:00
(wip) Admin orders list
This commit is contained in:
parent
78683a31b3
commit
2e696f94fd
177
app/frontend/src/javascript/components/store/orders.tsx
Normal file
177
app/frontend/src/javascript/components/store/orders.tsx
Normal file
@ -0,0 +1,177 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useImmer } from 'use-immer';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { react2angular } from 'react2angular';
|
||||
import { Loader } from '../base/loader';
|
||||
import { IApplication } from '../../models/application';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
import { StoreListHeader } from './store-list-header';
|
||||
import { AccordionItem } from './accordion-item';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
interface OrdersProps {
|
||||
onSuccess: (message: string) => void,
|
||||
onError: (message: string) => void,
|
||||
}
|
||||
/**
|
||||
* Option format, expected by react-select
|
||||
* @see https://github.com/JedWatson/react-select
|
||||
*/
|
||||
type selectOption = { value: number, label: string };
|
||||
/**
|
||||
* Option format, expected by checklist
|
||||
*/
|
||||
type checklistOption = { value: number, label: string };
|
||||
const statusOptions: checklistOption[] = [
|
||||
{ value: 0, label: 'cart' },
|
||||
{ value: 1, label: 'paid by credit card' },
|
||||
{ value: 2, label: 'paid in cash' },
|
||||
{ value: 3, label: 'being processed' },
|
||||
{ value: 4, label: 'ready' },
|
||||
{ value: 5, label: 'delivered' },
|
||||
{ value: 6, label: 'canceled' },
|
||||
{ value: 7, label: 'refunded' }
|
||||
];
|
||||
|
||||
/**
|
||||
* Admin list of orders
|
||||
*/
|
||||
const Orders: React.FC<OrdersProps> = ({ onSuccess, onError }) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
const [filters, setFilters] = useImmer<Filters>(initFilters);
|
||||
const [clearFilters, setClearFilters] = useState<boolean>(false);
|
||||
const [accordion, setAccordion] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
applyFilters();
|
||||
setClearFilters(false);
|
||||
}, [clearFilters]);
|
||||
|
||||
/**
|
||||
* Create a new order
|
||||
*/
|
||||
const newOrder = () => {
|
||||
console.log('Create new order');
|
||||
};
|
||||
|
||||
/**
|
||||
* Apply filters
|
||||
*/
|
||||
const applyFilters = () => {
|
||||
console.log('Apply filters:', filters);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear filters
|
||||
*/
|
||||
const clearAllFilters = () => {
|
||||
setFilters(initFilters);
|
||||
setClearFilters(true);
|
||||
console.log('Clear all filters');
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates sorting options to the react-select format
|
||||
*/
|
||||
const buildOptions = (): Array<selectOption> => {
|
||||
return [
|
||||
{ value: 0, label: t('app.admin.store.orders.sort.newest') },
|
||||
{ value: 1, label: t('app.admin.store.orders.sort.oldest') }
|
||||
];
|
||||
};
|
||||
/**
|
||||
* Display option: sorting
|
||||
*/
|
||||
const handleSorting = (option: selectOption) => {
|
||||
console.log('Sort option:', option);
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter: by status
|
||||
*/
|
||||
const handleSelectStatus = (s: checklistOption, checked) => {
|
||||
const list = [...filters.status];
|
||||
checked
|
||||
? list.push(s)
|
||||
: list.splice(list.indexOf(s), 1);
|
||||
setFilters(draft => {
|
||||
return { ...draft, status: list };
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Open/close accordion items
|
||||
*/
|
||||
const handleAccordion = (id, state) => {
|
||||
setAccordion({ ...accordion, [id]: state });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='orders'>
|
||||
<header>
|
||||
<h2>{t('app.admin.store.orders.heading')}</h2>
|
||||
<div className='grpBtn'>
|
||||
<FabButton className="main-action-btn" onClick={newOrder}>{t('app.admin.store.orders.create_order')}</FabButton>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="store-filters">
|
||||
<header>
|
||||
<h3>{t('app.admin.store.orders.filter')}</h3>
|
||||
<div className='grpBtn'>
|
||||
<FabButton onClick={clearAllFilters} className="is-black">{t('app.admin.store.orders.filter_clear')}</FabButton>
|
||||
</div>
|
||||
</header>
|
||||
<div className="accordion">
|
||||
<AccordionItem id={0}
|
||||
isOpen={accordion[0]}
|
||||
onChange={handleAccordion}
|
||||
label={t('app.admin.store.orders.filter_status')}
|
||||
>
|
||||
<div className='content'>
|
||||
<div className="list u-scrollbar">
|
||||
{statusOptions.map(s => (
|
||||
<label key={s.value}>
|
||||
<input type="checkbox" checked={filters.status.includes(s)} onChange={(event) => handleSelectStatus(s, event.target.checked)} />
|
||||
<p>{s.label}</p>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
<FabButton onClick={applyFilters} className="is-info">{t('app.admin.store.orders.filter_apply')}</FabButton>
|
||||
</div>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="store-list">
|
||||
<StoreListHeader
|
||||
productsCount={0}
|
||||
selectOptions={buildOptions()}
|
||||
onSelectOptionsChange={handleSorting}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const OrdersWrapper: React.FC<OrdersProps> = (props) => {
|
||||
return (
|
||||
<Loader>
|
||||
<Orders {...props} />
|
||||
</Loader>
|
||||
);
|
||||
};
|
||||
|
||||
Application.Components.component('orders', react2angular(OrdersWrapper, ['onSuccess', 'onError']));
|
||||
|
||||
interface Filters {
|
||||
reference: string,
|
||||
status: checklistOption[]
|
||||
}
|
||||
|
||||
const initFilters: Filters = {
|
||||
reference: '',
|
||||
status: []
|
||||
};
|
@ -13,7 +13,7 @@ import ProductCategoryAPI from '../../api/product-category';
|
||||
import MachineAPI from '../../api/machine';
|
||||
import { AccordionItem } from './accordion-item';
|
||||
import { X } from 'phosphor-react';
|
||||
import { ProductsListHeader } from './products-list-header';
|
||||
import { StoreListHeader } from './store-list-header';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
@ -274,8 +274,8 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
||||
</AccordionItem>
|
||||
</div>
|
||||
</div>
|
||||
<div className='store-products-list'>
|
||||
<ProductsListHeader
|
||||
<div className='store-list'>
|
||||
<StoreListHeader
|
||||
productsCount={filteredProductsList.length}
|
||||
selectOptions={buildOptions()}
|
||||
onSelectOptionsChange={handleSorting}
|
||||
|
@ -3,13 +3,13 @@ import { useTranslation } from 'react-i18next';
|
||||
import Select from 'react-select';
|
||||
import Switch from 'react-switch';
|
||||
|
||||
interface ProductsListHeaderProps {
|
||||
interface StoreListHeaderProps {
|
||||
productsCount: number,
|
||||
selectOptions: selectOption[],
|
||||
onSelectOptionsChange: (option: selectOption) => void,
|
||||
switchLabel?: string,
|
||||
switchChecked: boolean,
|
||||
onSwitch: (boolean) => void
|
||||
switchChecked?: boolean,
|
||||
onSwitch?: (boolean) => void
|
||||
}
|
||||
/**
|
||||
* Option format, expected by react-select
|
||||
@ -20,7 +20,7 @@ interface ProductsListHeaderProps {
|
||||
/**
|
||||
* Renders an accordion item
|
||||
*/
|
||||
export const ProductsListHeader: React.FC<ProductsListHeaderProps> = ({ productsCount, selectOptions, onSelectOptionsChange, switchLabel, switchChecked, onSwitch }) => {
|
||||
export const StoreListHeader: React.FC<StoreListHeaderProps> = ({ productsCount, selectOptions, onSelectOptionsChange, switchLabel, switchChecked, onSwitch }) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
// Styles the React-select component
|
||||
@ -37,32 +37,34 @@ export const ProductsListHeader: React.FC<ProductsListHeaderProps> = ({ products
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='products-list-header'>
|
||||
<div className='store-list-header'>
|
||||
<div className='count'>
|
||||
<p>{t('app.admin.store.products_list_header.result_count')}<span>{productsCount}</span></p>
|
||||
<p>{t('app.admin.store.store_list_header.result_count')}<span>{productsCount}</span></p>
|
||||
</div>
|
||||
<div className="display">
|
||||
<div className='sort'>
|
||||
<p>{t('app.admin.store.products_list_header.display_options')}</p>
|
||||
<p>{t('app.admin.store.store_list_header.display_options')}</p>
|
||||
<Select
|
||||
options={selectOptions}
|
||||
onChange={evt => onSelectOptionsChange(evt)}
|
||||
styles={customStyles}
|
||||
/>
|
||||
</div>
|
||||
<div className='visibility'>
|
||||
<label>
|
||||
<span>{switchLabel || t('app.admin.store.products_list_header.visible_only')}</span>
|
||||
<Switch
|
||||
checked={switchChecked}
|
||||
onChange={(checked) => onSwitch(checked)}
|
||||
width={40}
|
||||
height={19}
|
||||
uncheckedIcon={false}
|
||||
checkedIcon={false}
|
||||
handleDiameter={15} />
|
||||
</label>
|
||||
</div>
|
||||
{onSwitch &&
|
||||
<div className='visibility'>
|
||||
<label>
|
||||
<span>{switchLabel || t('app.admin.store.store_list_header.visible_only')}</span>
|
||||
<Switch
|
||||
checked={switchChecked}
|
||||
onChange={(checked) => onSwitch(checked)}
|
||||
width={40}
|
||||
height={19}
|
||||
uncheckedIcon={false}
|
||||
checkedIcon={false}
|
||||
handleDiameter={15} />
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
@ -14,7 +14,7 @@ import useCart from '../../hooks/use-cart';
|
||||
import { emitCustomEvent } from 'react-custom-events';
|
||||
import { User } from '../../models/user';
|
||||
import { AccordionItem } from './accordion-item';
|
||||
import { ProductsListHeader } from './products-list-header';
|
||||
import { StoreListHeader } from './store-list-header';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
@ -213,8 +213,8 @@ const Store: React.FC<StoreProps> = ({ onError, currentUser }) => {
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<div className='store-products-list'>
|
||||
<ProductsListHeader
|
||||
<div className='store-list'>
|
||||
<StoreListHeader
|
||||
productsCount={products.length}
|
||||
selectOptions={buildOptions()}
|
||||
onSelectOptionsChange={handleSorting}
|
||||
|
@ -92,14 +92,15 @@
|
||||
@import "modules/settings/user-validation-setting";
|
||||
@import "modules/socials/fab-socials";
|
||||
@import "modules/store/_utilities";
|
||||
@import "modules/store/orders";
|
||||
@import "modules/store/product-categories";
|
||||
@import "modules/store/product-form";
|
||||
@import "modules/store/products-grid";
|
||||
@import "modules/store/products-list-header";
|
||||
@import "modules/store/products-list";
|
||||
@import "modules/store/products";
|
||||
@import "modules/store/store-filters";
|
||||
@import "modules/store/store-products-list";
|
||||
@import "modules/store/store-list-header";
|
||||
@import "modules/store/store-list";
|
||||
@import "modules/store/store";
|
||||
@import "modules/subscriptions/free-extend-modal";
|
||||
@import "modules/subscriptions/renew-modal";
|
||||
|
14
app/frontend/src/stylesheets/modules/store/orders.scss
Normal file
14
app/frontend/src/stylesheets/modules/store/orders.scss
Normal file
@ -0,0 +1,14 @@
|
||||
.orders {
|
||||
max-width: 1600px;
|
||||
margin: 0 auto;
|
||||
padding-bottom: 6rem;
|
||||
@include grid-col(12);
|
||||
gap: 3.2rem;
|
||||
align-items: flex-start;
|
||||
|
||||
header {
|
||||
@include header();
|
||||
padding-bottom: 0;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@
|
||||
|
||||
header {
|
||||
@include header();
|
||||
padding-bottom: 0;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
.products-list-header {
|
||||
.store-list-header {
|
||||
padding: 0.8rem 2.4rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
@ -1,4 +1,4 @@
|
||||
.store-products-list {
|
||||
.store-list {
|
||||
grid-column: 4 / -1;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
@ -1 +1 @@
|
||||
<h2>Orders page</h2>
|
||||
<orders on-success="onSuccess" on-error="onError"/>
|
||||
|
@ -1947,7 +1947,7 @@ en:
|
||||
name_za: "Z-A"
|
||||
price_low: "Price: low to high"
|
||||
price_high: "Price: high to low"
|
||||
products_list_header:
|
||||
store_list_header:
|
||||
result_count: "Result count:"
|
||||
display_options: "Display options:"
|
||||
visible_only: "Visible products only"
|
||||
@ -1987,3 +1987,13 @@ en:
|
||||
product_images_info: "<strong>Advice</strong></br>We advise you to use a square format, jpg or png, for jpgs, please use white for the background colour. The main visual will be the visual presented first in the product sheet."
|
||||
add_product_image: "Add an image"
|
||||
save: "Save"
|
||||
orders:
|
||||
heading: "Orders"
|
||||
create_order: "Create an order"
|
||||
filter: "Filter"
|
||||
filter_clear: "Clear all"
|
||||
filter_apply: "Apply"
|
||||
filter_status: "By status"
|
||||
sort:
|
||||
newest: "Newest first"
|
||||
oldest: "Oldest first"
|
||||
|
Loading…
x
Reference in New Issue
Block a user