Vlad Chikalkin c5799a7f00
Feature/orders (#25)
* add contacts scroller

* add service select

* add calendar & time picker

* context/order: add masterId

* Revert "context/order: add masterId"

This reverts commit d5d07d7b2f5b6673a621a30b00ad087c60675a3f.

* components/order-form: add back button

* disable submit button if no customer selected

* disable submit button if no service selected

* service component: comment span

* save selected date to context

* fix calendar padding

* hooks/slot: rename index -> master

* slot list: render immediately

* fix step components rendering

* add check icon for masters

* Revert "add check icon for masters"

This reverts commit cc81a9a504918ebbffcca8d035c7c4984f109957.

* prepare for split contacts grid into masters/clients grid

* create MastersGrid & master-select step

* optimize useCustomerContacts

* add ClientsGrid & 'client-select' step

* add self to masters list & border avatar

* context/order: split into files

* hooks/profile: allow pass empty args to useProfileQuery/useProfileMutation

* context/order: skip client-select in client steps

* packages: upgrade next@15.3.0

* .vscode: add launch.json

* back-button: fix steps using

* contacts: skip client step for client

* fix react types

* ServiceSelect: fix padding

* Revert "contacts: skip client step for client"

This reverts commit db9af07dab9df9428561a1952f5a2c91c5b9d88d.

* fix steps for client & master

* split datetime-select into files

* improve useSlots hook

* migrate from order context to zustand store

* pass order store via context

* fix submit button not working

* skip master select for master & client select for client

* select time feature & get final order values

* apps/web: rename actions/service -> actions/services

* create order works!

* split next-button into two buttons

* add result pages (success, error)

* packages/graphql: add eslint

* merge branch 'refactor-api' (#23)

* refactor customer api

* refactor slots api

* hooks/customers: use invalidateQueries

* refactor services api

* optimize hooks queryKey

* refactor orders api

* typo refactor hooks

* fix telegramId type (number)

* fix bot with new api

* rename customers masters & clients query

* fix useClientsQuery & useMastersQuery query

* new line after 'use client' & 'use server' directives

* move getAvailableTimeSlots to server

* getAvailableTimeSlots: add filter by orders

* take into service duration when computing times

* fix GetSlotsOrders order

* take into existing orders when computing times

* fix build

* app/orders: fill page with content

* stores/order: split into slices

* components/orders: remove nested components dirs

* move order store -> orders\order-store

* replace ScheduleTimeContext with ScheduleStore

* fix slots queries

* context: rename contexts properly

* finally organized stores & context

* move order-card & time-range to @/components/shared

* Refactor/components folder structure (#24)

* refactor components/navigation

* refactor components/orders

* refactor components/profile

* refactor components/schedule

* remove components/common/spinner

* add launch.json

* add horizontal calendar

* remove context/date.tsx

* optimize orders list fetching

* add numberOfDaysBefore param

* fix orders list in slot page

* graphql/api: remove throw new Error

* horizontal-calendar: switch months by arrow buttons

* SlotCard: use SlotComponentProps type

* stores/schedule: export useScheduleStore

* SlotPage: add page header title

* contacts: mark inactive contacts

* prefetchQuery customer profile pages

* fix create slot

* packages: radash -> radashi

* fix queries, using formatDate & formatTime on client

* graphql: remove rename operations files

* fix create order query

* fix show actual slot status after slot update

* order page

* slot page: replace buttons with floating panel

* fix blur & colors

* fix floating panel overflows content

* hide ClientsOrdersList for non masters

* hooks/services: rename input -> variables

* move OrderCard types close to component

* exact types for Slot components & page

* app/profile: show shared orders

* order-services: fix types

* order page: add buttons

* order-card: add colors

* add order status alert

* fix badges & alerts

* take into account cancelled and completed orders in the slot list

* action panel: hide if no handlers

* highlight days with slots in schedule calendar

* highlight days in horizontal calendar

* remove getSlotsOrders fn

* show masters avatar in orders list

* fix auth redirects

* fix orders list for client

* create useIsMaster hook to prevent duplication

* order: revert cancel button for master

* FloatingActionPanel: block buttons while pending request

* hooks: invalidate orders & slots after mutate & delete

* order: revert approve button for master

* api/orders: protect update order

* order-card: show date

* order-card: add showDate variables in props

* order: add repeat button

* disable dashboard button

* apps/bot: beautify messages

* order: notify to telegram messages

* orderUpdate: add status info
2025-06-27 13:44:17 +03:00

169 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Badge } from '@repo/ui/components/ui/badge';
import { cn } from '@repo/ui/lib/utils';
import { AlertCircle, Calendar, CheckCircle, FileText, Lock, Unlock, XCircle } from 'lucide-react';
import { type JSX } from 'react';
const BADGE_BY_STATE: Record<string, JSX.Element> = {
approved: (
<Badge
className={cn(
'bg-blue-100 text-blue-600 hover:bg-current',
'dark:bg-blue-900 dark:text-blue-100',
'pointer-events-none select-none',
)}
variant="secondary"
>
Подтверждено
</Badge>
),
cancelled: (
<Badge
className={cn(
'bg-red-100 text-red-600 hover:bg-current',
'dark:bg-red-900 dark:text-red-100',
'pointer-events-none select-none',
)}
variant="secondary"
>
Отменено
</Badge>
),
cancelling: (
<Badge
className={cn(
'bg-orange-100 text-orange-600 hover:bg-current',
'dark:bg-orange-900 dark:text-orange-100',
'pointer-events-none select-none',
)}
variant="secondary"
>
Отменяется
</Badge>
),
closed: (
<Badge
className={cn(
'bg-zinc-200 text-zinc-600 hover:bg-current',
'dark:bg-zinc-800 dark:text-zinc-200',
'pointer-events-none select-none',
)}
variant="secondary"
>
Закрыто
</Badge>
),
completed: (
<Badge
className={cn(
'bg-green-100 text-green-600 hover:bg-current',
'dark:bg-green-900 dark:text-green-100',
'pointer-events-none select-none',
)}
variant="secondary"
>
Завершено
</Badge>
),
created: (
<Badge
className={cn('hover:bg-current', 'pointer-events-none select-none')}
variant="secondary"
>
Создано
</Badge>
),
open: (
<Badge
className={cn(
'bg-green-100 text-green-600 hover:bg-current',
'dark:bg-green-700 dark:text-green-100',
'pointer-events-none select-none',
)}
variant="secondary"
>
Открыто
</Badge>
),
scheduled: (
<Badge
className={cn(
'bg-purple-100 text-purple-600 hover:bg-current',
'dark:bg-purple-900 dark:text-purple-100',
'pointer-events-none select-none',
)}
variant="secondary"
>
Запланировано
</Badge>
),
};
export function getBadge(state: string) {
return BADGE_BY_STATE[state] ?? null;
}
function Alert({ className, ...props }: JSX.IntrinsicElements['div']) {
return (
<div className={cn('flex items-center gap-2 rounded-xl px-4 p-2', className)} {...props} />
);
}
function AlertTitle({ className, ...props }: JSX.IntrinsicElements['span']) {
return <span className={cn('line-clamp-1 font-medium tracking-tight', className)} {...props} />;
}
const ALERT_BY_STATE: Record<string, JSX.Element> = {
approved: (
<Alert className="bg-blue-50 text-blue-800 dark:bg-blue-950 dark:text-blue-200">
<CheckCircle className="size-5 text-blue-600 dark:text-blue-400" />
<AlertTitle>Подтверждено</AlertTitle>
</Alert>
),
cancelled: (
<Alert className="bg-red-50 text-red-800 dark:bg-red-950 dark:text-red-200">
<XCircle className="size-5 text-red-600 dark:text-red-400" />
<AlertTitle>Отменено</AlertTitle>
</Alert>
),
cancelling: (
<Alert className="bg-orange-50 text-orange-800 dark:bg-orange-950 dark:text-orange-200">
<AlertCircle className="size-5 text-orange-600 dark:text-orange-400" />
<AlertTitle>Ожидает отмены</AlertTitle>
</Alert>
),
closed: (
<Alert className="bg-zinc-100 text-zinc-800 dark:bg-zinc-900 dark:text-zinc-200">
<Lock className="size-5 text-zinc-700 dark:text-zinc-300" />
<AlertTitle>Закрыто</AlertTitle>
</Alert>
),
completed: (
<Alert className="bg-green-50 text-green-800 dark:bg-green-950 dark:text-green-200">
<CheckCircle className="size-5 text-green-600 dark:text-green-400" />
<AlertTitle>Завершено</AlertTitle>
</Alert>
),
created: (
<Alert className="bg-gray-50 text-gray-800 dark:bg-gray-900 dark:text-gray-200">
<FileText className="size-5 text-gray-600 dark:text-gray-400" />
<AlertTitle>Создано</AlertTitle>
</Alert>
),
opened: (
<Alert className="bg-cyan-50 text-cyan-800 dark:bg-cyan-950 dark:text-cyan-200">
<Unlock className="size-5 text-cyan-600 dark:text-cyan-400" />
<AlertTitle>Открыто</AlertTitle>
</Alert>
),
scheduled: (
<Alert className="bg-purple-50 text-purple-800 dark:bg-purple-950 dark:text-purple-200">
<Calendar className="size-5 text-purple-600 dark:text-purple-400" />
<AlertTitle>Запланировано</AlertTitle>
</Alert>
),
};
export function getAlert(state: string) {
return ALERT_BY_STATE[state] ?? null;
}