import { keepPreviousData, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { formatDateTime, type Locale } from '@barev/i18n'; import { navigate, useLocation, useToast, useTRPC } from '@barev/web'; import { useEffect, useMemo, useState } from 'react'; import type { Me } from '../App.tsx'; import { errText, useT } from '../i18n.tsx'; import { I, Skeleton } from '../components/kit.tsx'; import { NewOrderDialog } from './NewOrder.tsx'; import { OrdersTabs } from './Moves.tsx'; type Group = 'active' | 'new' | 'pending' | 'assigned' | 'in_progress' | 'done' | 'cancelled' | 'all'; type Period = 'all' | 'today' | 'tomorrow' | 'week' | 'past'; const GROUPS: Group[] = ['active', 'new', 'pending', 'assigned', 'in_progress', 'done', 'cancelled']; const PERIODS: Period[] = ['all', 'today', 'tomorrow', 'week', 'past']; const MIN = 60_000; /** Цвет группы — как точка статуса */ export const GROUP_DOT: Record = { new: 'var(--bc-accent)', pending: 'var(--bc-perks-ink)', assigned: 'var(--bc-blue-ink)', in_progress: 'var(--bc-ok)', done: 'var(--bc-ink-3)', cancelled: 'var(--bc-danger)', }; export function Orders({ me }: { me: Me }) { const { t, locale, shared, amd, time } = useT(); const trpc = useTRPC(); const { query } = useLocation(); const group = (query.get('group') as Group) || 'active'; const period = (query.get('period') as Period) || 'all'; const [search, setSearch] = useState(query.get('q') ?? ''); const [q, setQ] = useState(search); const [creating, setCreating] = useState(false); const isSuper = me.roles.includes('SUPERADMIN'); useEffect(() => { const id = setTimeout(() => setQ(search.trim()), 300); return () => clearTimeout(id); }, [search]); const set = (patch: { group?: Group; period?: Period }) => { const p = new URLSearchParams(); const g = patch.group ?? group; const per = patch.period ?? period; if (g !== 'active') p.set('group', g); if (per !== 'all') p.set('period', per); const s = p.toString(); navigate(`/orders${s ? `?${s}` : ''}`, { replace: true }); }; const list = useQuery({ ...trpc.bo.orders.list.queryOptions({ group, period, search: q || undefined }), refetchInterval: 20_000, placeholderData: keepPreviousData }); const counts = useQuery({ ...trpc.bo.orders.counts.queryOptions({ period, search: q || undefined }), refetchInterval: 20_000 }); const c = counts.data; const countOf = (g: Group) => (!c ? null : g === 'active' ? c.new + c.pending + c.assigned + c.in_progress : g === 'all' ? null : c[g]); // по дням: «Сегодня, 21 окт.» const days = useMemo(() => { const out: { key: string; label: string; rows: NonNullable }[] = []; const dayKey = (d: Date) => formatDateTime(locale as Locale, d, { year: 'numeric', month: '2-digit', day: '2-digit' }); const today = dayKey(new Date()); const tomorrow = dayKey(new Date(Date.now() + 86_400_000)); for (const r of list.data ?? []) { const k = dayKey(r.scheduledAt); let d = out.at(-1); if (!d || d.key !== k) { const nice = formatDateTime(locale as Locale, r.scheduledAt, { weekday: 'short', day: 'numeric', month: 'long' }); const label = k === today ? `${t('ord.today')}, ${nice}` : k === tomorrow ? `${t('ord.tomorrow')}, ${nice}` : nice; d = { key: k, label: label.charAt(0).toUpperCase() + label.slice(1), rows: [] }; out.push(d); } d.rows.push(r); } return out; }, [list.data, locale, t]); return ( <>

{t('ord.title')}

{!!c?.needsDispatcher && group !== 'new' && ( )}
{PERIODS.map((p) => ( ))}
{GROUPS.map((g) => { const n = countOf(g); return ( ); })}
{list.isPending ? (
) : days.length === 0 ? (
{t('ord.empty')}
) : (
{days.map((d) => (

{d.label} {d.rows.length}

))}
)} {creating && setCreating(false)} />} ); } export function statusLabel(shared: (k: string) => string, flow: string, status: string) { const k = `status.${flow}.${status}`; const v = shared(k); return v === k ? status : v; } /** Автораспределение: видно всем, переключает суперадмин */ function AutoSwitch({ canEdit }: { canEdit: boolean }) { const { t } = useT(); const trpc = useTRPC(); const qc = useQueryClient(); const toast = useToast(); const s = useQuery(trpc.bo.orders.autoSetting.queryOptions()); const save = useMutation( trpc.bo.orders.setAutoSetting.mutationOptions({ onSuccess: () => qc.invalidateQueries({ queryKey: trpc.bo.orders.autoSetting.queryKey() }), onError: (e) => toast(errText(t, e), 'error'), }), ); if (!s.data) return null; const on = s.data.auto; return ( ); }