Refactor async components to synchronous functions for improved performance
- Converted several async components to synchronous functions, including `Layout`, `AddOrdersPage`, `ProfilePage`, `SlotPage`, and `ServicePage`, enhancing rendering efficiency. - Removed unnecessary prefetching logic and hydration boundaries, simplifying component structure and improving maintainability. - Updated the `TelegramProvider` to return null during the initial mount instead of a loading message, streamlining the loading state handling. - Enhanced loading state management in order-related components by adding loading spinners and data not found alerts, improving user experience during data fetching.
This commit is contained in:
parent
c7648e8bf9
commit
458a06a620
@ -1,6 +1,6 @@
|
||||
import { TelegramProvider } from '@/providers/telegram';
|
||||
import { type PropsWithChildren } from 'react';
|
||||
|
||||
export default async function Layout({ children }: Readonly<PropsWithChildren>) {
|
||||
export default function Layout({ children }: Readonly<PropsWithChildren>) {
|
||||
return <TelegramProvider>{children}</TelegramProvider>;
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { getOrder } from '@/actions/api/orders';
|
||||
import { Container } from '@/components/layout';
|
||||
import { PageHeader } from '@/components/navigation';
|
||||
import {
|
||||
@ -9,23 +8,14 @@ import {
|
||||
OrderStatus,
|
||||
} from '@/components/orders';
|
||||
import { type OrderPageParameters } from '@/components/orders/types';
|
||||
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query';
|
||||
|
||||
type Props = { params: Promise<OrderPageParameters> };
|
||||
|
||||
export default async function ProfilePage(props: Readonly<Props>) {
|
||||
const parameters = await props.params;
|
||||
const documentId = parameters.documentId;
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
await queryClient.prefetchQuery({
|
||||
queryFn: () => getOrder({ documentId }),
|
||||
queryKey: ['order', documentId],
|
||||
});
|
||||
|
||||
return (
|
||||
<HydrationBoundary state={dehydrate(queryClient)}>
|
||||
<>
|
||||
<PageHeader title="Запись" />
|
||||
<Container>
|
||||
<OrderDateTime {...parameters} />
|
||||
@ -35,6 +25,6 @@ export default async function ProfilePage(props: Readonly<Props>) {
|
||||
<div className="pb-24" />
|
||||
<OrderButtons {...parameters} />
|
||||
</Container>
|
||||
</HydrationBoundary>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import { Container } from '@/components/layout';
|
||||
import { PageHeader } from '@/components/navigation';
|
||||
import { OrderForm } from '@/components/orders';
|
||||
|
||||
export default async function AddOrdersPage() {
|
||||
export default function AddOrdersPage() {
|
||||
return (
|
||||
<>
|
||||
<PageHeader title="Новая запись" />
|
||||
|
||||
@ -1,26 +1,16 @@
|
||||
import { getSlot } from '@/actions/api/slots';
|
||||
import { Container } from '@/components/layout';
|
||||
import { PageHeader } from '@/components/navigation';
|
||||
import { SlotButtons, SlotDateTime, SlotOrdersList } from '@/components/schedule';
|
||||
import { type SlotPageParameters } from '@/components/schedule/types';
|
||||
import { BookButton } from '@/components/shared/book-button';
|
||||
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query';
|
||||
|
||||
type Props = { params: Promise<SlotPageParameters> };
|
||||
|
||||
export default async function SlotPage(props: Readonly<Props>) {
|
||||
const parameters = await props.params;
|
||||
const documentId = parameters.documentId;
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
await queryClient.prefetchQuery({
|
||||
queryFn: () => getSlot({ documentId }),
|
||||
queryKey: ['slot', documentId],
|
||||
});
|
||||
|
||||
return (
|
||||
<HydrationBoundary state={dehydrate(queryClient)}>
|
||||
<>
|
||||
<PageHeader title="Слот" />
|
||||
<Container>
|
||||
<SlotDateTime {...parameters} />
|
||||
@ -29,6 +19,6 @@ export default async function SlotPage(props: Readonly<Props>) {
|
||||
<div className="pb-24" />
|
||||
<SlotButtons {...parameters} />
|
||||
</Container>
|
||||
</HydrationBoundary>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,28 +1,20 @@
|
||||
import { getService } from '@/actions/api/services';
|
||||
import { Container } from '@/components/layout';
|
||||
import { PageHeader } from '@/components/navigation';
|
||||
import { ServiceButtons, ServiceDataCard } from '@/components/profile/services';
|
||||
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query';
|
||||
|
||||
// Тип параметров страницы
|
||||
type Props = { params: Promise<{ serviceId: string }> };
|
||||
|
||||
export default async function ProfilePage(props: Readonly<Props>) {
|
||||
const { serviceId } = await props.params;
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
await queryClient.prefetchQuery({
|
||||
queryFn: () => getService({ documentId: serviceId }),
|
||||
queryKey: ['service', serviceId],
|
||||
});
|
||||
|
||||
return (
|
||||
<HydrationBoundary state={dehydrate(queryClient)}>
|
||||
<>
|
||||
<PageHeader title="Услуга" />
|
||||
<Container className="px-0">
|
||||
<ServiceDataCard serviceId={serviceId} />
|
||||
<ServiceButtons serviceId={serviceId} />
|
||||
</Container>
|
||||
</HydrationBoundary>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,25 +1,29 @@
|
||||
'use client';
|
||||
import { DataNotFound } from '../shared/alert';
|
||||
import { ContactRow } from '../shared/contact-row';
|
||||
import { type OrderComponentProps } from './types';
|
||||
import { useOrderQuery } from '@/hooks/api/orders';
|
||||
import { LoadingSpinner } from '@repo/ui/components/ui/spinner';
|
||||
|
||||
export function OrderContacts({ documentId }: Readonly<OrderComponentProps>) {
|
||||
const { data: { order } = {} } = useOrderQuery({ documentId });
|
||||
const { data: { order } = {}, isLoading } = useOrderQuery({ documentId });
|
||||
|
||||
if (!order) return null;
|
||||
const noContacts = !order?.slot?.master && !order?.client;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h1 className="font-bold">Участники</h1>
|
||||
<div className="space-y-2">
|
||||
{order.slot?.master && (
|
||||
{isLoading && <LoadingSpinner />}
|
||||
{!isLoading && noContacts ? <DataNotFound title="Пользователи не найдены" /> : null}
|
||||
{order?.slot?.master && (
|
||||
<ContactRow
|
||||
className="rounded-2xl bg-background p-2 px-4 dark:bg-primary/5"
|
||||
description="Мастер"
|
||||
{...order.slot?.master}
|
||||
/>
|
||||
)}
|
||||
{order.client && (
|
||||
{order?.client && (
|
||||
<ContactRow
|
||||
className="rounded-2xl bg-background p-2 px-4 dark:bg-primary/5"
|
||||
description="Клиент"
|
||||
|
||||
@ -6,7 +6,16 @@ import { useOrderQuery } from '@/hooks/api/orders';
|
||||
import { formatDate } from '@repo/utils/datetime-format';
|
||||
|
||||
export function OrderDateTime({ documentId }: Readonly<OrderComponentProps>) {
|
||||
const { data: { order } = {} } = useOrderQuery({ documentId });
|
||||
const { data: { order } = {}, isLoading } = useOrderQuery({ documentId });
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex animate-pulse flex-col space-y-1">
|
||||
<div className="h-5 w-28 rounded bg-muted" />
|
||||
<div className="h-9 w-48 rounded bg-muted" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!order) return null;
|
||||
|
||||
|
||||
@ -1,18 +1,20 @@
|
||||
'use client';
|
||||
|
||||
import { DataNotFound } from '../shared/alert';
|
||||
import { ServiceCard } from '../shared/service-card';
|
||||
import { type OrderComponentProps } from './types';
|
||||
import { useOrderQuery } from '@/hooks/api/orders';
|
||||
import { LoadingSpinner } from '@repo/ui/components/ui/spinner';
|
||||
|
||||
export function OrderServices({ documentId }: Readonly<OrderComponentProps>) {
|
||||
const { data: { order } = {} } = useOrderQuery({ documentId });
|
||||
|
||||
if (!order) return null;
|
||||
const { data: { order } = {}, isLoading } = useOrderQuery({ documentId });
|
||||
|
||||
return (
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h1 className="font-bold">Услуги</h1>
|
||||
{order.services?.map(
|
||||
{isLoading && <LoadingSpinner />}
|
||||
{!isLoading && !order?.services?.length ? <DataNotFound title="Услуги не найдены" /> : null}
|
||||
{order?.services?.map(
|
||||
(service) => service && <ServiceCard key={service.documentId} {...service} />,
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -5,7 +5,14 @@ import { getAlert } from '@/components/shared/status';
|
||||
import { useOrderQuery } from '@/hooks/api/orders';
|
||||
|
||||
export function OrderStatus({ documentId }: Readonly<OrderComponentProps>) {
|
||||
const { data: { order } = {} } = useOrderQuery({ documentId });
|
||||
const { data: { order } = {}, isLoading } = useOrderQuery({ documentId });
|
||||
|
||||
if (isLoading)
|
||||
return (
|
||||
<div className="flex animate-pulse flex-col space-y-1">
|
||||
<div className="h-10 w-full rounded bg-muted" />
|
||||
</div>
|
||||
);
|
||||
|
||||
return order?.state && getAlert(order.state);
|
||||
}
|
||||
|
||||
@ -57,6 +57,8 @@ export function ProfileDataCard() {
|
||||
<div className="h-10 w-full rounded bg-muted" />
|
||||
<div className="h-4 w-16 rounded bg-muted" />
|
||||
<div className="h-10 w-full rounded bg-muted" />
|
||||
<div className="h-4 w-16 rounded bg-muted" />
|
||||
<div className="h-10 w-full rounded bg-muted" />
|
||||
<div className="h-5 w-60 rounded bg-muted" />
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@ -12,10 +12,27 @@ type Props = {
|
||||
};
|
||||
|
||||
export function ServiceDataCard({ serviceId }: Readonly<Props>) {
|
||||
const { data: { service } = {} } = useServiceQuery({ documentId: serviceId });
|
||||
const { data: { service } = {}, isLoading } = useServiceQuery({ documentId: serviceId });
|
||||
const { cancelChanges, hasChanges, isPending, resetTrigger, saveChanges, updateField } =
|
||||
useServiceEdit(serviceId);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Card className="p-4">
|
||||
<div className="flex animate-pulse flex-col gap-4">
|
||||
<div className="h-4 w-16 rounded bg-muted" />
|
||||
<div className="h-10 w-full rounded bg-muted" />
|
||||
<div className="h-4 w-16 rounded bg-muted" />
|
||||
<div className="h-10 w-full rounded bg-muted" />
|
||||
<div className="h-4 w-16 rounded bg-muted" />
|
||||
<div className="h-10 w-full rounded bg-muted" />
|
||||
<div className="h-4 w-16 rounded bg-muted" />
|
||||
<div className="h-28 w-full rounded bg-muted" />
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (!service) return null;
|
||||
|
||||
return (
|
||||
|
||||
@ -3,12 +3,24 @@
|
||||
import { type SlotComponentProps } from '../types';
|
||||
import { SlotDate } from './slot-date';
|
||||
import { SlotTime } from './slot-time';
|
||||
import { useSlotQuery } from '@/hooks/api/slots';
|
||||
import { ScheduleStoreProvider } from '@/stores/schedule';
|
||||
import { withContext } from '@/utils/context';
|
||||
|
||||
export const SlotDateTime = withContext(ScheduleStoreProvider)(function (
|
||||
props: Readonly<SlotComponentProps>,
|
||||
) {
|
||||
const { isLoading } = useSlotQuery(props);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex animate-pulse flex-col space-y-1">
|
||||
<div className="h-5 w-28 rounded bg-muted" />
|
||||
<div className="h-9 w-48 rounded bg-muted" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<SlotDate {...props} />
|
||||
|
||||
@ -13,7 +13,7 @@ export function TelegramProvider(props: Readonly<PropsWithChildren>) {
|
||||
// side.
|
||||
const didMount = useDidMount();
|
||||
|
||||
if (!didMount) return <div>Loading</div>;
|
||||
if (!didMount) return null;
|
||||
|
||||
return <RootInner {...props} />;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user