prepare for split contacts grid into masters/clients grid

This commit is contained in:
vchikalkin 2025-04-09 13:18:22 +03:00
parent 1e69802b82
commit 461bca0a0b
8 changed files with 82 additions and 54 deletions

View File

@ -1,61 +1,88 @@
'use client';
import { LoadingSpinner } from '../../common/spinner';
import { CardSectionHeader } from '@/components/ui';
import { ContactsFilterProvider } from '@/context/contacts-filter';
import { OrderContext } from '@/context/order';
import { useCustomerContacts } from '@/hooks/contacts';
// eslint-disable-next-line import/extensions
import AvatarPlaceholder from '@/public/avatar/avatar_placeholder.png';
import { withContext } from '@/utils/context';
import { type CustomerFieldsFragment } from '@repo/graphql/types';
import { Card } from '@repo/ui/components/ui/card';
import { Label } from '@repo/ui/components/ui/label';
import { cn } from '@repo/ui/lib/utils';
import Image from 'next/image';
import { use } from 'react';
export function ContactsGrid() {
const { customerId: selected, setCustomerId: setSelected } = use(OrderContext);
type ContactsGridProps = {
readonly contacts: CustomerFieldsFragment[];
readonly onSelect: (contactId: null | string) => void;
readonly selected?: null | string;
readonly title: string;
};
function ContactsGridBase({ contacts, onSelect, selected, title }: ContactsGridProps) {
return (
<Card className="p-4">
<div className="flex flex-col gap-4">
<CardSectionHeader title={title} />
<div className="grid max-h-screen grid-cols-4 gap-2 overflow-y-auto">
{contacts.map((contact) => {
if (!contact) return null;
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)}
type="radio"
value={contact?.documentId}
/>
<div
className={cn(
'w-20 h-20 rounded-full border-2 transition-all duration-75',
selected === contact?.documentId ? 'border-primary' : 'border-transparent',
)}
>
<div className="size-full border-4 border-transparent">
<Image
alt={contact?.name}
className="size-full rounded-full object-cover"
height={80}
src={contact?.photoUrl || AvatarPlaceholder}
width={80}
/>
</div>
</div>
<span className="mt-2 max-w-20 break-words text-center text-sm font-medium">
{contact?.name}
</span>
</Label>
);
})}
</div>
</div>
</Card>
);
}
export const ContactsGrid = withContext(ContactsFilterProvider)(function () {
const { contacts, isLoading } = useCustomerContacts();
const { customerId, setCustomerId } = use(OrderContext);
if (isLoading) return <LoadingSpinner />;
function handleOnSelect(contactId: string) {
setSelected(contactId);
}
return (
<div className="grid grid-cols-4 gap-4">
{contacts.map((contact) => {
if (!contact) return null;
return (
<Label className="flex cursor-pointer flex-col items-center" key={contact?.documentId}>
<input
checked={selected === contact?.documentId}
className="hidden"
name="user"
onChange={() => handleOnSelect(contact?.documentId)}
type="radio"
value={contact?.documentId}
/>
<div
className={cn(
'w-20 h-20 rounded-full border-2 transition-all duration-75',
selected === contact?.documentId ? 'border-primary' : 'border-transparent',
)}
>
<div className="size-full border-4 border-transparent">
<Image
alt={contact?.name}
className="size-full rounded-full object-cover"
height={80}
src={contact?.photoUrl || AvatarPlaceholder}
width={80}
/>
</div>
</div>
<span className="mt-2 max-w-20 break-words text-center text-sm font-medium">
{contact?.name}
</span>
</Label>
);
})}
</div>
<ContactsGridBase
contacts={contacts}
onSelect={(contactId) => setCustomerId(contactId)}
selected={customerId}
title="Все"
/>
);
}
});

View File

@ -1,4 +1,3 @@
export * from './card-header';
export * from './checkbox-field';
export * from './link-button';
export * from './text-field';

View File

@ -1,5 +1,6 @@
'use client';
import { CheckboxWithText, DataField, ProfileCardHeader } from './components';
import { CardSectionHeader } from '../ui';
import { CheckboxWithText, DataField } from './components';
import { type ProfileProps } from './types';
import { useProfileMutation, useProfileQuery } from '@/hooks/profile';
import { Enum_Customer_Role as Role } from '@repo/graphql/types';
@ -37,7 +38,7 @@ export function ProfileDataCard() {
return (
<Card className="p-4">
<div className="flex flex-col gap-4">
<ProfileCardHeader title="Ваши данные" />
<CardSectionHeader title="Ваши данные" />
<DataField
fieldName="name"
id="name"

View File

@ -2,7 +2,7 @@ type Props = {
title: string;
};
export function ProfileCardHeader({ title }: Readonly<Props>) {
export function CardSectionHeader({ title }: Readonly<Props>) {
return (
<div className="flex flex-row justify-between">
<h1 className="font-bold text-primary">{title}</h1>

View File

@ -0,0 +1 @@
export * from './card-header';

View File

@ -1,12 +1,12 @@
'use client';
import { createContext, useMemo, useState } from 'react';
import { createContext, type PropsWithChildren, useMemo, useState } from 'react';
export type FilterType = 'all' | 'clients' | 'masters';
type ContextType = { filter: FilterType; setFilter: (filter: FilterType) => void };
export const ContactsFilterContext = createContext<ContextType>({} as ContextType);
export function ContactsFilterProvider({ children }: { readonly children: React.ReactNode }) {
export function ContactsFilterProvider({ children }: Readonly<PropsWithChildren>) {
const [filter, setFilter] = useState<FilterType>('all');
const value = useMemo(() => ({ filter, setFilter }), [filter, setFilter]);

View File

@ -9,9 +9,9 @@ type ContextType = {
nextStep: () => void;
prevStep: () => void;
serviceId: null | string;
setCustomerId: (customerId: string) => void;
setCustomerId: (customerId: null | string) => void;
setDate: (date: Date) => void;
setServiceId: (serviceId: string) => void;
setServiceId: (serviceId: null | string) => void;
setStep: (step: Steps) => void;
setTime: (time: string) => void;
step: Steps;

View File

@ -5,7 +5,7 @@ import { sift } from 'radash';
import { use, useMemo } from 'react';
export function useCustomerContacts() {
const { filter } = use(ContactsFilterContext);
const { filter, setFilter } = use(ContactsFilterContext);
const { data: clientsData, isLoading: isLoadingClients } = useClientsQuery();
const { data: mastersData, isLoading: isLoadingMasters } = useMastersQuery();
@ -25,5 +25,5 @@ export function useCustomerContacts() {
}
}, [clients, masters, filter]);
return { contacts, isLoading };
return { contacts, filter, isLoading, setFilter };
}