2025-10-11 14:09:18 +03:00

173 lines
5.6 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.

/* eslint-disable canonical/id-match */
'use client';
import { DataNotFound } from '@/components/shared/alert';
import { UserAvatar } from '@/components/shared/user-avatar';
import { CardSectionHeader } from '@/components/ui';
import { useContactsInfiniteQuery, useCustomerQuery } from '@/hooks/api/customers';
import { useOrderStore } from '@/stores/order';
import { type CustomerFieldsFragment, Enum_Customer_Role } from '@repo/graphql/types';
import { Button } from '@repo/ui/components/ui/button';
import { Card } from '@repo/ui/components/ui/card';
import { Label } from '@repo/ui/components/ui/label';
import { LoadingSpinner } from '@repo/ui/components/ui/spinner';
import { cn } from '@repo/ui/lib/utils';
import { getCustomerFullName } from '@repo/utils/customer';
import { sift } from 'radashi';
type ContactsGridProps = {
readonly contacts: CustomerFieldsFragment[];
readonly hasNextPage?: boolean;
readonly isLoading?: boolean;
readonly onClick: () => void;
readonly onFetchNextPage?: () => void;
readonly onSelect: (contactId: null | string) => void;
readonly selected?: null | string;
readonly title: string;
};
type UseContactsProps = Partial<{
showInactive: boolean;
}>;
export function ClientsGrid() {
const { contacts, fetchNextPage, hasNextPage, isLoading } = useContacts({ showInactive: true });
const clientId = useOrderStore((store) => store.clientId);
const setClientId = useOrderStore((store) => store.setClientId);
const masterId = useOrderStore((store) => store.masterId);
return (
<ContactsGridBase
contacts={contacts.filter((contact) => contact.documentId !== masterId)}
hasNextPage={Boolean(hasNextPage)}
isLoading={isLoading}
onClick={() => {
if (clientId) setClientId(null);
}}
onFetchNextPage={fetchNextPage}
onSelect={(contactId) => setClientId(contactId)}
selected={clientId}
title="Выбор клиента"
/>
);
}
export function ContactsGridBase({
contacts,
hasNextPage,
isLoading,
onClick,
onFetchNextPage,
onSelect,
selected,
title,
}: ContactsGridProps) {
const { data: { customer } = {} } = useCustomerQuery();
return (
<Card className="p-4">
<div className="flex flex-col gap-4">
<CardSectionHeader title={title} />
{isLoading && <LoadingSpinner />}
{!isLoading && (!contacts || contacts.length === 0) ? (
<DataNotFound title="Контакты не найдены" />
) : null}
<div className="grid max-h-screen grid-cols-4 gap-2 overflow-y-auto">
{!isLoading &&
contacts?.map((contact) => {
if (!contact) return null;
const isCurrentUser = contact.documentId === customer?.documentId;
return (
<Label
className="flex cursor-pointer flex-col items-center"
key={contact.documentId}
>
<input
checked={selected === contact.documentId}
className="hidden"
name="user"
onChange={() => onSelect(contact.documentId)}
onClick={onClick}
type="radio"
value={contact.documentId}
/>
<div
className={cn(
'rounded-full border-2 transition-all duration-75',
selected === contact.documentId ? 'border-primary' : 'border-transparent',
)}
>
<UserAvatar {...contact} size="md" />
</div>
<span
className={cn(
'mt-2 max-w-20 break-words text-center text-sm font-medium',
isCurrentUser && 'font-bold',
)}
>
{getCustomerFullName(contact)}
</span>
</Label>
);
})}
</div>
{hasNextPage && onFetchNextPage && (
<Button onClick={onFetchNextPage} variant="ghost">
Загрузить еще
</Button>
)}
</div>
</Card>
);
}
export function MastersGrid() {
const { contacts, fetchNextPage, hasNextPage, isLoading } = useContacts();
const masterId = useOrderStore((store) => store.masterId);
const setMasterId = useOrderStore((store) => store.setMasterId);
const clientId = useOrderStore((store) => store.clientId);
return (
<ContactsGridBase
contacts={contacts.filter(
(contact) => contact.documentId !== clientId && contact.role !== Enum_Customer_Role.Client,
)}
hasNextPage={Boolean(hasNextPage)}
isLoading={isLoading}
onClick={() => {
if (masterId) setMasterId(null);
}}
onFetchNextPage={fetchNextPage}
onSelect={(contactId) => setMasterId(contactId)}
selected={masterId}
title="Выбор мастера"
/>
);
}
function useContacts({ showInactive = false }: UseContactsProps = {}) {
const { data: { customer } = {}, isLoading: isLoadingCustomer } = useCustomerQuery();
const {
data: { pages } = { pages: [] },
isLoading: isLoadingContacts,
...query
} = useContactsInfiniteQuery();
const isLoading = isLoadingContacts || isLoadingCustomer;
const contacts = sift(pages.flatMap((page) => page.customers));
return {
isLoading,
...query,
contacts: [{ ...customer, name: 'Я', surname: undefined } as CustomerFieldsFragment].concat(
showInactive ? contacts : contacts.filter((contact) => contact.active),
),
};
}