prepare for split contacts grid into masters/clients grid
This commit is contained in:
parent
1e69802b82
commit
461bca0a0b
@ -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="Все"
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
export * from './card-header';
|
||||
export * from './checkbox-field';
|
||||
export * from './link-button';
|
||||
export * from './text-field';
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
1
apps/web/components/ui/index.ts
Normal file
1
apps/web/components/ui/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './card-header';
|
||||
@ -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]);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 };
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user