refactor(orders): remove ClientsOrdersList and streamline OrdersList component

- Eliminated the ClientsOrdersList component to simplify the orders page structure.
- Updated OrdersList to handle both client and master views, enhancing code reusability.
- Improved order fetching logic and UI rendering for better performance and user experience.
This commit is contained in:
vchikalkin 2025-08-23 14:40:09 +03:00
parent 73dd674815
commit ad589e471c
4 changed files with 164 additions and 80 deletions

View File

@ -1,6 +1,6 @@
'use client';
import { Container } from '@/components/layout';
import { ClientsOrdersList, OrdersList } from '@/components/orders';
import { OrdersList } from '@/components/orders';
import { DateSelect } from '@/components/orders/orders-list/date-select';
import { DateTimeStoreProvider } from '@/stores/datetime';
@ -10,7 +10,6 @@ export default function ProfilePage() {
<DateTimeStoreProvider>
<div />
<DateSelect />
<ClientsOrdersList />
<OrdersList />
</DateTimeStoreProvider>
</Container>

View File

@ -1,66 +1,16 @@
'use client';
import { OrderCard } from '@/components/shared/order-card';
import { OrdersList as OrdersListComponent } from './orders-list';
import { useCurrentAndNext } from './utils';
import { useCustomerQuery, useIsMaster } from '@/hooks/api/customers';
import { useOrdersInfiniteQuery } from '@/hooks/api/orders';
import { useDateTimeStore } from '@/stores/datetime';
import { Button } from '@repo/ui/components/ui/button';
import type * as GQL from '@repo/graphql/types';
import { getDateUTCRange } from '@repo/utils/datetime-format';
export function ClientsOrdersList() {
const { data: { customer } = {} } = useCustomerQuery();
const isMaster = useIsMaster();
const selectedDate = useDateTimeStore((store) => store.date);
const { endOfDay, startOfDay } = getDateUTCRange(selectedDate).day();
const {
data: { pages } = {},
fetchNextPage,
hasNextPage,
isLoading,
} = useOrdersInfiniteQuery(
{
filters: {
slot: {
datetime_start: selectedDate
? {
gte: startOfDay,
lt: endOfDay,
}
: undefined,
master: {
documentId: {
eq: isMaster ? customer?.documentId : undefined,
},
},
},
},
},
{ enabled: Boolean(customer?.documentId) && isMaster },
);
const orders = pages?.flatMap((page) => page.orders) ?? [];
if (!orders?.length || isLoading || !isMaster) return null;
return (
<div className="flex flex-col space-y-2">
<h1 className="font-bold">Записи клиентов</h1>
{orders?.map((order) => order && <OrderCard key={order.documentId} showDate {...order} />)}
{hasNextPage && (
<Button onClick={() => fetchNextPage()} variant="ghost">
Загрузить еще
</Button>
)}
</div>
);
}
export function OrdersList() {
const { data: { customer } = {} } = useCustomerQuery();
const isMaster = useIsMaster();
const selectedDate = useDateTimeStore((store) => store.date);
const { endOfDay, startOfDay } = getDateUTCRange(selectedDate).day();
@ -72,40 +22,32 @@ export function OrdersList() {
} = useOrdersInfiniteQuery(
{
filters: {
client: {
documentId: {
eq: customer?.documentId,
},
},
client: isMaster ? undefined : { documentId: { eq: customer?.documentId } },
slot: {
datetime_start: selectedDate
? {
gte: startOfDay,
lt: endOfDay,
}
: undefined,
datetime_start: selectedDate ? { gte: startOfDay, lt: endOfDay } : undefined,
master: isMaster ? { documentId: { eq: customer?.documentId } } : undefined,
},
},
},
{ enabled: Boolean(customer?.documentId) },
);
const orders = pages?.flatMap((page) => page.orders) ?? [];
const orders =
(pages?.flatMap((page) => page.orders).filter(Boolean) as GQL.OrderFieldsFragment[]) ?? [];
const { current, next } = useCurrentAndNext(orders);
if (!orders?.length || isLoading) return null;
return (
<div className="flex flex-col space-y-2">
<h1 className="font-bold">Ваши записи</h1>
{orders?.map(
(order) =>
order && <OrderCard avatarSource="master" key={order.documentId} showDate {...order} />,
)}
{hasNextPage && (
<Button onClick={() => fetchNextPage()} variant="ghost">
Загрузить еще
</Button>
)}
</div>
<OrdersListComponent
avatarSource={isMaster ? 'client' : 'master'}
current={current}
hasNextPage={hasNextPage}
next={next}
onLoadMore={() => fetchNextPage()}
orders={orders}
title={isMaster ? 'Записи клиентов' : 'Ваши записи'}
/>
);
}

View File

@ -0,0 +1,78 @@
import { getOrderStatus, getStatusText, type OrderStatus } from './utils';
import { OrderCard } from '@/components/shared/order-card';
import type * as GQL from '@repo/graphql/types';
import { Button } from '@repo/ui/components/ui/button';
import { cn } from '@repo/ui/lib/utils';
type Order = GQL.OrderFieldsFragment;
type OrdersListProps = Pick<Parameters<typeof OrderCard>[0], 'avatarSource'> & {
readonly current: null | Order;
readonly hasNextPage?: boolean;
readonly next: null | Order;
readonly onLoadMore?: () => void;
readonly orders: Order[];
readonly title: string;
};
export function OrdersList({
avatarSource,
current,
hasNextPage = false,
next,
onLoadMore,
orders,
title,
}: OrdersListProps) {
return (
<div className="flex flex-col space-y-2">
<h1 className="font-bold">{title}</h1>
{orders?.map((order) => {
if (!order) return null;
const status = getOrderStatus(order, current, next);
return (
<DateStatusWrapper key={order.documentId} status={status}>
<OrderCard avatarSource={avatarSource} showDate {...order} />
</DateStatusWrapper>
);
})}
{hasNextPage && onLoadMore && (
<Button onClick={onLoadMore} variant="ghost">
Загрузить еще
</Button>
)}
</div>
);
}
function DateStatusWrapper({
children,
status,
}: {
readonly children: React.ReactNode;
readonly status: OrderStatus;
}) {
if (!status) return <>{children}</>;
const statusText = getStatusText(status);
return (
<div className="space-y-1">
{statusText && (
<div className="px-2">
<span
className={cn(
'text-xs',
status === 'current' ? 'text-secondary-foreground' : 'text-muted-foreground',
)}
>
{statusText}
</span>
</div>
)}
{children}
</div>
);
}

View File

@ -0,0 +1,65 @@
import * as GQL from '@repo/graphql/types';
import dayjs from 'dayjs';
import { useMemo } from 'react';
export type OrderStatus = 'current' | 'next' | undefined;
type Order = GQL.OrderFieldsFragment;
export function getCurrentAndNext(orders: Order[]) {
const now = dayjs();
let current: null | Order = null;
let next: null | Order = null;
for (let index = orders.length - 1; index >= 0; index--) {
const order = orders[index];
const nextOrder = index > 0 ? orders[index - 1] : null;
if (
order &&
now.isAfter(dayjs(order.datetime_start)) &&
now.isBefore(dayjs(order.datetime_end))
) {
current = order;
}
if (
nextOrder?.state === GQL.Enum_Order_State.Approved &&
now.isSame(dayjs(nextOrder?.datetime_start), 'day')
) {
next = nextOrder;
}
}
return { current, next };
}
export function getOrderStatus(
order: Order,
current: null | Order,
next: null | Order,
): OrderStatus {
if (current && order.documentId === current.documentId) return 'current';
if (next && order.documentId === next.documentId) return 'next';
return undefined;
}
export function getStatusText(status: OrderStatus): string {
switch (status) {
case 'current':
return 'Текущая запись';
case 'next':
return 'Следующая запись';
default:
return '';
}
}
export function useCurrentAndNext(orders: Order[]) {
const { current, next } = useMemo(() => {
return getCurrentAndNext(orders);
}, [orders, orders.length]);
return { current, next };
}