feat(orders, slots, schedule): enhance order and slot button functionality with date validation and conditional rendering

This commit is contained in:
vchikalkin 2025-08-02 20:40:09 +03:00
parent ee6ccbef84
commit ba88305bbf
4 changed files with 48 additions and 19 deletions

View File

@ -7,6 +7,7 @@ import { useIsMaster } from '@/hooks/api/customers';
import { useOrderMutation, useOrderQuery } from '@/hooks/api/orders';
import { usePushWithData } from '@/hooks/url';
import { Enum_Order_State } from '@repo/graphql/types';
import { isBeforeToday } from '@repo/utils/datetime-format';
export function OrderButtons({ documentId }: Readonly<OrderComponentProps>) {
const push = usePushWithData();
@ -49,14 +50,22 @@ export function OrderButtons({ documentId }: Readonly<OrderComponentProps>) {
push('/orders/add', order);
}
const isOrderStale = order?.datetime_start && isBeforeToday(order?.datetime_start);
const canCancel = !isOrderStale && (isCreated || (isMaster && isCancelling) || isApproved);
const canComplete = isMaster && isApproved;
const canConfirm = !isOrderStale && isMaster && isCreated;
const canRepeat = isCancelled || isCompleted;
const canReturn = !isOrderStale && isMaster && isCancelled;
return (
<FloatingActionPanel
isLoading={isPending}
onCancel={isCreated || (isMaster && isCancelling) || isApproved ? handleCancel : undefined}
onComplete={isMaster && isApproved ? handleOnComplete : undefined}
onConfirm={isMaster && isCreated ? handleApprove : undefined}
onRepeat={isCancelled || isCompleted ? handleOnRepeat : undefined}
onReturn={isMaster && isCancelled ? handleApprove : undefined}
onCancel={canCancel ? handleCancel : undefined}
onComplete={canComplete ? handleOnComplete : undefined}
onConfirm={canConfirm ? handleApprove : undefined}
onRepeat={canRepeat ? handleOnRepeat : undefined}
onReturn={canReturn ? handleApprove : undefined}
/>
);
}

View File

@ -7,14 +7,12 @@ import { useCustomerQuery } from '@/hooks/api/customers';
import { useMasterSlotsQuery } from '@/hooks/api/slots';
import { useDateTimeStore } from '@/stores/datetime';
import { LoadingSpinner } from '@repo/ui/components/ui/spinner';
import { getDateUTCRange } from '@repo/utils/datetime-format';
import dayjs from 'dayjs';
import { getDateUTCRange, isTodayOrAfter } from '@repo/utils/datetime-format';
import { type PropsWithChildren } from 'react';
export function DaySlotsList() {
const { data: { customer } = {} } = useCustomerQuery();
const selectedDate = useDateTimeStore((store) => store.date);
const now = dayjs();
const { endOfDay, startOfDay } = getDateUTCRange(selectedDate).day();
const { data: { slots } = {}, isLoading } = useMasterSlotsQuery({
@ -28,13 +26,13 @@ export function DaySlotsList() {
return <LoadingSpinner />;
}
const shouldShowAddForm = now.isSame(selectedDate, 'day') || now.isBefore(selectedDate, 'day');
const isSelectedDateTodayOrAfter = selectedDate && isTodayOrAfter(selectedDate);
if (!slots?.length) {
return (
<Wrapper>
<DataNotFound title="Слоты не найдены" />
{shouldShowAddForm && <DaySlotAddForm />}
{isSelectedDateTodayOrAfter && <DaySlotAddForm />}
</Wrapper>
);
}
@ -43,7 +41,7 @@ export function DaySlotsList() {
<Wrapper>
<h1 className="font-bold">Слоты</h1>
{slots.map((slot) => slot && <SlotCard key={slot.documentId} {...slot} />)}
{shouldShowAddForm && <DaySlotAddForm />}
{isSelectedDateTodayOrAfter && <DaySlotAddForm />}
</Wrapper>
);
}

View File

@ -5,6 +5,7 @@ import FloatingActionPanel from '../shared/action-panel';
import { type SlotComponentProps } from './types';
import { useSlotDelete, useSlotMutation, useSlotQuery } from '@/hooks/api/slots';
import { Enum_Slot_State } from '@repo/graphql/types';
import { isBeforeToday } from '@repo/utils/datetime-format';
import { useRouter } from 'next/navigation';
export function SlotButtons({ documentId }: Readonly<SlotComponentProps>) {
@ -29,22 +30,29 @@ export function SlotButtons({ documentId }: Readonly<SlotComponentProps>) {
return updateSlot({ data: { state: Enum_Slot_State.Closed } });
}
function handleDeleteSlot() {
const handleToggle = () => {
if (isOpened) handleCloseSlot();
if (isClosed) handleOpenSlot();
};
const handleDeleteSlot = () => {
router.back();
return deleteSlot();
}
};
const hasOrders = Boolean(slot?.orders.length);
const isSlotStale = slot?.datetime_start && isBeforeToday(slot?.datetime_start);
const canDelete = !isSlotStale && !hasOrders;
const canToggle = !isSlotStale;
return (
<FloatingActionPanel
isLoading={isPendingUpdate || isPendingDelete}
isOpen={isOpened}
onDelete={hasOrders ? undefined : () => handleDeleteSlot()}
onToggle={() => {
if (isOpened) handleCloseSlot();
if (isClosed) handleOpenSlot();
}}
onDelete={canDelete ? handleDeleteSlot : undefined}
onToggle={canToggle ? handleToggle : undefined}
/>
);
}

View File

@ -6,8 +6,11 @@ import 'dayjs/locale/ru';
type DateTime = Exclude<ConfigType, null | undefined>;
if (!dayjs.prototype.tz) {
if (!dayjs.prototype.utc) {
dayjs.extend(utc);
}
if (!dayjs.prototype.tz) {
dayjs.extend(timezone);
}
@ -88,6 +91,17 @@ export function getTimeZoneLabel(tz: string = DEFAULT_TZ): string {
return `GMT${offset}`;
}
export function isBeforeToday(date: Date | string) {
const nowUtc = dayjs().tz(DEFAULT_TZ);
const inputDateUtc = dayjs(date).tz(DEFAULT_TZ);
return inputDateUtc.isBefore(nowUtc, 'day');
}
export function isTodayOrAfter(date: Date | string) {
return !isBeforeToday(date);
}
export function sumTime(datetime: DateTime, durationMinutes: number) {
if (!datetime) return '';