fix badges & alerts
This commit is contained in:
parent
24f71a9b66
commit
a3fe14a53c
@ -1,25 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { type OrderComponentProps } from './types';
|
||||
import { getAlert } from '@/components/shared/status';
|
||||
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<OrderComponentProps>) {
|
||||
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 (
|
||||
<Alert className={styles}>
|
||||
<StatusIcon className={iconStyles} />
|
||||
<AlertTitle>{title}</AlertTitle>
|
||||
</Alert>
|
||||
);
|
||||
return order?.state && getAlert(order.state);
|
||||
}
|
||||
|
||||
@ -1,22 +1,16 @@
|
||||
/* eslint-disable canonical/id-match */
|
||||
'use client';
|
||||
|
||||
import { type SlotComponentProps } from '../types';
|
||||
import { getBadge } from '@/components/shared/status';
|
||||
import { ReadonlyTimeRange } from '@/components/shared/time-range';
|
||||
import { useSlotQuery } from '@/hooks/api/slots';
|
||||
import { Enum_Slot_State } from '@repo/graphql/types';
|
||||
import { Badge } from '@repo/ui/components/ui/badge';
|
||||
import type * as GQL from '@repo/graphql/types';
|
||||
import { cn } from '@repo/ui/lib/utils';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
const MAP_BADGE_TEXT: Record<Enum_Slot_State, string> = {
|
||||
closed: 'Закрыто',
|
||||
open: 'Открыто',
|
||||
reserved: 'Зарезервировано',
|
||||
};
|
||||
type Props = GQL.SlotFieldsFragment;
|
||||
|
||||
export function SlotCard(props: Readonly<SlotComponentProps>) {
|
||||
export function SlotCard(props: Readonly<Props>) {
|
||||
const pathname = usePathname();
|
||||
const { documentId } = props;
|
||||
|
||||
@ -25,9 +19,6 @@ export function SlotCard(props: Readonly<SlotComponentProps>) {
|
||||
const ordersNumber = slot?.orders?.length;
|
||||
const hasOrders = Boolean(ordersNumber);
|
||||
|
||||
const isOpened = slot?.state === Enum_Slot_State.Open;
|
||||
const isClosed = slot?.state === Enum_Slot_State.Closed;
|
||||
|
||||
return (
|
||||
<Link href={`${pathname}/slots/${props.documentId}`} rel="noopener noreferrer">
|
||||
<div className="flex items-center justify-between rounded-2xl bg-background p-4 px-6 dark:bg-primary/5">
|
||||
@ -42,23 +33,8 @@ export function SlotCard(props: Readonly<SlotComponentProps>) {
|
||||
{hasOrders ? 'Есть записи' : 'Свободно'}
|
||||
</span>
|
||||
</div>
|
||||
{slot?.state && (
|
||||
<Badge
|
||||
className={cn(
|
||||
isOpened ? 'bg-green-100 text-green-500 dark:bg-green-700 dark:text-green-100' : '',
|
||||
isClosed ? 'bg-red-100 text-red-500 dark:bg-red-700 dark:text-red-100' : '',
|
||||
)}
|
||||
>
|
||||
{getBadgeText(slot.state)}
|
||||
</Badge>
|
||||
)}
|
||||
{slot?.state && getBadge(slot.state)}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
function getBadgeText(state: Enum_Slot_State) {
|
||||
if (!state) return '';
|
||||
|
||||
return MAP_BADGE_TEXT[state];
|
||||
}
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import { ReadonlyTimeRange } from './time-range/readonly';
|
||||
import { getBadgeStyles, getBadgeText } from '@/utils/components/order';
|
||||
import { getBadge } from '@/components/shared/status';
|
||||
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';
|
||||
import Link from 'next/link';
|
||||
|
||||
type OrderClient = NonNullable<OrderComponentProps>['client'];
|
||||
@ -26,9 +25,7 @@ export function OrderCard({ documentId, ...order }: Readonly<OrderComponentProps
|
||||
{/* <span className="text-xs text-foreground">{clientName}</span> */}
|
||||
</div>
|
||||
</div>
|
||||
{order?.state && (
|
||||
<Badge className={getBadgeStyles(order.state)}>{getBadgeText(order.state)}</Badge>
|
||||
)}
|
||||
{order?.state && getBadge(order.state)}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
|
||||
168
apps/web/components/shared/status.tsx
Normal file
168
apps/web/components/shared/status.tsx
Normal file
@ -0,0 +1,168 @@
|
||||
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;
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from './status';
|
||||
@ -1,92 +0,0 @@
|
||||
import { type Enum_Order_State } from '@repo/graphql/types';
|
||||
import { AlertCircle, Calendar, CheckCircle, FileText, XCircle } from 'lucide-react';
|
||||
|
||||
const BADGE_TEXT: Record<Enum_Order_State, string> = {
|
||||
approved: 'Подтверждено',
|
||||
cancelled: 'Отменено',
|
||||
cancelling: 'Отменяется',
|
||||
completed: 'Завершено',
|
||||
created: 'Создано',
|
||||
scheduled: 'Запланировано',
|
||||
};
|
||||
|
||||
const BADGE_STYLES: Record<Enum_Order_State, string> = {
|
||||
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<Enum_Order_State, string> = {
|
||||
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];
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user