diff --git a/app/controllers/api/orders_controller.rb b/app/controllers/api/orders_controller.rb new file mode 100644 index 000000000..096e0df82 --- /dev/null +++ b/app/controllers/api/orders_controller.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +# API Controller for resources of type Order +# Orders are used in store +class API::OrdersController < API::ApiController + before_action :authenticate_user! + before_action :set_order, only: %i[show update destroy] + + def index + @result = ::Orders::OrderService.list(params, current_user) + end + + def show; end + + def update + authorize @order + + if @order.update(order_parameters) + render status: :ok + else + render json: @order.errors.full_messages, status: :unprocessable_entity + end + end + + def destroy + authorize @order + @order.destroy + head :no_content + end + + private + + def set_order + @order = Order.find(params[:id]) + end + + def order_params + params.require(:order).permit(:state) + end +end diff --git a/app/frontend/src/javascript/api/order.ts b/app/frontend/src/javascript/api/order.ts new file mode 100644 index 000000000..7203899a4 --- /dev/null +++ b/app/frontend/src/javascript/api/order.ts @@ -0,0 +1,16 @@ +import apiClient from './clients/api-client'; +import { AxiosResponse } from 'axios'; +import { Order, OrderIndexFilter, OrderIndex } from '../models/order'; +import ApiLib from '../lib/api'; + +export default class ProductAPI { + static async index (filters?: OrderIndexFilter): Promise { + const res: AxiosResponse = await apiClient.get(`/api/orders${ApiLib.filtersToQuery(filters)}`); + return res?.data; + } + + static async get (id: number | string): Promise { + const res: AxiosResponse = await apiClient.get(`/api/orders/${id}`); + return res?.data; + } +} diff --git a/app/frontend/src/javascript/components/cart/store-cart.tsx b/app/frontend/src/javascript/components/cart/store-cart.tsx index f385d3821..d9677348e 100644 --- a/app/frontend/src/javascript/components/cart/store-cart.tsx +++ b/app/frontend/src/javascript/components/cart/store-cart.tsx @@ -14,9 +14,9 @@ import { Order } from '../../models/order'; import { MemberSelect } from '../user/member-select'; import { CouponInput } from '../coupon/coupon-input'; import { Coupon } from '../../models/coupon'; -import { computePriceWithCoupon } from '../../lib/coupon'; import noImage from '../../../../images/no_image.png'; import Switch from 'react-switch'; +import OrderLib from '../../lib/order'; declare const Application: IApplication; @@ -132,52 +132,6 @@ const StoreCart: React.FC = ({ onSuccess, onError, currentUser, } }; - /** - * Get the item total - */ - const itemAmount = (item): number => { - return item.quantity * Math.trunc(item.amount * 100) / 100; - }; - - /** - * return true if cart has offered item - */ - const hasOfferedItem = (): boolean => { - return cart.order_items_attributes - .filter(i => i.is_offered).length > 0; - }; - - /** - * Get the offered item total - */ - const offeredAmount = (): number => { - return cart.order_items_attributes - .filter(i => i.is_offered) - .map(i => Math.trunc(i.amount * 100) * i.quantity) - .reduce((acc, curr) => acc + curr, 0) / 100; - }; - - /** - * Get the total amount before offered amount - */ - const totalBeforeOfferedAmount = (): number => { - return (Math.trunc(cart.total * 100) + Math.trunc(offeredAmount() * 100)) / 100; - }; - - /** - * Get the coupon amount - */ - const couponAmount = (): number => { - return (Math.trunc(cart.total * 100) - Math.trunc(computePriceWithCoupon(cart.total, cart.coupon) * 100)) / 100.00; - }; - - /** - * Get the paid total amount - */ - const paidTotal = (): number => { - return computePriceWithCoupon(cart.total, cart.coupon); - }; - return (
@@ -185,7 +139,7 @@ const StoreCart: React.FC = ({ onSuccess, onError, currentUser, {cart && cart.order_items_attributes.map(item => (
- +
{t('app.public.store_cart.reference_short')} @@ -203,7 +157,7 @@ const StoreCart: React.FC = ({ onSuccess, onError, currentUser,
{t('app.public.store_cart.total')} -

{FormatLib.price(itemAmount(item))}

+

{FormatLib.price(OrderLib.itemAmount(item))}

@@ -251,15 +205,15 @@ const StoreCart: React.FC = ({ onSuccess, onError, currentUser,

{t('app.public.store_cart.checkout_header')}

{t('app.public.store_cart.checkout_products_COUNT', { COUNT: cart?.order_items_attributes.length })}
-

{t('app.public.store_cart.checkout_products_total')} {FormatLib.price(totalBeforeOfferedAmount())}

- {hasOfferedItem() && -

{t('app.public.store_cart.checkout_gift_total')} -{FormatLib.price(offeredAmount())}

+

{t('app.public.store_cart.checkout_products_total')} {FormatLib.price(OrderLib.totalBeforeOfferedAmount(cart))}

+ {OrderLib.hasOfferedItem(cart) && +

{t('app.public.store_cart.checkout_gift_total')} -{FormatLib.price(OrderLib.offeredAmount(cart))}

} {cart.coupon && -

{t('app.public.store_cart.checkout_coupon')} -{FormatLib.price(couponAmount())}

+

{t('app.public.store_cart.checkout_coupon')} -{FormatLib.price(OrderLib.couponAmount(cart))}

}
-

{t('app.public.store_cart.checkout_total')} {FormatLib.price(paidTotal())}

+

{t('app.public.store_cart.checkout_total')} {FormatLib.price(OrderLib.paidTotal(cart))}

{t('app.public.store_cart.checkout')} diff --git a/app/frontend/src/javascript/components/store/order-item.tsx b/app/frontend/src/javascript/components/store/order-item.tsx index 2781f39b8..4f27f0d39 100644 --- a/app/frontend/src/javascript/components/store/order-item.tsx +++ b/app/frontend/src/javascript/components/store/order-item.tsx @@ -19,10 +19,10 @@ export const OrderItem: React.FC = ({ order, currentUser }) => { /** * Go to order page */ - const showOrder = (ref: string) => { + const showOrder = (order: Order) => { isPrivileged() - ? window.location.href = `/#!/admin/store/o/${ref}` - : window.location.href = `/#!/store/o/${ref}`; + ? window.location.href = `/#!/admin/store/orders/${order.id}` + : window.location.href = `/#!/dashboard/orders/${order.id}`; }; /** @@ -41,7 +41,7 @@ export const OrderItem: React.FC = ({ order, currentUser }) => { return 'error'; case 'canceled': return 'canceled'; - case 'pending' || 'under_preparation': + case 'in_progress': return 'pending'; default: return 'normal'; @@ -50,24 +50,24 @@ export const OrderItem: React.FC = ({ order, currentUser }) => { return (
-

order.ref

+

{order.reference}

- - order.state + + {t(`app.shared.store.order_item.state.${order.state}`)}
{isPrivileged() &&
{t('app.shared.store.order_item.client')} -

order.user.name

+

{order?.user?.name || ''}

} -

order.created_at

+

{FormatLib.date(order.created_at)}

{t('app.shared.store.order_item.total')}

{FormatLib.price(order?.total)}

- showOrder('orderRef')} icon={} className="is-black" /> + showOrder(order)} icon={} className="is-black" />
); }; diff --git a/app/frontend/src/javascript/components/store/orders-dashboard.tsx b/app/frontend/src/javascript/components/store/orders-dashboard.tsx index 2460cd8f6..5dba6c459 100644 --- a/app/frontend/src/javascript/components/store/orders-dashboard.tsx +++ b/app/frontend/src/javascript/components/store/orders-dashboard.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { react2angular } from 'react2angular'; import { Loader } from '../base/loader'; @@ -6,10 +6,14 @@ 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'; declare const Application: IApplication; interface OrdersDashboardProps { + currentUser: User, onError: (message: string) => void } /** @@ -21,15 +25,21 @@ type selectOption = { value: number, label: string }; /** * This component shows a list of all orders from the store for the current user */ -// TODO: delete next eslint disable -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export const OrdersDashboard: React.FC = ({ onError }) => { +export const OrdersDashboard: React.FC = ({ currentUser, onError }) => { const { t } = useTranslation('public'); - // TODO: delete next eslint disable - // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [orders, setOrders] = useState>([]); const [pageCount, setPageCount] = useState(0); const [currentPage, setCurrentPage] = useState(1); + const [totalCount, setTotalCount] = useState(1); + + useEffect(() => { + OrderAPI.index({}).then(res => { + setPageCount(res.total_pages); + setTotalCount(res.total_count); + setOrders(res.data); + }).catch(onError); + }, []); /** * Creates sorting options to the react-select format @@ -44,7 +54,26 @@ export const OrdersDashboard: React.FC = ({ onError }) => * Display option: sorting */ const handleSorting = (option: selectOption) => { - console.log('Sort option:', option); + OrderAPI.index({ page: 1, sort: option.value ? 'ASC' : 'DESC' }).then(res => { + setCurrentPage(1); + setOrders(res.data); + setPageCount(res.total_pages); + setTotalCount(res.total_count); + }).catch(onError); + }; + + /** + * Handle orders pagination + */ + const handlePagination = (page: number) => { + if (page !== currentPage) { + OrderAPI.index({ page }).then(res => { + setCurrentPage(page); + setOrders(res.data); + setPageCount(res.total_pages); + setTotalCount(res.total_count); + }).catch(onError); + } }; return ( @@ -55,15 +84,17 @@ export const OrdersDashboard: React.FC = ({ onError }) =>
- + {orders.map(order => ( + + ))}
{pageCount > 1 && - + }
@@ -78,4 +109,4 @@ const OrdersDashboardWrapper: React.FC = (props) => { ); }; -Application.Components.component('ordersDashboard', react2angular(OrdersDashboardWrapper, ['onError'])); +Application.Components.component('ordersDashboard', react2angular(OrdersDashboardWrapper, ['onError', 'currentUser'])); diff --git a/app/frontend/src/javascript/components/store/orders.tsx b/app/frontend/src/javascript/components/store/orders.tsx index 2402b226c..351d9f260 100644 --- a/app/frontend/src/javascript/components/store/orders.tsx +++ b/app/frontend/src/javascript/components/store/orders.tsx @@ -13,6 +13,8 @@ import { MemberSelect } from '../user/member-select'; import { User } from '../../models/user'; import { FormInput } from '../form/form-input'; import { TDateISODate } from '../../typings/date-iso'; +import OrderAPI from '../../api/order'; +import { Order } from '../../models/order'; declare const Application: IApplication; @@ -42,10 +44,17 @@ const Orders: React.FC = ({ currentUser, onSuccess, onError }) => { const { register, getValues } = useForm(); + const [orders, setOrders] = useState>([]); const [filters, setFilters] = useImmer(initFilters); const [clearFilters, setClearFilters] = useState(false); const [accordion, setAccordion] = useState({}); + useEffect(() => { + OrderAPI.index({}).then(res => { + setOrders(res.data); + }).catch(onError); + }, []); + useEffect(() => { applyFilters(); setClearFilters(false); @@ -228,7 +237,9 @@ const Orders: React.FC = ({ currentUser, onSuccess, onError }) => { onSelectOptionsChange={handleSorting} />
- + {orders.map(order => ( + + ))}
diff --git a/app/frontend/src/javascript/components/store/show-order.tsx b/app/frontend/src/javascript/components/store/show-order.tsx index b2aaac940..818668db6 100644 --- a/app/frontend/src/javascript/components/store/show-order.tsx +++ b/app/frontend/src/javascript/components/store/show-order.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { IApplication } from '../../models/application'; import { User } from '../../models/user'; @@ -7,11 +7,15 @@ import { Loader } from '../base/loader'; import noImage from '../../../../images/no_image.png'; import { FabStateLabel } from '../base/fab-state-label'; import Select from 'react-select'; +import OrderAPI from '../../api/order'; +import { Order } from '../../models/order'; +import FormatLib from '../../lib/format'; +import OrderLib from '../../lib/order'; declare const Application: IApplication; interface ShowOrderProps { - orderRef: string, + orderId: string, currentUser?: User, onError: (message: string) => void, onSuccess: (message: string) => void @@ -27,9 +31,17 @@ type selectOption = { value: number, label: string }; */ // TODO: delete next eslint disable // eslint-disable-next-line @typescript-eslint/no-unused-vars -export const ShowOrder: React.FC = ({ orderRef, currentUser, onError, onSuccess }) => { +export const ShowOrder: React.FC = ({ orderId, currentUser, onError, onSuccess }) => { const { t } = useTranslation('shared'); + const [order, setOrder] = useState(); + + useEffect(() => { + OrderAPI.get(orderId).then(data => { + setOrder(data); + }); + }, []); + /** * Check if the current operator has administrative rights or is a normal member */ @@ -42,14 +54,14 @@ export const ShowOrder: React.FC = ({ orderRef, currentUser, onE */ const buildOptions = (): Array => { return [ - { value: 0, label: t('app.shared.store.show_order.status.error') }, - { value: 1, label: t('app.shared.store.show_order.status.canceled') }, - { value: 2, label: t('app.shared.store.show_order.status.pending') }, - { value: 3, label: t('app.shared.store.show_order.status.under_preparation') }, - { value: 4, label: t('app.shared.store.show_order.status.paid') }, - { value: 5, label: t('app.shared.store.show_order.status.ready') }, - { value: 6, label: t('app.shared.store.show_order.status.collected') }, - { value: 7, label: t('app.shared.store.show_order.status.refunded') } + { value: 0, label: t('app.shared.store.show_order.state.error') }, + { value: 1, label: t('app.shared.store.show_order.state.canceled') }, + { value: 2, label: t('app.shared.store.show_order.state.pending') }, + { value: 3, label: t('app.shared.store.show_order.state.under_preparation') }, + { value: 4, label: t('app.shared.store.show_order.state.paid') }, + { value: 5, label: t('app.shared.store.show_order.state.ready') }, + { value: 6, label: t('app.shared.store.show_order.state.collected') }, + { value: 7, label: t('app.shared.store.show_order.state.refunded') } ]; }; @@ -81,17 +93,21 @@ export const ShowOrder: React.FC = ({ orderRef, currentUser, onE return 'error'; case 'canceled': return 'canceled'; - case 'pending' || 'under_preparation': + case 'in_progress': return 'pending'; default: return 'normal'; } }; + if (!order) { + return null; + } + return (
-

[order.ref]

+

[{order.reference}]

{isPrivileged() &&