From 24f71a9b66abd7879df69e033724d7fb61efcc12 Mon Sep 17 00:00:00 2001 From: vchikalkin Date: Tue, 24 Jun 2025 13:47:53 +0300 Subject: [PATCH] add order status alert --- .../app/(main)/orders/[documentId]/page.tsx | 9 +- apps/web/components/orders/index.ts | 1 + apps/web/components/orders/order-status.tsx | 25 +++++ apps/web/components/shared/order-card.tsx | 30 +----- apps/web/utils/components/order/index.ts | 1 + apps/web/utils/components/order/status.ts | 92 +++++++++++++++++++ packages/ui/src/components/ui/alert.tsx | 59 ++++++++++++ 7 files changed, 187 insertions(+), 30 deletions(-) create mode 100644 apps/web/components/orders/order-status.tsx create mode 100644 apps/web/utils/components/order/index.ts create mode 100644 apps/web/utils/components/order/status.ts create mode 100644 packages/ui/src/components/ui/alert.tsx diff --git a/apps/web/app/(main)/orders/[documentId]/page.tsx b/apps/web/app/(main)/orders/[documentId]/page.tsx index e9f4b74..7ec03c3 100644 --- a/apps/web/app/(main)/orders/[documentId]/page.tsx +++ b/apps/web/app/(main)/orders/[documentId]/page.tsx @@ -1,7 +1,13 @@ import { getOrder } from '@/actions/api/orders'; import { Container } from '@/components/layout'; import { PageHeader } from '@/components/navigation'; -import { OrderButtons, OrderContacts, OrderDateTime, OrderServices } from '@/components/orders'; +import { + OrderButtons, + OrderContacts, + OrderDateTime, + OrderServices, + OrderStatus, +} from '@/components/orders'; import { type OrderPageParameters } from '@/components/orders/types'; import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'; @@ -23,6 +29,7 @@ export default async function ProfilePage(props: Readonly) { +
diff --git a/apps/web/components/orders/index.ts b/apps/web/components/orders/index.ts index adece76..897ea93 100644 --- a/apps/web/components/orders/index.ts +++ b/apps/web/components/orders/index.ts @@ -4,4 +4,5 @@ export * from './order-contacts'; export * from './order-datetime'; export * from './order-form'; export * from './order-services'; +export * from './order-status'; export * from './orders-list'; diff --git a/apps/web/components/orders/order-status.tsx b/apps/web/components/orders/order-status.tsx new file mode 100644 index 0000000..92c895e --- /dev/null +++ b/apps/web/components/orders/order-status.tsx @@ -0,0 +1,25 @@ +'use client'; + +import { type OrderComponentProps } from './types'; +import { useOrderQuery } from '@/hooks/api/orders'; +import { getAlertStyles, getBadgeText, getStatusIcon } from '@/utils/components/order'; +import { Alert, AlertTitle } from '@repo/ui/components/ui/alert'; + +export function OrderStatus({ documentId }: Readonly) { + const { data: { order } = {} } = useOrderQuery({ documentId }); + + const state = order?.state; + + if (!state) return null; + + const title = getBadgeText(state); + const styles = getAlertStyles(state); + const { icon: StatusIcon, iconStyles } = getStatusIcon(state); + + return ( + + + {title} + + ); +} diff --git a/apps/web/components/shared/order-card.tsx b/apps/web/components/shared/order-card.tsx index a3e36b3..67079d7 100644 --- a/apps/web/components/shared/order-card.tsx +++ b/apps/web/components/shared/order-card.tsx @@ -1,7 +1,7 @@ 'use client'; import { ReadonlyTimeRange } from './time-range/readonly'; -import { type Enum_Order_State } from '@repo/graphql/types'; +import { getBadgeStyles, getBadgeText } from '@/utils/components/order'; import type * as GQL from '@repo/graphql/types'; import { Avatar, AvatarFallback, AvatarImage } from '@repo/ui/components/ui/avatar'; import { Badge } from '@repo/ui/components/ui/badge'; @@ -44,31 +44,3 @@ function ClientAvatar({ client }: { readonly client: OrderClient }) { ); } - -const MAP_BADGE_TEXT: Record = { - approved: 'Подтверждено', - cancelled: 'Отменено', - cancelling: 'Отменяется', - completed: 'Завершено', - created: 'Создано', - scheduled: 'Запланировано', -}; - -const MAP_BADGE_STYLES: Record = { - approved: 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-100', - cancelled: 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-100', - cancelling: 'bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-100', - completed: 'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-100', - created: '', - scheduled: 'bg-purple-100 text-purple-700 dark:bg-purple-900 dark:text-purple-100', -}; - -function getBadgeStyles(state: Enum_Order_State) { - if (!state) return ''; - return MAP_BADGE_STYLES[state]; -} - -function getBadgeText(state: Enum_Order_State) { - if (!state) return ''; - return MAP_BADGE_TEXT[state]; -} diff --git a/apps/web/utils/components/order/index.ts b/apps/web/utils/components/order/index.ts new file mode 100644 index 0000000..420cc02 --- /dev/null +++ b/apps/web/utils/components/order/index.ts @@ -0,0 +1 @@ +export * from './status'; diff --git a/apps/web/utils/components/order/status.ts b/apps/web/utils/components/order/status.ts new file mode 100644 index 0000000..c1da0b5 --- /dev/null +++ b/apps/web/utils/components/order/status.ts @@ -0,0 +1,92 @@ +import { type Enum_Order_State } from '@repo/graphql/types'; +import { AlertCircle, Calendar, CheckCircle, FileText, XCircle } from 'lucide-react'; + +const BADGE_TEXT: Record = { + approved: 'Подтверждено', + cancelled: 'Отменено', + cancelling: 'Отменяется', + completed: 'Завершено', + created: 'Создано', + scheduled: 'Запланировано', +}; + +const BADGE_STYLES: Record = { + approved: 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-100', + cancelled: 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-100', + cancelling: 'bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-100', + completed: 'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-100', + created: '', + scheduled: 'bg-purple-100 text-purple-700 dark:bg-purple-900 dark:text-purple-100', +}; + +const ALERT_STYLES: Record = { + approved: + 'border-blue-200 bg-blue-50 text-blue-800 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-200', + cancelled: + 'border-red-200 bg-red-50 text-red-800 dark:border-red-800 dark:bg-red-950 dark:text-red-200', + cancelling: + 'border-orange-200 bg-orange-50 text-orange-800 dark:border-orange-800 dark:bg-orange-950 dark:text-orange-200', + completed: + 'border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950 dark:text-green-200', + created: + 'border-gray-200 bg-gray-50 text-gray-800 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-200', + scheduled: + 'border-purple-200 bg-purple-50 text-purple-800 dark:border-purple-800 dark:bg-purple-950 dark:text-purple-200', +}; + +const STATUS_ICONS: Record< + Enum_Order_State, + { + icon: React.ComponentType<{ className?: string }>; + iconStyles: string; + } +> = { + approved: { + icon: CheckCircle, + iconStyles: 'text-blue-600 dark:text-blue-400', + }, + cancelled: { + icon: XCircle, + iconStyles: 'text-red-600 dark:text-red-400', + }, + cancelling: { + icon: AlertCircle, + iconStyles: 'text-orange-600 dark:text-orange-400', + }, + completed: { + icon: CheckCircle, + iconStyles: 'text-green-600 dark:text-green-400', + }, + created: { + icon: FileText, + iconStyles: 'text-gray-600 dark:text-gray-400', + }, + scheduled: { + icon: Calendar, + iconStyles: 'text-purple-600 dark:text-purple-400', + }, +}; + +export function getAlertStyles(state: Enum_Order_State) { + if (!state) return ''; + + return ALERT_STYLES[state]; +} + +export function getBadgeStyles(state: Enum_Order_State) { + if (!state) return ''; + + return BADGE_STYLES[state]; +} + +export function getBadgeText(state: Enum_Order_State) { + if (!state) return ''; + + return BADGE_TEXT[state]; +} + +export function getStatusIcon(state: Enum_Order_State) { + if (!state) return { icon: () => null, iconStyles: '' }; + + return STATUS_ICONS[state]; +} diff --git a/packages/ui/src/components/ui/alert.tsx b/packages/ui/src/components/ui/alert.tsx new file mode 100644 index 0000000..e339967 --- /dev/null +++ b/packages/ui/src/components/ui/alert.tsx @@ -0,0 +1,59 @@ +import { cn } from '@repo/ui/lib/utils'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { type ComponentProps } from 'react'; + +const alertVariants = cva( + 'relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current', + { + defaultVariants: { + variant: 'default', + }, + variants: { + variant: { + default: 'bg-card text-card-foreground', + destructive: + 'bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 [&>svg]:text-current', + }, + }, + }, +); + +function Alert({ + className, + variant, + ...props +}: ComponentProps<'div'> & VariantProps) { + return ( +
+ ); +} + +function AlertDescription({ className, ...props }: ComponentProps<'div'>) { + return ( +
+ ); +} + +function AlertTitle({ className, ...props }: ComponentProps<'div'>) { + return ( +
+ ); +} + +export { Alert, AlertDescription, AlertTitle };