ui phase 1

This commit is contained in:
hamid
2026-07-17 17:10:39 +03:30
parent f1cba6cf74
commit 370c1beefa
151 changed files with 4254 additions and 1840 deletions
@@ -0,0 +1,90 @@
'use client';
import { useLocale, useTranslations } from 'next-intl';
import { useRouter } from 'next/navigation';
import { Box, Paper, Skeleton, Stack, Typography } from '@mui/material';
import { AppButton, EmptyState, ErrorState, Money, StatusChip } from '@/components';
import { BOOKING_STATUS_KIND } from '@/components/booking/statusKind';
import { ROUTES } from '@/constants';
import { formatShamsiDate } from '@/utils';
import { useBookingList } from '@/services/bookings';
import type { BookingListItemDto } from '@/services/bookings/types';
/**
* Customer رزروها — the "My bookings" list. Reads `useBookingList('customer')`; each row opens the
* booking detail (`/bookings/{id}`). This is the customer entry to the f8 booking-detail surface (the C5
* `converted` state also lands here). Amounts render in Toman via the money util.
*/
export default function BookingsScreen() {
const t = useTranslations('booking');
const { data, isLoading, isError, refetch } = useBookingList('customer');
const items = data?.items ?? [];
return (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
<Box>
<Typography variant="h5" component="h1">
{t('list_title')}
</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{t('list_subtitle')}
</Typography>
</Box>
{isLoading ? (
<Stack sx={{ gap: 2 }}>
{[0, 1].map((key) => (
<Skeleton key={key} variant="rounded" height={120} />
))}
</Stack>
) : isError ? (
<ErrorState message={t('list_error')} retryLabel={t('retry')} onRetry={() => refetch()} />
) : items.length === 0 ? (
<EmptyState icon="bookings" title={t('list_empty_title')} body={t('list_empty_body')} />
) : (
<Stack sx={{ gap: 2 }}>
{items.map((item) => (
<BookingRow key={item.id} item={item} />
))}
</Stack>
)}
</Box>
);
}
function BookingRow({ item }: { item: BookingListItemDto }) {
const t = useTranslations('booking');
const locale = useLocale();
const router = useRouter();
return (
<Paper elevation={0} sx={{ p: 2.5, border: '1px solid', borderColor: 'divider', borderRadius: 2 }}>
<Stack sx={{ gap: 1.5 }}>
<Stack direction="row" sx={{ justifyContent: 'space-between', alignItems: 'flex-start', gap: 1.5, flexWrap: 'wrap' }}>
<Stack sx={{ gap: 0.25, minWidth: 0 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 700 }}>
{item.counterpartyName}
</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{formatShamsiDate(item.scheduledDate, locale)} · {t('session_count', { count: item.sessionCount })}
</Typography>
</Stack>
<StatusChip status={BOOKING_STATUS_KIND[item.status]} label={t(`bstatus_${item.status}`)} />
</Stack>
<Stack direction="row" sx={{ justifyContent: 'space-between', alignItems: 'center', gap: 1.5, flexWrap: 'wrap' }}>
<Typography variant="body2" sx={{ fontWeight: 700 }}>
{t('list_total')}: <Money amountIrr={item.amountIrr} size="sm" sx={{ fontWeight: 700 }} />
</Typography>
<AppButton
variant="outlined"
color="primary"
endIcon="bookings"
onClick={() => router.push(`/${locale}${ROUTES.BOOKINGS}/${item.id}`)}
>
{t('view_booking')}
</AppButton>
</Stack>
</Stack>
</Paper>
);
}