1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-01-18 07:52:23 +01:00

Admin orders (tmp)

This commit is contained in:
vincent 2022-09-01 16:08:37 +02:00
parent 539d89cf8e
commit 27a3bdc308
17 changed files with 534 additions and 77 deletions

View File

@ -141,7 +141,7 @@ const StoreCart: React.FC<StoreCartProps> = ({ onSuccess, onError, currentUser,
<img alt=''src={noImage} />
</div>
<div className="ref">
<span>ref: </span>
<span>{t('app.public.store_cart.reference_short')} </span>
<p>{item.orderable_name}</p>
</div>
<div className="actions">

View File

@ -0,0 +1,40 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Order } from '../../models/order';
import FormatLib from '../../lib/format';
import { FabButton } from '../base/fab-button';
interface OrderItemProps {
order?: Order
statusColor: string
}
/**
* List item for an order
*/
export const OrderItem: React.FC<OrderItemProps> = ({ order, statusColor }) => {
const { t } = useTranslation('admin');
/**
* Go to order page
*/
const showOrder = (token: string) => {
window.location.href = `/#!/admin/store/o/${token}`;
};
return (
<div className='order-item'>
<p className="ref">order.token</p>
<span className={`order-status ${statusColor}`}>order.state</span>
<div className='client'>
<span>{t('app.admin.store.order_item.client')}</span>
<p>order.user.name</p>
</div>
<p className="date">order.created_at</p>
<div className='price'>
<span>{t('app.admin.store.order_item.total')}</span>
<p>{FormatLib.price(order?.total)}</p>
</div>
<FabButton onClick={() => showOrder('orderToken')} icon={<i className="fas fa-eye" />} className="is-black" />
</div>
);
};

View File

@ -7,6 +7,8 @@ import { IApplication } from '../../models/application';
import { FabButton } from '../base/fab-button';
import { StoreListHeader } from './store-list-header';
import { AccordionItem } from './accordion-item';
import { OrderItem } from './order-item';
import { MemberSelect } from '../user/member-select';
declare const Application: IApplication;
@ -15,24 +17,15 @@ interface OrdersProps {
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
*/
* 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
@ -56,6 +49,17 @@ const Orders: React.FC<OrdersProps> = ({ onSuccess, onError }) => {
console.log('Create new order');
};
const statusOptions: checklistOption[] = [
{ value: 0, label: t('app.admin.store.orders.status.error') },
{ value: 1, label: t('app.admin.store.orders.status.canceled') },
{ value: 2, label: t('app.admin.store.orders.status.pending') },
{ value: 3, label: t('app.admin.store.orders.status.under_preparation') },
{ value: 4, label: t('app.admin.store.orders.status.paid') },
{ value: 5, label: t('app.admin.store.orders.status.ready') },
{ value: 6, label: t('app.admin.store.orders.status.collected') },
{ value: 7, label: t('app.admin.store.orders.status.refunded') }
];
/**
* Apply filters
*/
@ -101,6 +105,15 @@ const Orders: React.FC<OrdersProps> = ({ onSuccess, onError }) => {
});
};
/**
* Filter: by member
*/
const handleSelectMember = (userId: number) => {
setFilters(draft => {
return { ...draft, memberId: userId };
});
};
/**
* Open/close accordion items
*/
@ -108,13 +121,31 @@ const Orders: React.FC<OrdersProps> = ({ onSuccess, onError }) => {
setAccordion({ ...accordion, [id]: state });
};
/**
* Returns a className according to the status
*/
const statusColor = (status: string) => {
switch (status) {
case 'error':
return 'error';
case 'canceled':
return 'canceled';
case 'pending' || 'under_preparation':
return 'pending';
default:
return 'normal';
}
};
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>
{false &&
<div className='grpBtn'>
<FabButton className="main-action-btn" onClick={newOrder}>{t('app.admin.store.orders.create_order')}</FabButton>
</div>
}
</header>
<div className="store-filters">
@ -128,13 +159,25 @@ const Orders: React.FC<OrdersProps> = ({ onSuccess, onError }) => {
<AccordionItem id={0}
isOpen={accordion[0]}
onChange={handleAccordion}
label={t('app.admin.store.orders.filter_ref')}
>
<div className='content'>
<div className="group">
<input type="text" />
<FabButton onClick={applyFilters} className="is-info">{t('app.admin.store.orders.filter_apply')}</FabButton>
</div>
</div>
</AccordionItem>
<AccordionItem id={1}
isOpen={accordion[1]}
onChange={handleAccordion}
label={t('app.admin.store.orders.filter_status')}
>
<div className='content'>
<div className="list u-scrollbar">
<div className="group u-scrollbar">
{statusOptions.map(s => (
<label key={s.value}>
<input type="checkbox" checked={filters.status.includes(s)} onChange={(event) => handleSelectStatus(s, event.target.checked)} />
<input type="checkbox" checked={filters.status.some(o => o.label === s.label)} onChange={(event) => handleSelectStatus(s, event.target.checked)} />
<p>{s.label}</p>
</label>
))}
@ -142,6 +185,18 @@ const Orders: React.FC<OrdersProps> = ({ onSuccess, onError }) => {
<FabButton onClick={applyFilters} className="is-info">{t('app.admin.store.orders.filter_apply')}</FabButton>
</div>
</AccordionItem>
<AccordionItem id={2}
isOpen={accordion[2]}
onChange={handleAccordion}
label={t('app.admin.store.orders.filter_client')}
>
<div className='content'>
<div className="group">
<MemberSelect noHeader onSelected={handleSelectMember} />
<FabButton onClick={applyFilters} className="is-info">{t('app.admin.store.orders.filter_apply')}</FabButton>
</div>
</div>
</AccordionItem>
</div>
</div>
@ -151,6 +206,12 @@ const Orders: React.FC<OrdersProps> = ({ onSuccess, onError }) => {
selectOptions={buildOptions()}
onSelectOptionsChange={handleSorting}
/>
<div className="orders-list">
<OrderItem statusColor={statusColor('error')} />
<OrderItem statusColor={statusColor('canceled')} />
<OrderItem statusColor={statusColor('pending')} />
<OrderItem statusColor={statusColor('refunded')} />
</div>
</div>
</div>
);
@ -168,10 +229,12 @@ Application.Components.component('orders', react2angular(OrdersWrapper, ['onSucc
interface Filters {
reference: string,
status: checklistOption[]
status: checklistOption[],
memberId: number
}
const initFilters: Filters = {
reference: '',
status: []
status: [],
memberId: null
};

View File

@ -243,7 +243,7 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
label={t('app.admin.store.products.filter_categories')}
>
<div className='content'>
<div className="list u-scrollbar">
<div className="group u-scrollbar">
{productCategories.map(pc => (
<label key={pc.id} className={pc.parent_id ? 'offset' : ''}>
<input type="checkbox" checked={filters.categories.includes(pc)} onChange={(event) => handleSelectCategory(pc, event.target.checked)} />
@ -261,7 +261,7 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
label={t('app.admin.store.products.filter_machines')}
>
<div className='content'>
<div className="list u-scrollbar">
<div className="group u-scrollbar">
{machines.map(m => (
<label key={m.value}>
<input type="checkbox" checked={filters.machines.includes(m)} onChange={(event) => handleSelectMachine(m, event.target.checked)} />

View File

@ -0,0 +1,125 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { IApplication } from '../../models/application';
import { react2angular } from 'react2angular';
import { Loader } from '../base/loader';
import noImage from '../../../../images/no_image.png';
declare const Application: IApplication;
interface ShowOrderProps {
orderRef: string,
onError: (message: string) => void,
onSuccess: (message: string) => void
}
/**
* This component shows an order details
*/
export const ShowOrder: React.FC<ShowOrderProps> = ({ orderRef, onError, onSuccess }) => {
const { t } = useTranslation('admin');
/**
* Returns a className according to the status
*/
const statusColor = (status: string) => {
switch (status) {
case 'error':
return 'error';
case 'canceled':
return 'canceled';
case 'pending' || 'under_preparation':
return 'pending';
default:
return 'normal';
}
};
return (
<div className='show-order'>
<header>
<h2>[order.ref]</h2>
<div className="grpBtn">
<a href={''}
target='_blank'
className='fab-button is-black'
rel='noreferrer'>
{t('app.admin.store.show_order.see_invoice')}
</a>
</div>
</header>
<div className="client-info">
<label>{t('app.admin.store.show_order.client')}</label>
<div className="content">
<div className='group'>
<span>{t('app.admin.store.show_order.client')}</span>
<p>order.user.name</p>
</div>
<div className='group'>
<span>{t('app.admin.store.show_order.created_at')}</span>
<p>order.created_at</p>
</div>
<div className='group'>
<span>{t('app.admin.store.show_order.last_update')}</span>
<p>order.???</p>
</div>
<span className={`order-status ${statusColor('error')}`}>order.state</span>
</div>
</div>
<div className="cart">
<label>{t('app.admin.store.show_order.cart')}</label>
<div>
{/* loop sur les articles du panier */}
<article className='store-cart-list-item'>
<div className='picture'>
<img alt=''src={noImage} />
</div>
<div className="ref">
<span>{t('app.admin.store.show_order.reference_short')} orderable_id?</span>
<p>o.orderable_name</p>
</div>
<div className="actions">
<div className='price'>
<p>o.amount</p>
<span>/ {t('app.admin.store.show_order.unit')}</span>
</div>
<span className="count">o.quantity</span>
<div className='total'>
<span>{t('app.admin.store.show_order.item_total')}</span>
<p>o.quantity * o.amount</p>
</div>
</div>
</article>
</div>
</div>
<div className="group">
<div className="payment-info">
<label>{t('app.admin.store.show_order.payment_informations')}</label>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsum rerum commodi quaerat possimus! Odit, harum.</p>
</div>
<div className="amount">
<label>{t('app.admin.store.show_order.amount')}</label>
<p>{t('app.admin.store.show_order.products_total')}<span>order.amount</span></p>
<p className='gift'>{t('app.admin.store.show_order.gift_total')}<span>-order.amount</span></p>
<p>{t('app.admin.store.show_order.coupon')}<span>order.amount</span></p>
<p className='total'>{t('app.admin.store.show_order.total')} <span>order.total</span></p>
</div>
</div>
</div>
);
};
const ShowOrderWrapper: React.FC<ShowOrderProps> = (props) => {
return (
<Loader>
<ShowOrder {...props} />
</Loader>
);
};
Application.Components.component('showOrder', react2angular(ShowOrderWrapper, ['orderRef', 'onError', 'onSuccess']));

View File

@ -164,7 +164,7 @@ const Store: React.FC<StoreProps> = ({ onError, currentUser }) => {
<header>
<h3>{t('app.public.store.products.filter_categories')}</h3>
</header>
<div className="list u-scrollbar">
<div className="group u-scrollbar">
{categoriesTree.map(c =>
<div key={c.parent.id} className={`parent ${activeCategory?.id === c.parent.id || activeCategory?.parent === c.parent.id ? 'is-active' : ''}`}>
<p onClick={() => filterCategory(c.parent.id)}>
@ -199,7 +199,7 @@ const Store: React.FC<StoreProps> = ({ onError, currentUser }) => {
label={t('app.public.store.products.filter_machines')}
>
<div className='content'>
<div className="list u-scrollbar">
<div className="group u-scrollbar">
{machines.map(m => (
<label key={m.value}>
<input type="checkbox" />

View File

@ -0,0 +1,49 @@
/* eslint-disable
no-return-assign,
no-undef,
*/
'use strict';
Application.Controllers.controller('ShowOrdersController', ['$scope', 'CSRF', 'growl', '$state', '$transition$',
function ($scope, CSRF, growl, $state, $transition$) {
/* PRIVATE SCOPE */
/* PUBLIC SCOPE */
$scope.orderToken = $transition$.params().token;
/**
* Callback triggered in case of error
*/
$scope.onError = (message) => {
growl.error(message);
};
/**
* Callback triggered in case of success
*/
$scope.onSuccess = (message) => {
growl.success(message);
};
/**
* Click Callback triggered in case of back products list
*/
$scope.backProductsList = () => {
$state.go('app.admin.store.orders');
};
/* PRIVATE SCOPE */
/**
* Kind of constructor: these actions will be realized first when the controller is loaded
*/
const initialize = function () {
// set the authenticity tokens in the forms
CSRF.setMetaTags();
};
// init the controller (call at the end !)
return initialize();
}
]);

View File

@ -904,6 +904,17 @@ angular.module('application.router', ['ui.router'])
}
})
// show order
.state('app.admin.order_show', {
url: '/admin/store/o/:token',
views: {
'main@': {
templateUrl: '/admin/orders/show.html',
controller: 'ShowOrdersController'
}
}
})
// invoices
.state('app.admin.invoices', {
url: '/admin/invoices',

View File

@ -65,6 +65,14 @@
}
span { @include text-sm; }
}
.count {
padding: 0.8rem 1.6rem;
display: flex;
justify-content: center;
align-items: center;
background-color: var(--gray-soft);
border-radius: var(--border-radius-sm);
}
.total {
span {
@include text-sm;
@ -100,8 +108,6 @@
&-info {
p { @include text-sm; }
}
&-coupon {
}
aside {
grid-area: 1 / 10 / 3 / 13;

View File

@ -1,4 +1,5 @@
.orders {
.orders,
.show-order {
max-width: 1600px;
margin: 0 auto;
padding-bottom: 6rem;
@ -11,4 +12,147 @@
padding-bottom: 0;
grid-column: 1 / -1;
}
&-list {
& > *:not(:first-child) {
margin-top: 1.6rem;
}
.order-item {
width: 100%;
display: grid;
grid-auto-flow: column;
grid-template-columns: 1fr 15rem 15rem 10ch 12rem;
gap: 2.4rem;
justify-items: flex-start;
align-items: center;
padding: 1.6rem;
border: 1px solid var(--gray-soft-dark);
border-radius: var(--border-radius);
background-color: var(--gray-soft-lightest);
p { margin: 0; }
.ref { @include text-base(600); }
.client {
span {
@include text-xs;
color: var(--gray-hard-light);
}
p { @include text-sm; }
}
.date { @include text-sm; }
.price {
justify-self: flex-end;
span {
@include text-xs;
color: var(--gray-hard-light);
}
p { @include text-base(600); }
}
}
}
}
.show-order {
&-nav {
max-width: 1600px;
margin: 0 auto;
@include grid-col(12);
gap: 3.2rem;
justify-items: flex-start;
& > * {
grid-column: 2 / -2;
}
}
header { grid-column: 2 / -2; }
.client-info,
.cart {
grid-column: 2 / -2;
label {
margin-bottom: 1.6rem;
@include title-base;
}
.content {
display: flex;
align-items: center;
& > *:not(:last-child) {
margin-right: 2.4rem;
padding-right: 2.4rem;
border-right: 1px solid var(--gray-hard-dark);
}
}
p {
margin: 0;
line-height: 1.18;
}
.group {
span {
@include text-xs;
color: var(--gray-hard-light);
}
}
}
& > .group {
grid-column: 2 / -2;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 2.4rem;
align-items: flex-start;
.payment-info,
.amount {
padding: 2.4rem;
border: 1px solid var(--gray-soft);
border-radius: var(--border-radius);
label {
margin: 0 0 2.4rem;
padding: 0 0 0.8rem;
width: 100%;
border-bottom: 1px solid var(--gray-hard);
@include title-base;
}
}
.amount {
p {
display: flex;
justify-content: space-between;
align-items: center;
span { @include title-base; }
}
.gift { color: var(--information); }
.total {
padding: 1.6rem 0 0;
align-items: flex-start;
border-top: 1px solid var(--main);
@include text-base(600);
span { @include title-lg; }
}
}
}
}
.order-status {
--status-color: var(--success);
&.error { --status-color: var(--alert); }
&.canceled { --status-color: var(--alert-light); }
&.pending { --status-color: var(--information); }
&.normal { --status-color: var(--success); }
padding: 0.4rem 0.8rem;
display: flex;
justify-content: center;
align-items: center;
background-color: var(--gray-soft-light);
border-radius: var(--border-radius);
@include text-sm(500);
line-height: 1.714;
&::before {
content: "";
margin-right: 0.8rem;
width: 1rem;
height: 1rem;
background-color: var(--status-color);
border-radius: 50%;
}
}

View File

@ -22,6 +22,7 @@
max-width: 1600px;
margin: 0 auto;
@include grid-col(12);
gap: 3.2rem;
justify-items: flex-start;
& > * {
grid-column: 2 / -2;

View File

@ -0,0 +1,19 @@
<div class="header-page">
<div class="back">
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
</div>
<div class="center">
<h1 translate>{{ 'app.admin.store.manage_the_store' }}</h1>
</div>
</div>
<section>
<div class="show-order-nav">
<a class="back-btn" ng-click="backProductsList()" tabindex="0">
<i class="fas fa-angle-left"></i>
<span translate>{{ 'app.admin.store.back_products_list' }}</span>
</a>
</div>
<show-order order-token="orderToken" on-error="onError" on-success="onSuccess" />
</section>

View File

@ -1,18 +1,12 @@
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
<section class="heading-title">
<h1 translate>{{ 'app.admin.store.manage_the_store' }}</h1>
</section>
</div>
<div class="header-page">
<div class="back">
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
</div>
</section>
<div class="center">
<h1 translate>{{ 'app.admin.store.manage_the_store' }}</h1>
</div>
</div>
<section class="m-lg admin-store-manage">
<div class="row">

View File

@ -1,18 +1,12 @@
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
<section class="heading-title">
<h1 translate>{{ 'app.admin.store.manage_the_store' }}</h1>
</section>
</div>
<div class="header-page">
<div class="back">
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
</div>
</section>
<div class="center">
<h1 translate>{{ 'app.admin.store.manage_the_store' }}</h1>
</div>
</div>
<section class="admin-store-manage">
<div class="edit-product-nav">

View File

@ -1,20 +1,14 @@
<section class="heading b-b">
<div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l">
<section class="heading-title">
<h1 translate>{{ 'app.admin.store.manage_the_store' }}</h1>
</section>
</div>
<div class="header-page">
<div class="back">
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
</div>
</section>
<section class="admin-store-manage">
<div class="center">
<h1 translate>{{ 'app.admin.store.manage_the_store' }}</h1>
</div>
</div>
<section>
<div class="new-product-nav">
<a class="back-btn" ng-click="backProductsList()" tabindex="0">
<i class="fas fa-angle-left"></i>

View File

@ -2011,3 +2011,19 @@ en:
order_item:
total: "Total"
client: "Client"
show_order:
see_invoice: "See invoice"
client: "Client"
created_at: "Creation date"
last_update: "Last update"
cart: "Cart"
reference_short: "ref:"
unit: "Unit"
item_total: "Total"
payment_informations : "Payment informations"
amount: "Amount"
products_total: "Products total"
gift_total: "Discount total"
coupon: "Coupon"
cart_total: "Cart total"

View File

@ -417,14 +417,15 @@ en:
checkout: "Checkout"
cart_is_empty: "Your cart is empty"
pickup: "Pickup your products"
reference_short: "ref:"
unit: "Unit"
total: "Total"
checkout_header: "Total amount for your cart"
checkout_products_COUNT: "Your cart contains {COUNT} {COUNT, plural, =1{product} other{products}}"
checkout_products_total: "Product total"
checkout_products_total: "Products total"
checkout_gift_total: "Discount total"
checkout_coupon: "Coupon"
checkout_total: "Total amount"
checkout_total: "Cart total"
checkout_error: "An unexpected error occurred. Please contact the administrator."
checkout_success: "Purchase confirmed. Thanks!"
member_select: