diff --git a/.husky/pre-commit b/.husky/pre-commit index ce021a51..5f813bab 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,5 +1,4 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" -yarn lint-staged --no-stash -yarn build \ No newline at end of file +yarn lint-staged --no-stash \ No newline at end of file diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 00000000..2f5f0f64 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +yarn build \ No newline at end of file diff --git a/app/(landing)/(components)/ActivitiesSection/ActivitiesSection.tsx b/app/(landing)/(components)/ActivitiesSection/ActivitiesSection.tsx index 15050f48..b2df522b 100644 --- a/app/(landing)/(components)/ActivitiesSection/ActivitiesSection.tsx +++ b/app/(landing)/(components)/ActivitiesSection/ActivitiesSection.tsx @@ -57,11 +57,11 @@ export const ActivitiesSection = async ({ cityId }: ActivitiesSectionProps) => {
{getActivityResponse.rows.map((activity) => { - const activityCover = activity.media.find((item) => item.flag === 'AVATAR')!; + const activityCover = activity.media.find((item) => item.flag === 'AVATAR'); return ( - + {activityCover && } {activity.category.RU} {activity.name} diff --git a/app/app/(main)/activities/(components)/ActivityList/ActivityList.tsx b/app/app/(main)/activities/(components)/ActivityList/ActivityList.tsx index 6e3d6f8e..a31b7bfe 100644 --- a/app/app/(main)/activities/(components)/ActivityList/ActivityList.tsx +++ b/app/app/(main)/activities/(components)/ActivityList/ActivityList.tsx @@ -33,11 +33,11 @@ export const ActivityList = () => { <>
{activities.map((activity) => { - const activityCover = activity.media.find((item) => item.flag === 'AVATAR')!; + const activityCover = activity.media.find((item) => item.flag === 'AVATAR'); return ( - + {activityCover && } {activity.category.RU} {activity.name} diff --git a/app/org/(panel)/(components)/OrgBreadcrumbs/OrgBreadcrumbs.tsx b/app/org/(panel)/(components)/OrgBreadcrumbs/OrgBreadcrumbs.tsx index ddbfeb45..e1f58300 100644 --- a/app/org/(panel)/(components)/OrgBreadcrumbs/OrgBreadcrumbs.tsx +++ b/app/org/(panel)/(components)/OrgBreadcrumbs/OrgBreadcrumbs.tsx @@ -1,5 +1,6 @@ 'use client'; +import React from 'react'; import { useParams, usePathname } from 'next/navigation'; import { I18nText } from '@/components/common'; @@ -63,13 +64,13 @@ export const OrgBreadcrumbs = ({ ids = {}, ...props }: OrgBreadcrumbsProps) => { ); return ( - <> - + + {clickable && {item}} {!clickable && {item}} {index !== filteredPathnames.length - 1 && } - + ); })} diff --git a/app/org/(panel)/activities/dashboard/(components)/ActivitiesDashboard/ActivitiesDashboard.tsx b/app/org/(panel)/activities/dashboard/(components)/ActivitiesDashboard/ActivitiesDashboard.tsx index 29b1660d..a5320d9f 100644 --- a/app/org/(panel)/activities/dashboard/(components)/ActivitiesDashboard/ActivitiesDashboard.tsx +++ b/app/org/(panel)/activities/dashboard/(components)/ActivitiesDashboard/ActivitiesDashboard.tsx @@ -20,6 +20,7 @@ import { getActivitiesDashboard } from '@/utils/api'; export const ActivitiesDashboard = async () => { const dashboard = await getActivitiesDashboard(); + return (
diff --git a/app/org/(panel)/activities/dashboard/page.tsx b/app/org/(panel)/activities/dashboard/page.tsx index 8ed74b0a..36d9a34c 100644 --- a/app/org/(panel)/activities/dashboard/page.tsx +++ b/app/org/(panel)/activities/dashboard/page.tsx @@ -9,8 +9,8 @@ export interface ActivitiesDashboardPageProps { searchParams: SearchParams; } -const DEFAULT_ACTIVITIES_LIMIT = '10'; -const DEFAULT_ACTIVITIES_PAGE = '1'; +const DEFAULT_ACTIVITIES_LIMIT = 10; +const DEFAULT_ACTIVITIES_PAGE = 1; const ActivitiesDashboardPage = async ({ searchParams }: ActivitiesDashboardPageProps) => { const activitiesTableResponse = await getActivities({ diff --git a/app/org/(panel)/organizations/[organizationId]/(components)/OrganizationHeader/OrganizationHeader.tsx b/app/org/(panel)/organizations/[organizationId]/(components)/OrganizationHeader/OrganizationHeader.tsx index d2abf810..a8639567 100644 --- a/app/org/(panel)/organizations/[organizationId]/(components)/OrganizationHeader/OrganizationHeader.tsx +++ b/app/org/(panel)/organizations/[organizationId]/(components)/OrganizationHeader/OrganizationHeader.tsx @@ -19,7 +19,8 @@ export const OrganizationHeader = ({ organization }: OrganizationHeaderProps) => priority={false} className='h-[168px] w-full rounded-lg' src={organization.background || background} - alt='org-background' + alt={`${organization.name ?? ''} background`} + fill />
@@ -29,7 +30,7 @@ export const OrganizationHeader = ({ organization }: OrganizationHeaderProps) => width={80} height={80} src={organization.avatar || avatar} - alt='org-background' + alt={`${organization.name ?? ''} avatar`} />
diff --git a/app/org/(panel)/organizations/[organizationId]/(constants)/navigation.tsx b/app/org/(panel)/organizations/[organizationId]/(constants)/navigation.tsx index 0534035e..3b687673 100644 --- a/app/org/(panel)/organizations/[organizationId]/(constants)/navigation.tsx +++ b/app/org/(panel)/organizations/[organizationId]/(constants)/navigation.tsx @@ -1,5 +1,5 @@ import type { LucideIcon } from 'lucide-react'; -import { ActivityIcon, MapPinIcon, UserIcon, UsersRoundIcon } from 'lucide-react'; +import { ActivityIcon, CalendarIcon, MapPinIcon, UserIcon, UsersRoundIcon } from 'lucide-react'; import { ROUTES } from '@/utils/constants'; @@ -66,7 +66,7 @@ export const PARTNER_PROFILE_TABS: ProfileTab[] = [ route: ROUTES.ORG.ORGANIZATIONS.ACTIVITIES }, { - icon: ActivityIcon, + icon: CalendarIcon, title: 'organization.profile.header.schedule', value: ORGANIZATION_PROFILE_TAB_VALUES.SCHEDULE, route: ROUTES.ORG.ORGANIZATIONS.SCHEDULE diff --git a/app/org/(panel)/organizations/[organizationId]/activities/page.tsx b/app/org/(panel)/organizations/[organizationId]/activities/page.tsx index 4d18e3d7..f88e59d9 100644 --- a/app/org/(panel)/organizations/[organizationId]/activities/page.tsx +++ b/app/org/(panel)/organizations/[organizationId]/activities/page.tsx @@ -26,8 +26,6 @@ const OrganizationActivitiesPage = async ({ params }: OrganizationActivitiesPage } }); - console.log('@', getActivityResponse); - return ( <>
diff --git a/app/org/(panel)/organizations/[organizationId]/addresses/(components)/ActionAddressForm/ActionAddressForm.tsx b/app/org/(panel)/organizations/[organizationId]/addresses/(components)/ActionAddressForm/ActionAddressForm.tsx index bd5c5747..ff4b5e66 100644 --- a/app/org/(panel)/organizations/[organizationId]/addresses/(components)/ActionAddressForm/ActionAddressForm.tsx +++ b/app/org/(panel)/organizations/[organizationId]/addresses/(components)/ActionAddressForm/ActionAddressForm.tsx @@ -178,7 +178,7 @@ export const ActionAddressForm = ({ )} />
- +
{ + const { functions } = useAddScheduleDialog(); + + return ( + + {trigger} + + + + + + + + + + + +
+ +
+
+
+ ); +}; diff --git a/app/org/(panel)/organizations/[organizationId]/schedule/(components)/AddScheduleDialog/components/AddScheduleForm/AddScheduleForm.tsx b/app/org/(panel)/organizations/[organizationId]/schedule/(components)/AddScheduleDialog/components/AddScheduleForm/AddScheduleForm.tsx new file mode 100644 index 00000000..91f2baf0 --- /dev/null +++ b/app/org/(panel)/organizations/[organizationId]/schedule/(components)/AddScheduleDialog/components/AddScheduleForm/AddScheduleForm.tsx @@ -0,0 +1,364 @@ +import { ActivitiesCombobox, AddressCombobox, EmployeesCombobox } from '@/components/comboboxes'; +import { I18nText } from '@/components/common'; +import { + Button, + ClockInput, + DatePicker, + DatePickerWithRange, + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + NumberFormatInput, + Switch, + Typography +} from '@/components/ui'; +import { cn } from '@/lib/utils'; +import { convertLocalitiesToComboboxItems } from '@/utils/helpers/convertLocalitiesToComboboxItems'; + +import { useAddScheduleForm } from './hooks/useAddScheduleForm'; + +interface AddScheduleFormProps { + onAdded: () => void; +} + +export const AddScheduleForm = ({ onAdded }: AddScheduleFormProps) => { + const { state, form, functions } = useAddScheduleForm({ onAdded }); + + return ( +
+ +
+
+ ( + + + + + form.setValue('activityId', newValue ?? '')} + className='w-full' + /> + + {form.formState?.errors?.activityId && ( + + )} + + + )} + /> + ( + + + + + form.setValue('addressId', newValue ?? '')} + convertAddresses={convertLocalitiesToComboboxItems} + /> + + {form.formState?.errors?.addressId && ( + + )} + + + )} + /> + ( + + + + + form.setValue('leadingId', newValue ?? '')} + /> + + {form.formState?.errors?.leadingId && ( + + )} + + + )} + /> + {form.getValues('isRegularActivity') && ( + <> + + + + {Array.from({ length: 7 }, (_, index) => { + const day = index as 0 | 1 | 2 | 3 | 4 | 5 | 6; + const dayOff = form.watch(`workingHours.${day}.dayOff`); + + return ( +
+ +
+ ( + <> + + + {form.formState.errors?.workingHours?.[day]?.time?.from && ( + + )} + + + )} + /> +
+ +
+ ( + <> + + + {form.formState.errors?.workingHours?.[day]?.time?.to && ( + + )} + + + )} + /> +
+
+ ); + })} + + )} +
+
+ ( + + + + + +
+ + {form.getValues('preEntry') && } + {!form.getValues('preEntry') && } +
+
+ + {form.formState?.errors?.preEntry && ( + + )} + +
+ )} + /> + ( + + + + + +
+ + {form.getValues('isRegularActivity') && ( + + )} + {!form.getValues('isRegularActivity') && ( + + )} +
+
+ + {form.formState?.errors?.isRegularActivity && ( + + )} + +
+ )} + /> + {form.getValues('isRegularActivity') && ( + ( + + + + + + form.setValue('dateRange', newValue)} + /> + + + {form.formState?.errors?.dateRange && ( + + )} + + + )} + /> + )} + {form.getValues('preEntry') === true && ( + ( + + + + + + + + + {form.formState?.errors?.numberOfSeats && ( + + )} + + + )} + /> + )} + {!form.getValues('isRegularActivity') && ( +
+ + + +
+ ( + + + + + + {form.formState?.errors?.date && ( + + )} + + + )} + /> + ( + <> + + + {form.formState.errors?.time?.from?.message && ( + + )} + + + )} + /> + + ( + <> + + + {form.formState.errors?.time?.to && ( + + )} + + + )} + /> +
+
+ )} +
+
+ + +
+ + ); +}; diff --git a/app/org/(panel)/organizations/[organizationId]/schedule/(components)/AddScheduleDialog/components/AddScheduleForm/constants/addScheduleSchema.ts b/app/org/(panel)/organizations/[organizationId]/schedule/(components)/AddScheduleDialog/components/AddScheduleForm/constants/addScheduleSchema.ts new file mode 100644 index 00000000..7eb39a84 --- /dev/null +++ b/app/org/(panel)/organizations/[organizationId]/schedule/(components)/AddScheduleDialog/components/AddScheduleForm/constants/addScheduleSchema.ts @@ -0,0 +1,70 @@ +import * as z from 'zod'; + +const timeSchema = z.object({ + from: z.string(), + to: z.string() +}); + +const dateSchema = z.object({ + from: z.date().optional(), + to: z.date().optional() +}); + +const workingHourSchema = z.object({ + time: timeSchema, + dayOff: z.boolean() +}); + +export const addScheduleSchema = z + .object({ + activityId: z.string().min(1, { message: 'validation.required' }), + addressId: z.string().min(1, { message: 'validation.required' }), + leadingId: z.string().min(1, { message: 'validation.required' }), + preEntry: z.boolean().default(false), + isRegularActivity: z.boolean().default(false), + dateRange: dateSchema.optional(), + date: z.date().optional(), + time: timeSchema, + numberOfSeats: z.string().optional(), + workingHours: z.object({ + '0': workingHourSchema, + '1': workingHourSchema, + '2': workingHourSchema, + '3': workingHourSchema, + '4': workingHourSchema, + '5': workingHourSchema, + '6': workingHourSchema + }) + }) + .superRefine((values, ctx) => { + if (values.preEntry && !values.numberOfSeats) { + ctx.addIssue({ + code: 'custom', + message: 'validation.required', + path: ['numberOfSeats'] + }); + } + if (values.isRegularActivity && !values.dateRange) { + ctx.addIssue({ + code: 'custom', + message: 'validation.required', + path: ['dateRange'] + }); + } else if (values.isRegularActivity && !values.dateRange?.to) { + ctx.addIssue({ + code: 'custom', + message: 'validation.dateEnd.required', + path: ['dateRange'] + }); + } + if (!values.isRegularActivity && !values.date) { + ctx.addIssue({ + code: 'custom', + message: 'validation.required', + path: ['date'] + }); + } + return true; + }); + +export type AddScheduleSchema = z.infer; diff --git a/app/org/(panel)/organizations/[organizationId]/schedule/(components)/AddScheduleDialog/components/AddScheduleForm/hooks/useAddScheduleForm.tsx b/app/org/(panel)/organizations/[organizationId]/schedule/(components)/AddScheduleDialog/components/AddScheduleForm/hooks/useAddScheduleForm.tsx new file mode 100644 index 00000000..01be2b4f --- /dev/null +++ b/app/org/(panel)/organizations/[organizationId]/schedule/(components)/AddScheduleDialog/components/AddScheduleForm/hooks/useAddScheduleForm.tsx @@ -0,0 +1,107 @@ +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useParams } from 'next/navigation'; +import { toast } from 'sonner'; + +import type { WeekAndTime } from '@/api-types'; +import type { PostScheduleParams } from '@/utils/api'; +import { usePostScheduleMutation } from '@/utils/api'; +import { useI18n } from '@/utils/contexts'; +import { getWeekDayByIndex } from '@/utils/helpers'; + +import type { AddScheduleSchema } from '../constants/addScheduleSchema'; +import { addScheduleSchema } from '../constants/addScheduleSchema'; + +interface UseAddScheduleFormParams { + onAdded: () => void; +} + +export const useAddScheduleForm = ({ onAdded }: UseAddScheduleFormParams) => { + const i18n = useI18n(); + const params = useParams<{ organizationId: string }>(); + const addScheduleForm = useForm({ + mode: 'onSubmit', + resolver: zodResolver(addScheduleSchema), + defaultValues: { + activityId: '', + addressId: '', + leadingId: '', + preEntry: false, + isRegularActivity: false, + numberOfSeats: '', + time: { from: '09:00', to: '18:00' }, + workingHours: { + '0': { time: { from: '09:00', to: '18:00' }, dayOff: false }, + '1': { time: { from: '09:00', to: '18:00' }, dayOff: false }, + '2': { time: { from: '09:00', to: '18:00' }, dayOff: false }, + '3': { time: { from: '09:00', to: '18:00' }, dayOff: false }, + '4': { time: { from: '09:00', to: '18:00' }, dayOff: false }, + '5': { time: { from: '09:00', to: '18:00' }, dayOff: false }, + '6': { time: { from: '09:00', to: '18:00' }, dayOff: true } + } + } + }); + + const postScheduleMutation = usePostScheduleMutation(); + + const onSubmit = addScheduleForm.handleSubmit(async (values) => { + let weekAndTime: WeekAndTime[] | undefined; + let dateStart = ''; + let dateEnd: string | undefined; + let numberOfSeats: number | undefined; + if (values.isRegularActivity && values.dateRange?.from) { + weekAndTime = Object.entries(values.workingHours) + .filter(([, element]) => !element.dayOff) + .map(([day, element]): WeekAndTime => { + const [from1, from2] = element.time.from.split(':'); + const [to1, to2] = element.time.to.split(':'); + + const fromHour = Number(from1); + const fromMinutes = Number(from2); + + const toHour = Number(to1); + const toMinutes = Number(to2); + + return { + weekDay: getWeekDayByIndex(+day), + hourStart: fromHour, + minStart: fromMinutes, + hourEnd: toHour, + minEnd: toMinutes + }; + }); + dateStart = values.dateRange?.from?.toISOString(); + dateEnd = values.dateRange?.to?.toISOString(); + } else if (!values.isRegularActivity && values.date) { + dateStart = values.date.toISOString(); + } + if (values.preEntry && values.numberOfSeats) { + numberOfSeats = +values.numberOfSeats; + } + + const postScheduleMutationParams: PostScheduleParams = { + ...values, + weekAndTime, + dateStart, + dateEnd, + numberOfSeats + }; + await postScheduleMutation.mutateAsync({ + params: postScheduleMutationParams + }); + toast(i18n.formatMessage({ id: 'dialog.addAddress.success' }), { + cancel: { label: 'Close' } + }); + + onAdded(); + }); + + return { + state: { + isLoading: postScheduleMutation.isPending, + organizationId: params.organizationId + }, + form: addScheduleForm, + functions: { onSubmit } + }; +}; diff --git a/app/org/(panel)/organizations/[organizationId]/schedule/(components)/AddScheduleDialog/hooks/useAddScheduleDialog.ts b/app/org/(panel)/organizations/[organizationId]/schedule/(components)/AddScheduleDialog/hooks/useAddScheduleDialog.ts new file mode 100644 index 00000000..3bc41e61 --- /dev/null +++ b/app/org/(panel)/organizations/[organizationId]/schedule/(components)/AddScheduleDialog/hooks/useAddScheduleDialog.ts @@ -0,0 +1,9 @@ +import React from 'react'; + +export const useAddScheduleDialog = () => { + const [open, setOpen] = React.useState(false); + + const onAdded = () => setOpen(false); + + return { state: { open }, functions: { setOpen, onAdded } }; +}; diff --git a/app/org/(panel)/organizations/[organizationId]/schedule/(components)/OrganizationSchedulesTable/OrganizationSchedulesTable.tsx b/app/org/(panel)/organizations/[organizationId]/schedule/(components)/OrganizationSchedulesTable/OrganizationSchedulesTable.tsx new file mode 100644 index 00000000..aaaf80da --- /dev/null +++ b/app/org/(panel)/organizations/[organizationId]/schedule/(components)/OrganizationSchedulesTable/OrganizationSchedulesTable.tsx @@ -0,0 +1,78 @@ +'use client'; + +import React from 'react'; + +import type { PaginationResponse, ScheduleResponse } from '@/api-types'; +import { I18nText } from '@/components/common'; +import { + DataTable, + DataTableBody, + DataTableBottomContent, + DataTableComponent, + DataTableCurrentPageLabel, + DataTableHeader, + DataTablePagination, + DataTableSelectedLabel, + DataTableToolbar, + getPageCount, + getPageIndex, + Typography, + useDataTable +} from '@/components/ui'; + +import { columns } from './constants/columns'; +import { convertSchedulesToTableRows } from './helpers/convertSchedulesToTableRows'; +import { useOrganizationSchedulesTable } from './hooks/useOrganizationSchedulesTable'; + +interface OrganizationScheduleTableProps { + schedules: ScheduleResponse[]; + pagination: PaginationResponse; +} + +export const OrganizationSchedulesTable = ({ + schedules, + pagination +}: OrganizationScheduleTableProps) => { + const rows = React.useMemo(() => convertSchedulesToTableRows(schedules), [schedules]); + const table = useDataTable(rows, columns); + const { state, functions } = useOrganizationSchedulesTable(); + + return ( + + + + + + + + + {(count, selectedCount) => ( + <> + {selectedCount} {count}{' '} + + + )} + +
+ + {(count, current, limit) => ( + + {getPageIndex(current)} {' '} + {getPageIndex(getPageCount(limit, count))} + + )} + + + +
+
+
+ ); +}; diff --git a/app/org/(panel)/organizations/[organizationId]/schedule/(components)/OrganizationSchedulesTable/constants/columns.tsx b/app/org/(panel)/organizations/[organizationId]/schedule/(components)/OrganizationSchedulesTable/constants/columns.tsx new file mode 100644 index 00000000..f0d7de8b --- /dev/null +++ b/app/org/(panel)/organizations/[organizationId]/schedule/(components)/OrganizationSchedulesTable/constants/columns.tsx @@ -0,0 +1,69 @@ +import type { ColumnDef } from '@tanstack/react-table'; + +import { Checkbox, generateDataTableColumn } from '@/components/ui'; + +import type { SchedulesTableRow } from '../helpers/convertSchedulesToTableRows'; + +export const columns: ColumnDef[] = [ + { + id: 'select', + header: ({ table }) => ( +
+ table.toggleAllPageRowsSelected(!!value)} + aria-label='Select all' + /> +
+ ), + cell: ({ row }) => ( +
+ row.toggleSelected(!!value)} + aria-label='Select row' + /> +
+ ), + enableSorting: false, + enableHiding: false + }, + generateDataTableColumn({ + accessorKey: 'activity', + headerLabel: 'table.column.schedule.activity', + sortable: true + }), + generateDataTableColumn({ + accessorKey: 'address', + headerLabel: 'table.column.schedule.address', + sortable: true + }), + generateDataTableColumn({ + accessorKey: 'leading', + headerLabel: 'table.column.schedule.leading', + sortable: true + }), + generateDataTableColumn({ + accessorKey: 'date', + headerLabel: 'table.column.schedule.date', + centered: true + }), + generateDataTableColumn({ + accessorKey: 'time', + headerLabel: 'table.column.schedule.time', + centered: true + }), + generateDataTableColumn({ + accessorKey: 'numberOfSeats', + headerLabel: 'table.column.schedule.numberOfSeats', + centered: true + }), + generateDataTableColumn({ + accessorKey: 'isDone', + headerLabel: 'table.column.schedule.isDone', + centered: true + }) +]; diff --git a/app/org/(panel)/organizations/[organizationId]/schedule/(components)/OrganizationSchedulesTable/helpers/convertSchedulesToTableRows.tsx b/app/org/(panel)/organizations/[organizationId]/schedule/(components)/OrganizationSchedulesTable/helpers/convertSchedulesToTableRows.tsx new file mode 100644 index 00000000..f5dda593 --- /dev/null +++ b/app/org/(panel)/organizations/[organizationId]/schedule/(components)/OrganizationSchedulesTable/helpers/convertSchedulesToTableRows.tsx @@ -0,0 +1,38 @@ +import * as fns from 'date-fns'; +import { CheckIcon, XIcon } from 'lucide-react'; + +import type { ScheduleResponse } from '@/api-types'; +import { addLeadingZero } from '@/utils/helpers'; + +export interface SchedulesTableRow { + id: string; + activity: string; + address: string; + leading: string; + date: string; + time: string; + numberOfSeats: number; + isDone: string | JSX.Element; +} + +export const convertSchedulesToTableRows = (schedules: ScheduleResponse[]): SchedulesTableRow[] => + schedules.map((schedule) => { + const startTime = schedule.weekAndTime + ? `${addLeadingZero(schedule.weekAndTime.hourStart)}:${addLeadingZero(schedule.weekAndTime.minStart)}` + : ''; + const endTime = + schedule.weekAndTime?.hourEnd !== undefined && schedule.weekAndTime?.minEnd !== undefined + ? `/${addLeadingZero(schedule.weekAndTime.hourEnd)}:${addLeadingZero(schedule.weekAndTime.minEnd)}` + : ''; + + return { + id: schedule.id, + activity: schedule.activity?.name ?? '-', + address: schedule.address?.value ?? '-', + leading: `${schedule.leading?.surname ?? ''} ${schedule.leading?.name ?? ''}` ?? '-', + date: schedule.date ? fns.format(new Date(schedule.date), 'dd.MM.yy') : '-', + numberOfSeats: schedule.numberOfSeats ?? 0, + isDone: schedule.isDone ? : , + time: `${startTime}${endTime}` + }; + }); diff --git a/app/org/(panel)/organizations/[organizationId]/schedule/(components)/OrganizationSchedulesTable/hooks/useOrganizationSchedulesTable.tsx b/app/org/(panel)/organizations/[organizationId]/schedule/(components)/OrganizationSchedulesTable/hooks/useOrganizationSchedulesTable.tsx new file mode 100644 index 00000000..3fd9b259 --- /dev/null +++ b/app/org/(panel)/organizations/[organizationId]/schedule/(components)/OrganizationSchedulesTable/hooks/useOrganizationSchedulesTable.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import type { DateRange } from 'react-day-picker'; +import * as fns from 'date-fns'; +import { useDebounceCallback } from 'usehooks-ts'; + +import { DatePickerWithRange, Input } from '@/components/ui'; +import { useI18n } from '@/utils/contexts'; +import { useSearchParams } from '@/utils/hooks'; + +const ACTIVITY_INPUT_DELAY = 500; +const DATE_RANGE_INPUT_DELAY = 100; + +export const useOrganizationSchedulesTable = () => { + const i18n = useI18n(); + const { searchParams, setSearchParams, setSearchParam } = useSearchParams(); + const [isPending, startTransition] = React.useTransition(); + + const activityFilter = searchParams.get('activity'); + const dateStartFilter = searchParams.get('dateStart'); + const dateEndFilter = searchParams.get('dateEnd'); + + const dateRange = { + from: dateStartFilter ? new Date(dateStartFilter) : undefined, + to: dateEndFilter ? new Date(dateEndFilter) : undefined + }; + + const onPaginationClick = (page: number) => + startTransition(() => setSearchParam('current', String(page))); + + const onActivityFilterChange = useDebounceCallback( + (value: string) => + startTransition(() => + setSearchParams([ + { key: 'activity', value }, + { key: 'current', value: '1' } + ]) + ), + ACTIVITY_INPUT_DELAY + ); + + const onDateRangeChange = useDebounceCallback((dateRange?: DateRange) => { + if (dateRange) { + setSearchParams([ + { + key: 'dateStart', + value: dateRange.from ? fns.format(dateRange.from, 'yyyy-MM-dd') : '' + }, + { + key: 'dateEnd', + value: dateRange.to ? fns.format(dateRange.to, 'yyyy-MM-dd') : '' + }, + { key: 'current', value: '1' } + ]); + } + }, DATE_RANGE_INPUT_DELAY); + + const toolbar = React.useCallback( + () => [ + onActivityFilterChange(event.target.value)} + className='max-w-[180px]' + />, + + ], + [onActivityFilterChange, activityFilter, onDateRangeChange, dateRange] + ); + + return { + state: { toolbar, isLoading: isPending }, + functions: { onPaginationClick } + }; +}; diff --git a/app/org/(panel)/organizations/[organizationId]/schedule/page.tsx b/app/org/(panel)/organizations/[organizationId]/schedule/page.tsx index 18b2e40a..6296b5bf 100644 --- a/app/org/(panel)/organizations/[organizationId]/schedule/page.tsx +++ b/app/org/(panel)/organizations/[organizationId]/schedule/page.tsx @@ -1,3 +1,54 @@ -const OrganizationSchedulePage = () =>
Schedule page
; +import { PlusCircledIcon } from '@radix-ui/react-icons'; + +import { I18nText } from '@/components/common'; +import { Button, Typography } from '@/components/ui'; +import { getSchedules } from '@/utils/api/requests'; + +import { AddScheduleDialog } from './(components)/AddScheduleDialog/AddScheduleDialog'; +import { OrganizationSchedulesTable } from './(components)/OrganizationSchedulesTable/OrganizationSchedulesTable'; + +export interface OrganizationsPageProps { + searchParams: SearchParams; + params: { organizationId: string }; +} + +const DEFAULT_SCHEDULES_LIMIT = 10; +const DEFAULT_SCHEDULES_PAGE = 1; + +const OrganizationSchedulePage = async ({ searchParams, params }: OrganizationsPageProps) => { + const getSchedulesResponse = await getSchedules({ + params: { + organizationId: params.organizationId, + limit: DEFAULT_SCHEDULES_LIMIT, + current: DEFAULT_SCHEDULES_PAGE, + ...searchParams + }, + config: { + cache: 'no-store' + } + }); + + return ( +
+
+ + + + + + + + } + /> +
+ +
+ ); +}; export default OrganizationSchedulePage; diff --git a/app/partner/[organizationId]/(components)/OrganizationHeader/OrganizationHeader.tsx b/app/partner/[organizationId]/(components)/OrganizationHeader/OrganizationHeader.tsx index 043769f0..9f357a94 100644 --- a/app/partner/[organizationId]/(components)/OrganizationHeader/OrganizationHeader.tsx +++ b/app/partner/[organizationId]/(components)/OrganizationHeader/OrganizationHeader.tsx @@ -40,7 +40,7 @@ export const OrganizationHeader = ({ organization }: OrganizationHeaderProps) => width={60} height={60} src={organization.avatar || avatar} - alt='org-background' + alt={organization.name ?? ''} />
diff --git a/generated/api/activity.ts b/generated/api/activity.ts new file mode 100644 index 00000000..78a393bb --- /dev/null +++ b/generated/api/activity.ts @@ -0,0 +1,20 @@ +/** + * Generated by orval v6.27.1 🍺 + * Do not edit manually. + * BQ + * ## API BQ + * OpenAPI spec version: 1.0 + */ +import type { Translations } from './translations'; +import type { MediaDto } from './mediaDto'; + +export interface Activity { + /** Категория активности */ + category: Translations; + /** ID активности */ + id: string; + /** Категория активности */ + media: MediaDto[]; + /** Название активности */ + name: string; +} diff --git a/generated/api/activityControllerGetPublicActivitysParams.ts b/generated/api/activityControllerGetPublicActivitysParams.ts index 21c9146b..cda1114e 100644 --- a/generated/api/activityControllerGetPublicActivitysParams.ts +++ b/generated/api/activityControllerGetPublicActivitysParams.ts @@ -30,4 +30,8 @@ export type ActivityControllerGetPublicActivitysParams = { * Статус */ status?: ActivityControllerGetPublicActivitysStatus; + /** + * Идентификатор организации + */ + organizationId?: string; }; diff --git a/generated/api/activityResponse.ts b/generated/api/activityResponse.ts index c76466b2..29d00cb3 100644 --- a/generated/api/activityResponse.ts +++ b/generated/api/activityResponse.ts @@ -29,6 +29,10 @@ export interface ActivityResponse { name: string; /** Кол-во орешков */ nutsCount: number; + /** Идентификатор организации */ + organizationId: string; + /** Название организации */ + organizationName: string; /** Стоимость */ price: number; /** Повторное прохождение */ diff --git a/generated/api/address.ts b/generated/api/address.ts new file mode 100644 index 00000000..5377a8c3 --- /dev/null +++ b/generated/api/address.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v6.27.1 🍺 + * Do not edit manually. + * BQ + * ## API BQ + * OpenAPI spec version: 1.0 + */ + +export interface Address { + /** ID адреса */ + id: string; + /** Строка адреса */ + value: string; +} diff --git a/generated/api/createActivityDto.ts b/generated/api/createActivityDto.ts index 71b1b687..9f9fee08 100644 --- a/generated/api/createActivityDto.ts +++ b/generated/api/createActivityDto.ts @@ -20,6 +20,8 @@ export interface CreateActivityDto { media?: MediaDto[]; /** Название */ name: string; + /** ID организации */ + organizationId: string; /** Стоимость */ price: number; /** Повторное прохождение */ diff --git a/generated/api/createAddressDto.ts b/generated/api/createAddressDto.ts index 19c188d6..6f3695e0 100644 --- a/generated/api/createAddressDto.ts +++ b/generated/api/createAddressDto.ts @@ -5,19 +5,26 @@ * ## API BQ * OpenAPI spec version: 1.0 */ +import type { I } from './i'; import type { WorkingHourDto } from './workingHourDto'; export interface CreateAddressDto { - /** Детали адреса в свободной форме */ - details: string; - /** Номер дома */ - house: string; + city?: string; + cityWithType?: string; + country?: string; + flat?: number; + geoLat?: I; + geoLon?: I; + house?: string; /** Идентификатор партнера */ legalEntityId: string; - /** Населенный пункт */ - locality: string; - /** Улица */ - street: string; + postalCode?: string; + region?: string; + settlement?: string; + settlementWithType?: string; + street?: string; + unrestrictedValue?: string; + value?: string; /** Рабочие часы */ workingHours: WorkingHourDto[]; } diff --git a/generated/api/createScheduleDto.ts b/generated/api/createScheduleDto.ts new file mode 100644 index 00000000..a4800165 --- /dev/null +++ b/generated/api/createScheduleDto.ts @@ -0,0 +1,29 @@ +/** + * Generated by orval v6.27.1 🍺 + * Do not edit manually. + * BQ + * ## API BQ + * OpenAPI spec version: 1.0 + */ +import type { WeekAndTime } from './weekAndTime'; + +export interface CreateScheduleDto { + /** Идентификатор активности */ + activityId: string; + /** Идентификатор адреса */ + addressId: string; + /** Дата окончания */ + dateEnd?: string; + /** Дата начала */ + dateStart: string; + /** Регулярность активности */ + isRegularActivity: boolean; + /** Идентификатор ведущего */ + leadingId: string; + /** Количество мест */ + numberOfSeats?: number; + /** Предварительная запись */ + preEntry: boolean; + /** День недели и время */ + weekAndTime?: WeekAndTime[]; +} diff --git a/generated/api/employeeControllerGetEmployeesParams.ts b/generated/api/employeeControllerGetEmployeesParams.ts new file mode 100644 index 00000000..d1e2237f --- /dev/null +++ b/generated/api/employeeControllerGetEmployeesParams.ts @@ -0,0 +1,25 @@ +/** + * Generated by orval v6.29.1 🍺 + * Do not edit manually. + * Big Quest + * ## API Big Quest + * OpenAPI spec version: 1.0 + */ +import type { EmployeePosition } from './employeePosition'; + +export type EmployeeControllerGetEmployeesParams = { + current?: number; + limit?: number; + /** + * Идентификатор организации + */ + organizationId: string; + /** + * Роль сотрудника + */ + position?: EmployeePosition; + /** + * ФИО пользователя или его часть + */ + search?: string; +}; diff --git a/generated/api/employeeListResponse.ts b/generated/api/employeeListResponse.ts new file mode 100644 index 00000000..91e2f165 --- /dev/null +++ b/generated/api/employeeListResponse.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v6.29.1 🍺 + * Do not edit manually. + * Big Quest + * ## API Big Quest + * OpenAPI spec version: 1.0 + */ +import type { EmployeeResponse } from './employeeResponse'; +import type { Pagination } from './pagination'; + +export interface EmployeeListResponse { + /** Пагинация */ + pagination: Pagination; + /** Список */ + rows: EmployeeResponse[]; +} diff --git a/generated/api/employeePosition.ts b/generated/api/employeePosition.ts new file mode 100644 index 00000000..3a26ff70 --- /dev/null +++ b/generated/api/employeePosition.ts @@ -0,0 +1,18 @@ +/** + * Generated by orval v6.29.1 🍺 + * Do not edit manually. + * Big Quest + * ## API Big Quest + * OpenAPI spec version: 1.0 + */ + +/** + * Роль сотрудника + */ +export type EmployeePosition = (typeof EmployeePosition)[keyof typeof EmployeePosition]; + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const EmployeePosition = { + ADMINISTRATOR: 'ADMINISTRATOR', + LEADING: 'LEADING' +} as const; diff --git a/generated/api/employeeResponse.ts b/generated/api/employeeResponse.ts new file mode 100644 index 00000000..cc9e2973 --- /dev/null +++ b/generated/api/employeeResponse.ts @@ -0,0 +1,30 @@ +/** + * Generated by orval v6.29.1 🍺 + * Do not edit manually. + * Big Quest + * ## API Big Quest + * OpenAPI spec version: 1.0 + */ +import type { EmployeePosition } from './employeePosition'; + +export interface EmployeeResponse { + /** Фото аватара */ + avatar?: string; + /** Email сотрудника */ + email: string; + /** Имя */ + firstName: string; + /** Идентификатор сотрудника */ + id: string; + /** Фамилия */ + lastName: string; + /** Отчество */ + middleName?: string; + /** Идентификатор организации */ + organizationId: string; + /** Тел номер */ + phone: string; + position: EmployeePosition; + /** Идентификатор пользователя */ + userId: string; +} diff --git a/generated/api/i.ts b/generated/api/i.ts new file mode 100644 index 00000000..28bebdfe --- /dev/null +++ b/generated/api/i.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v6.27.1 🍺 + * Do not edit manually. + * BQ + * ## API BQ + * OpenAPI spec version: 1.0 + */ + +export interface I { + [key: string]: any; +} diff --git a/generated/api/index.ts b/generated/api/index.ts index 6bdfe876..5f6cef12 100644 --- a/generated/api/index.ts +++ b/generated/api/index.ts @@ -6,6 +6,7 @@ * OpenAPI spec version: 1.0 */ +export * from './activity'; export * from './activityControllerGetPublicActivitysParams'; export * from './activityControllerGetPublicActivitysSortBy'; export * from './activityControllerGetPublicActivitysStatus'; @@ -18,6 +19,7 @@ export * from './activityView'; export * from './activityWithPaginationResponse'; export * from './addUserDto'; export * from './addUserDtoRole'; +export * from './address'; export * from './addressControllerGetAddressParams'; export * from './addressResponse'; export * from './addressResponseDto'; @@ -46,11 +48,14 @@ export * from './createChangesDtoNew'; export * from './createChangesDtoOld'; export * from './createConfigDto'; export * from './createConfigDtoData'; +export * from './createScheduleDto'; export * from './dashBoardResponse'; export * from './dateAndTime'; export * from './datePeriodWithTimes'; export * from './deleteOrganizationsDto'; export * from './generateNewCodeResponse'; +export * from './i'; +export * from './leading'; export * from './legalInformation'; export * from './legals'; export * from './locality'; @@ -66,6 +71,10 @@ export * from './mediaResponseType'; export * from './organizationControllerFindOrganizationsParams'; export * from './organizationControllerFindOrganizationsStage'; export * from './organizationControllerFindOrganizationsType'; +export * from './employeeControllerGetEmployeesParams'; +export * from './employeeListResponse'; +export * from './employeePosition'; +export * from './employeeResponse'; export * from './organizationInformationDto'; export * from './organizationListPaginationResponse'; export * from './organizationListResponse'; @@ -91,8 +100,12 @@ export * from './requisitesDto'; export * from './scheduleActivityDto'; export * from './scheduleAddress'; export * from './scheduleControllerGetScheduleByOrganizationParams'; +export * from './scheduleControllerGetSchedulesSortBy'; +export * from './scheduleListResponse'; export * from './scheduleResponse'; +export * from './scheduleSortByEnum'; export * from './schedulesResponse'; +export * from './scheduleControllerGetSchedulesParams'; export * from './sortDirectionEnum'; export * from './tariffResponse'; export * from './tariffResponseStatus'; @@ -116,5 +129,8 @@ export * from './userResponseRolesItem'; export * from './userResponseSex'; export * from './usersControllerGetUsersParams'; export * from './usersResponse'; +export * from './weekAndTime'; +export * from './weekAndTimeEntityResponse'; +export * from './weekDayEnum'; export * from './workingHourDto'; export * from './workingTimeDto'; diff --git a/generated/api/leading.ts b/generated/api/leading.ts new file mode 100644 index 00000000..9da019eb --- /dev/null +++ b/generated/api/leading.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v6.27.1 🍺 + * Do not edit manually. + * BQ + * ## API BQ + * OpenAPI spec version: 1.0 + */ + +export interface Leading { + /** ID ведущего */ + id: string; + /** Имя ведущего */ + name: string; + /** Фамилия ведущего */ + surname: string; +} diff --git a/generated/api/mainAddressResponse.ts b/generated/api/mainAddressResponse.ts index b5c53bb7..094f92b0 100644 --- a/generated/api/mainAddressResponse.ts +++ b/generated/api/mainAddressResponse.ts @@ -8,16 +8,23 @@ import type { WorkingHourDto } from './workingHourDto'; export interface MainAddressResponse { - /** Детали адреса в свободной форме */ - details: string; - /** Номер дома */ - house: string; - /** Идентификатор адреса */ + city?: string; + cityWithType?: string; + country?: string; + flat?: number; + geoLat?: number; + geoLon?: number; + house?: string; id: string; - /** Населенный пункт */ - locality: string; - /** Улица */ - street: string; + /** Идентификатор партнера */ + legalEntityId: string; + postalCode?: string; + region?: string; + settlement?: string; + settlementWithType?: string; + street?: string; + unrestrictedValue?: string; + value?: string; /** Рабочие часы */ workingHours: WorkingHourDto[]; } diff --git a/generated/api/pagination.ts b/generated/api/pagination.ts index 5a9bf301..73b2387d 100644 --- a/generated/api/pagination.ts +++ b/generated/api/pagination.ts @@ -5,7 +5,6 @@ * ## API BQ * OpenAPI spec version: 1.0 */ -import type { SortDirectionEnum } from './sortDirectionEnum'; export interface Pagination { /** Общее число записей */ @@ -14,7 +13,4 @@ export interface Pagination { current: number; /** Кол-во элементов */ limit: number; - /** Сортировка */ - sortBy?: string; - sortDirection?: SortDirectionEnum; } diff --git a/generated/api/scheduleControllerGetSchedulesParams.ts b/generated/api/scheduleControllerGetSchedulesParams.ts new file mode 100644 index 00000000..e3b4cbb2 --- /dev/null +++ b/generated/api/scheduleControllerGetSchedulesParams.ts @@ -0,0 +1,33 @@ +/** + * Generated by orval v6.29.1 🍺 + * Do not edit manually. + * Big Quest + * ## API Big Quest + * OpenAPI spec version: 1.0 + */ +import type { ScheduleSortByEnum } from './scheduleSortByEnum'; + +export type ScheduleControllerGetSchedulesParams = { + current?: number; + limit?: number; + /** + * Дата С + */ + dateStart?: string; + /** + * Дата По + */ + dateEnd?: string; + /** + * Сортировка по ключам. Для DESC сортировки в начале добавляется "-" + */ + sort?: ScheduleSortByEnum; + /** + * ID активности + */ + activityId?: string; + /** + * ID организации + */ + organizationId?: string; +}; diff --git a/generated/api/scheduleControllerGetSchedulesSortBy.ts b/generated/api/scheduleControllerGetSchedulesSortBy.ts new file mode 100644 index 00000000..f7559c96 --- /dev/null +++ b/generated/api/scheduleControllerGetSchedulesSortBy.ts @@ -0,0 +1,17 @@ +/** + * Generated by orval v6.27.1 🍺 + * Do not edit manually. + * BQ + * ## API BQ + * OpenAPI spec version: 1.0 + */ + +export type ScheduleControllerGetSchedulesSortBy = + (typeof ScheduleControllerGetSchedulesSortBy)[keyof typeof ScheduleControllerGetSchedulesSortBy]; + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const ScheduleControllerGetSchedulesSortBy = { + ACTIVITY: 'ACTIVITY', + ADDRESS: 'ADDRESS', + LEADING: 'LEADING' +} as const; diff --git a/generated/api/scheduleListResponse.ts b/generated/api/scheduleListResponse.ts new file mode 100644 index 00000000..4cc328a5 --- /dev/null +++ b/generated/api/scheduleListResponse.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v6.27.1 🍺 + * Do not edit manually. + * BQ + * ## API BQ + * OpenAPI spec version: 1.0 + */ +import type { Pagination } from './pagination'; +import type { ScheduleResponse } from './scheduleResponse'; + +export interface ScheduleListResponse { + /** Пагинация */ + pagination: Pagination; + /** Список */ + rows: ScheduleResponse[]; +} diff --git a/generated/api/scheduleResponse.ts b/generated/api/scheduleResponse.ts index bc8b407c..ba538d1a 100644 --- a/generated/api/scheduleResponse.ts +++ b/generated/api/scheduleResponse.ts @@ -5,30 +5,32 @@ * ## API BQ * OpenAPI spec version: 1.0 */ -import type { ScheduleAddress } from './scheduleAddress'; -import type { DateAndTime } from './dateAndTime'; -import type { DatePeriodWithTimes } from './datePeriodWithTimes'; -import type { Points } from './points'; +import type { Activity } from './activity'; +import type { Address } from './address'; +import type { Leading } from './leading'; +import type { WeekAndTimeEntityResponse } from './weekAndTimeEntityResponse'; export interface ScheduleResponse { - /** Идентификатор активности */ - activityId: string; + /** Активность */ + activity?: Activity; /** Адрес */ - address: ScheduleAddress; - /** Дата и время */ - dateAndTime: DateAndTime; - /** Период дат и время по л=дням недели */ - datePeriodWithTimes: DatePeriodWithTimes; - /** Предварительная запись */ - entry: boolean; - /** Идентификатор */ + address?: Address; + /** Дата расписания */ + date: string; + /** ID расписания */ id: string; - /** Идентификатор ведущего */ - leadingEmployeeId: string; - /** Максимальное кол-во участников */ - maxNumberOfParticipants: number; - /** Координаты */ - points: Points; - /** Регулярность */ - regular: boolean; + /** Проведено */ + isDone: boolean; + /** Признак регулярности */ + isRegularActivity: boolean; + /** Ведущий */ + leading?: Leading; + /** Кол-во записей */ + numberOfSeats?: number; + /** ID организации */ + organizationId?: string; + /** Признак предварительной записи */ + preEntry: boolean; + /** Дни недели и время */ + weekAndTime?: WeekAndTimeEntityResponse; } diff --git a/generated/api/scheduleSortByEnum.ts b/generated/api/scheduleSortByEnum.ts new file mode 100644 index 00000000..c5af0007 --- /dev/null +++ b/generated/api/scheduleSortByEnum.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v6.27.1 🍺 + * Do not edit manually. + * BQ + * ## API BQ + * OpenAPI spec version: 1.0 + */ + +export type ScheduleSortByEnum = (typeof ScheduleSortByEnum)[keyof typeof ScheduleSortByEnum]; + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const ScheduleSortByEnum = { + activity: 'activity', + address: 'address', + leading: 'leading' +} as const; diff --git a/generated/api/updateAddressDto.ts b/generated/api/updateAddressDto.ts index 1fc3ee44..ce68ce41 100644 --- a/generated/api/updateAddressDto.ts +++ b/generated/api/updateAddressDto.ts @@ -5,19 +5,26 @@ * ## API BQ * OpenAPI spec version: 1.0 */ +import type { I } from './i'; import type { WorkingHourDto } from './workingHourDto'; export interface UpdateAddressDto { - /** Детали адреса в свободной форме */ - details?: string; - /** Номер дома */ + city?: string; + cityWithType?: string; + country?: string; + flat?: number; + geoLat?: I; + geoLon?: I; house?: string; - /** Идентификатор адреса */ - id: string; - /** Населенный пункт */ - locality?: string; - /** Улица */ + /** Идентификатор партнера */ + legalEntityId: string; + postalCode?: string; + region?: string; + settlement?: string; + settlementWithType?: string; street?: string; + unrestrictedValue?: string; + value?: string; /** Рабочие часы */ - workingHours?: WorkingHourDto[]; + workingHours: WorkingHourDto[]; } diff --git a/generated/api/userResponse.ts b/generated/api/userResponse.ts index 9cb4bf3a..5074abcf 100644 --- a/generated/api/userResponse.ts +++ b/generated/api/userResponse.ts @@ -10,6 +10,8 @@ import type { UserResponseRolesItem } from './userResponseRolesItem'; import type { UserResponseSex } from './userResponseSex'; export interface UserResponse { + /** Дата рождения */ + age?: number; /** Аватар */ avatar: string; /** Дата создания */ diff --git a/generated/api/weekAndTime.ts b/generated/api/weekAndTime.ts new file mode 100644 index 00000000..590f0a93 --- /dev/null +++ b/generated/api/weekAndTime.ts @@ -0,0 +1,20 @@ +/** + * Generated by orval v6.27.1 🍺 + * Do not edit manually. + * BQ + * ## API BQ + * OpenAPI spec version: 1.0 + */ +import type { WeekDayEnum } from './weekDayEnum'; + +export interface WeekAndTime { + /** Время окончания. Часы от 1 до 23 */ + hourEnd?: number; + /** Время начала. Часы от 1 до 23 */ + hourStart: number; + /** Время окончания. Минуты от 0 до 59 */ + minEnd?: number; + /** Время начала. Минуты от 0 до 59 */ + minStart: number; + weekDay: WeekDayEnum; +} diff --git a/generated/api/weekAndTimeEntityResponse.ts b/generated/api/weekAndTimeEntityResponse.ts new file mode 100644 index 00000000..14863047 --- /dev/null +++ b/generated/api/weekAndTimeEntityResponse.ts @@ -0,0 +1,20 @@ +/** + * Generated by orval v6.27.1 🍺 + * Do not edit manually. + * BQ + * ## API BQ + * OpenAPI spec version: 1.0 + */ +import type { WeekDayEnum } from './weekDayEnum'; + +export interface WeekAndTimeEntityResponse { + /** Время окончания (час) */ + hourEnd?: number; + /** Время начала (час) */ + hourStart: number; + /** Время окончания (мин) */ + minEnd?: number; + /** Время начала (мин) */ + minStart: number; + weekDay: WeekDayEnum; +} diff --git a/generated/api/weekDayEnum.ts b/generated/api/weekDayEnum.ts new file mode 100644 index 00000000..a00d9371 --- /dev/null +++ b/generated/api/weekDayEnum.ts @@ -0,0 +1,23 @@ +/** + * Generated by orval v6.27.1 🍺 + * Do not edit manually. + * BQ + * ## API BQ + * OpenAPI spec version: 1.0 + */ + +/** + * День недели + */ +export type WeekDayEnum = (typeof WeekDayEnum)[keyof typeof WeekDayEnum]; + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const WeekDayEnum = { + MONDAY: 'MONDAY', + TUESDAY: 'TUESDAY', + WEDNESDAY: 'WEDNESDAY', + THURSDAY: 'THURSDAY', + FRIDAY: 'FRIDAY', + SATURDAY: 'SATURDAY', + SUNDAY: 'SUNDAY' +} as const; diff --git a/mock/database/activityList.ts b/mock/database/activities.ts similarity index 95% rename from mock/database/activityList.ts rename to mock/database/activities.ts index 3bf18a37..7e1a1219 100644 --- a/mock/database/activityList.ts +++ b/mock/database/activities.ts @@ -2,7 +2,7 @@ import type { ActivityListResponse } from '@/api-types'; import { CATEGORIES } from './categories'; -export const ACTIVITY_LIST: ActivityListResponse[] = [ +export const ACTIVITIES: ActivityListResponse[] = [ { id: '1', media: [ @@ -79,7 +79,7 @@ export const ACTIVITY_LIST: ActivityListResponse[] = [ url: 'http://localhost:31299/api/1.0/static/activity/video-1.mp4' } ], - name: 'Рисуем живопись', + name: 'Играем в футбол', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore ', ageLimit: [7, 13], @@ -127,7 +127,7 @@ export const ACTIVITY_LIST: ActivityListResponse[] = [ url: 'http://localhost:31299/api/1.0/static/activity/video-1.mp4' } ], - name: 'Рисуем живопись', + name: 'Пишем стихи', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore ', ageLimit: [7, 13], @@ -163,7 +163,7 @@ export const ACTIVITY_LIST: ActivityListResponse[] = [ url: 'http://localhost:31299/api/1.0/static/activity/video-1.mp4' } ], - name: 'Рисуем живопись', + name: 'Читаем книги', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore ', @@ -206,7 +206,7 @@ export const ACTIVITY_LIST: ActivityListResponse[] = [ url: 'http://localhost:31299/api/1.0/static/activity/video-1.mp4' } ], - name: 'Рисуем живопись', + name: 'Играем в пейнтбол', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore ', diff --git a/mock/database/employees.ts b/mock/database/employees.ts index 67f5f690..3174029c 100644 --- a/mock/database/employees.ts +++ b/mock/database/employees.ts @@ -1 +1,218 @@ -export const EMPLOYEES = []; +import type { EmployeeResponse } from '@/api-types'; + +export const EMPLOYEES: EmployeeResponse[] = [ + { + avatar: 'http://localhost/31299/api/1.0/static/employees/avatar.webp', + email: 'john.doe@example.com', + firstName: 'John', + id: '123e4567-e89b-12d3-a456-426614174000', + lastName: 'Doe', + middleName: 'Michael', + organizationId: '987e6543-21cb-42d3-b987-321654987000', + phone: '79234567890', + position: 'LEADING', + userId: 'abcdef12-3456-7890-abcd-ef1234567890' + }, + { + email: 'jane.smith@example.com', + firstName: 'Jane', + id: '223e4567-e89b-12d3-a456-426614174001', + lastName: 'Smith', + organizationId: '987e6543-21cb-42d3-b987-321654987000', + phone: '79987654321', + position: 'ADMINISTRATOR', + userId: 'bcdef123-4567-8901-bcde-f12345678901' + }, + { + avatar: 'http://localhost/31299/api/1.0/static/employees/avatar.webp', + email: 'alex.jones@example.com', + firstName: 'Alex', + id: '323e4567-e89b-12d3-a456-426614174002', + lastName: 'Jones', + middleName: 'David', + organizationId: '987e6543-21cb-42d3-b987-321654987001', + phone: '79122334455', + position: 'LEADING', + userId: 'cdef1234-5678-9012-cdef-123456789012' + }, + { + email: 'anna.brown@example.com', + firstName: 'Anna', + id: '423e4567-e89b-12d3-a456-426614174003', + lastName: 'Brown', + organizationId: '987e6543-21cb-42d3-b987-321654987001', + phone: '79988776655', + position: 'ADMINISTRATOR', + userId: 'def12345-6789-0123-def1-234567890123' + }, + { + email: 'michael.white@example.com', + firstName: 'Michael', + id: '523e4567-e89b-12d3-a456-426614174004', + lastName: 'White', + organizationId: '987e6543-21cb-42d3-b987-321654987002', + phone: '79122334455', + position: 'LEADING', + userId: 'ef123456-7890-1234-ef12-345678901234' + }, + { + email: 'emily.johnson@example.com', + firstName: 'Emily', + id: '623e4567-e89b-12d3-a456-426614174005', + lastName: 'Johnson', + organizationId: '987e6543-21cb-42d3-b987-321654987002', + phone: '79344556677', + position: 'ADMINISTRATOR', + userId: 'f1234567-8901-2345-f123-456789012345' + }, + { + avatar: 'http://localhost/31299/api/1.0/static/employees/avatar.webp', + email: 'peter.wilson@example.com', + firstName: 'Peter', + id: '723e4567-e89b-12d3-a456-426614174006', + lastName: 'Wilson', + middleName: 'Andrew', + organizationId: '987e6543-21cb-42d3-b987-321654987003', + phone: '79566778899', + position: 'LEADING', + userId: '01234567-8901-2345-6789-012345678901' + }, + { + email: 'sarah.miller@example.com', + firstName: 'Sarah', + id: '823e4567-e89b-12d3-a456-426614174007', + lastName: 'Miller', + organizationId: '987e6543-21cb-42d3-b987-321654987003', + phone: '79122334455', + position: 'LEADING', + userId: '12345678-9012-3456-7890-123456789012' + }, + { + email: 'david.thompson@example.com', + firstName: 'David', + id: '923e4567-e89b-12d3-a456-426614174008', + lastName: 'Thompson', + organizationId: '987e6543-21cb-42d3-b987-321654987004', + phone: '79344556677', + position: 'LEADING', + userId: '23456789-0123-4567-8901-234567890123' + }, + { + email: 'olivia.davis@example.com', + firstName: 'Olivia', + id: 'a23e4567-e89b-12d3-a456-426614174009', + lastName: 'Davis', + organizationId: '987e6543-21cb-42d3-b987-321654987004', + phone: '79566778899', + position: 'ADMINISTRATOR', + userId: '34567890-1234-5678-9012-345678901234' + }, + { + avatar: 'https://example.com/avatar10.jpg', + email: 'mark.jackson@example.com', + firstName: 'Mark', + id: 'b23e4567-e89b-12d3-a456-426614174010', + lastName: 'Jackson', + middleName: 'Robert', + organizationId: '987e6543-21cb-42d3-b987-321654987005', + phone: '71122334455', + position: 'LEADING', + userId: '45678901-2345-6789-0123-456789012345' + }, + { + email: 'lisa.martinez@example.com', + firstName: 'Lisa', + id: 'c23e4567-e89b-12d3-a456-426614174011', + lastName: 'Martinez', + organizationId: '987e6543-21cb-42d3-b987-321654987005', + phone: '73344556677', + position: 'ADMINISTRATOR', + userId: '56789012-3456-7890-1234-567890123456' + }, + { + avatar: 'https://example.com/avatar12.jpg', + email: 'kevin.robinson@example.com', + firstName: 'Kevin', + id: 'd23e4567-e89b-12d3-a456-426614174012', + lastName: 'Robinson', + middleName: 'Joseph', + organizationId: '987e6543-21cb-42d3-b987-321654987006', + phone: '75566778899', + position: 'LEADING', + userId: '67890123-4567-8901-2345-678901234567' + }, + { + email: 'hannah.garcia@example.com', + firstName: 'Hannah', + id: 'e23e4567-e89b-12d3-a456-426614174013', + lastName: 'Garcia', + organizationId: '987e6543-21cb-42d3-b987-321654987006', + phone: '79988776655', + position: 'ADMINISTRATOR', + userId: '78901234-5678-9012-3456-789012345678' + }, + { + email: 'jason.hernandez@example.com', + firstName: 'Jason', + id: 'f23e4567-e89b-12d3-a456-426614174014', + lastName: 'Hernandez', + organizationId: '987e6543-21cb-42d3-b987-321654987007', + phone: '71122334455', + position: 'LEADING', + userId: '89012345-6789-0123-4567-890123456789' + }, + { + email: 'mia.morris@example.com', + firstName: 'Mia', + id: 'g23e4567-e89b-12d3-a456-426614174015', + lastName: 'Morris', + organizationId: '987e6543-21cb-42d3-b987-321654987007', + phone: '73344556677', + position: 'LEADING', + userId: '90123456-7890-1234-5678-901234567890' + }, + { + avatar: 'https://example.com/avatar16.jpg', + email: 'ryan.cook@example.com', + firstName: 'Ryan', + id: 'h23e4567-e89b-12d3-a456-426614174016', + lastName: 'Cook', + middleName: 'Christopher', + organizationId: '987e6543-21cb-42d3-b987-321654987008', + phone: '75566778899', + position: 'LEADING', + userId: '01234567-8901-2345-6789-012345678901' + }, + { + email: 'natalie.young@example.com', + firstName: 'Natalie', + id: 'i23e4567-e89b-12d3-a456-426614174017', + lastName: 'Young', + organizationId: '987e6543-21cb-42d3-b987-321654987008', + phone: '79988776655', + position: 'ADMINISTRATOR', + userId: '12345678-9012-3456-7890-123456789012' + }, + { + avatar: 'https://example.com/avatar18.jpg', + email: 'chris.wright@example.com', + firstName: 'Chris', + id: 'j23e4567-e89b-12d3-a456-426614174018', + lastName: 'Wright', + middleName: 'Alexander', + organizationId: '987e6543-21cb-42d3-b987-321654987009', + phone: '71122334455', + position: 'LEADING', + userId: '23456789-0123-4567-8901-234567890123' + }, + { + email: 'jennifer.hill@example.com', + firstName: 'Jennifer', + id: 'k23e4567-e89b-12d3-a456-426614174019', + lastName: 'Hill', + organizationId: '987e6543-21cb-42d3-b987-321654987009', + phone: '73344556677', + position: 'ADMINISTRATOR', + userId: '34567890-1234-5678-9012-345678901234' + } +]; diff --git a/mock/database/index.ts b/mock/database/index.ts index b1248489..e948ab96 100644 --- a/mock/database/index.ts +++ b/mock/database/index.ts @@ -1,4 +1,4 @@ -import { ACTIVITY_LIST } from './activityList'; +import { ACTIVITIES } from './activities'; import { ADDRESSES } from './addresses'; import { CATEGORIES } from './categories'; import { EMPLOYEES } from './employees'; @@ -7,7 +7,7 @@ import { USER } from './user'; export const DATABASE = { USER, CATEGORIES, - ACTIVITY_LIST, + ACTIVITIES, ORGANIZATIONS: { EMPLOYEES, ADDRESSES diff --git a/mock/requests/activity/get.ts b/mock/requests/activity/get.ts index f2a1fd50..3d943ea3 100644 --- a/mock/requests/activity/get.ts +++ b/mock/requests/activity/get.ts @@ -7,13 +7,13 @@ export const getActivities: RestRequestConfig = { routes: [ { data: (request) => { - const { name, category } = request.query; + const { query, category } = request.query; - let rows = DATABASE.ACTIVITY_LIST; + let rows = DATABASE.ACTIVITIES; - if (typeof name === 'string') { + if (typeof query === 'string') { rows = rows.filter((activity) => - activity.name.toLowerCase().includes(name.toLowerCase()) + activity.name.toLowerCase().includes(query.toLowerCase()) ); } diff --git a/mock/requests/activity/id/get.ts b/mock/requests/activity/id/get.ts index 93813974..298f918a 100644 --- a/mock/requests/activity/id/get.ts +++ b/mock/requests/activity/id/get.ts @@ -8,7 +8,7 @@ export const getActivityById: RestRequestConfig = { { data: (request) => { const { id } = request.params; - return DATABASE.ACTIVITY_LIST.find((activity) => activity.id === id); + return DATABASE.ACTIVITIES.find((activity) => activity.id === id); } } ] diff --git a/mock/requests/address/get.ts b/mock/requests/address/get.ts index afdb1d08..8643ff0b 100644 --- a/mock/requests/address/get.ts +++ b/mock/requests/address/get.ts @@ -1,7 +1,7 @@ import type { RestRequestConfig } from 'mock-config-server'; export const getAddressConfig: RestRequestConfig = { - path: '/address/:address', + path: '/address', method: 'get', routes: [ { diff --git a/mock/requests/auth/login/email/post.ts b/mock/requests/auth/login/email/post.ts index 1b6bf3a3..3b183e23 100644 --- a/mock/requests/auth/login/email/post.ts +++ b/mock/requests/auth/login/email/post.ts @@ -27,7 +27,7 @@ export const postAuthLoginEmailConfig: RestRequestConfig = { response: (data, { setCookie }) => { setCookie(COOKIES.SESSION_ID, COOKIES.SESSION_ID, { httpOnly: true, - maxAge: 360000, + maxAge: 36000000, path: '/' }); diff --git a/mock/requests/employee/get.ts b/mock/requests/employee/get.ts new file mode 100644 index 00000000..ef6b8b57 --- /dev/null +++ b/mock/requests/employee/get.ts @@ -0,0 +1,37 @@ +import { DATABASE } from 'mock/database'; +import type { RestRequestConfig } from 'mock-config-server'; + +export const getEmployeeConfig: RestRequestConfig = { + path: '/employee', + method: 'get', + routes: [ + { + data: (request) => { + const { search, position } = request.query; + + let rows = DATABASE.ORGANIZATIONS.EMPLOYEES; + + if (typeof search === 'string') { + rows = rows.filter((employee) => + (employee.firstName + employee.middleName + employee.lastName) + .toLowerCase() + .includes(search.toLowerCase()) + ); + } + + if (typeof position === 'string') { + rows = rows.filter((employee) => employee.position === position); + } + + return { + pagination: { + limit: request.query.limit ?? 10, + current: request.query.current ?? 1, + count: rows.length + }, + rows + }; + } + } + ] +}; diff --git a/mock/requests/index.ts b/mock/requests/index.ts index b05b69ed..b3701fc3 100644 --- a/mock/requests/index.ts +++ b/mock/requests/index.ts @@ -10,6 +10,7 @@ export * from './auth/new-code/get'; export * from './category/get'; export * from './changes/get'; export * from './changes/post'; +export * from './employee/get'; export * from './legal-address/id/delete'; export * from './legal-address/id/get'; export * from './legal-address/id/put'; @@ -21,8 +22,10 @@ export * from './organization/id/employees/get'; export * from './organization/id/get'; export * from './organization/id/put'; export * from './organization/register/post'; +export * from './schedules/organizationId/get'; export * from './tariff/id/put'; export * from './tariff/legalEntityId/get'; export * from './user/get'; +export * from './user/me/get'; export * from './user/profile/settings/get'; export * from './user/put'; diff --git a/mock/requests/organization/get.ts b/mock/requests/organization/get.ts index 8fae6e52..16d9a20d 100644 --- a/mock/requests/organization/get.ts +++ b/mock/requests/organization/get.ts @@ -28,7 +28,7 @@ export const getOrganizationConfig: RestRequestConfig = { stage: 'CONCLUSION', type: 'SPONSOR', tariff: 'Бесплатный', - countDays: '♾️', + countDays: 'INFINITY', id: '40d1c089-39eb-465c-8651-c68cbf11b725' }, { @@ -37,7 +37,7 @@ export const getOrganizationConfig: RestRequestConfig = { stage: 'CONCLUSION', type: 'SPONSOR', tariff: 'Бесплатный', - countDays: '♾️', + countDays: 'INFINITY', id: '88aa5df3-5773-43e0-be50-f78f457a5bed' }, { @@ -55,7 +55,7 @@ export const getOrganizationConfig: RestRequestConfig = { stage: 'REQUEST', type: 'FRANCHISEE', tariff: 'Бесплатный', - countDays: '♾️', + countDays: 'INFINITY', id: '92930682-c2a8-4ec8-b7a5-a5641e5abb28' }, { @@ -139,7 +139,7 @@ export const getOrganizationConfig: RestRequestConfig = { stage: 'REQUEST', type: 'FRANCHISEE', tariff: 'Бесплатный', - countDays: '♾️', + countDays: 'INFINITY', id: 'bb02448f-19c4-496f-b274-b8078145f0d1' }, { @@ -148,7 +148,7 @@ export const getOrganizationConfig: RestRequestConfig = { stage: 'CONCLUSION', type: 'SPONSOR', tariff: 'Бесплатный', - countDays: '♾️', + countDays: 'INFINITY', id: '645c2f33-9290-4680-a63c-8725ebffbd02' }, { @@ -241,7 +241,7 @@ export const getOrganizationConfig: RestRequestConfig = { stage: 'CONCLUSION', type: 'SPONSOR', tariff: 'Бесплатный', - countDays: '♾️', + countDays: 'INFINITY', id: 'bd4bdeee-3dbe-48ee-af24-576d66f13382' }, { @@ -277,7 +277,7 @@ export const getOrganizationConfig: RestRequestConfig = { stage: 'CONCLUSION', type: 'PARTNER', tariff: 'Бесплатный', - countDays: '♾️', + countDays: 'INFINITY', id: '964ecdd8-2b33-4a25-a195-1641d36020da' }, { @@ -327,7 +327,7 @@ export const getOrganizationConfig: RestRequestConfig = { stage: 'NEGOTIATION', type: 'PARTNER', tariff: 'Бесплатный', - countDays: '♾️', + countDays: 'INFINITY', id: '16423bc3-18bd-4c5a-965d-804c0d0bf4ea' }, { @@ -336,7 +336,7 @@ export const getOrganizationConfig: RestRequestConfig = { stage: 'NEGOTIATION', type: 'PARTNER', tariff: 'Бесплатный', - countDays: '♾️', + countDays: 'INFINITY', id: 'f69602d4-1caf-4f21-a855-7a722162882e' }, { @@ -440,7 +440,7 @@ export const getOrganizationConfig: RestRequestConfig = { stage: 'CONCLUSION', type: 'SPONSOR', tariff: 'Бесплатный', - countDays: '♾️', + countDays: 'INFINITY', id: '40d1c089-39eb-465c-8651-c68cbf11b725' }, { @@ -449,7 +449,7 @@ export const getOrganizationConfig: RestRequestConfig = { stage: 'CONCLUSION', type: 'SPONSOR', tariff: 'Бесплатный', - countDays: '♾️', + countDays: 'INFINITY', id: '88aa5df3-5773-43e0-be50-f78f457a5bed' }, { @@ -467,7 +467,7 @@ export const getOrganizationConfig: RestRequestConfig = { stage: 'REQUEST', type: 'FRANCHISEE', tariff: 'Бесплатный', - countDays: '♾️', + countDays: 'INFINITY', id: '92930682-c2a8-4ec8-b7a5-a5641e5abb28' }, { diff --git a/mock/requests/schedules/organizationId/get.ts b/mock/requests/schedules/organizationId/get.ts new file mode 100644 index 00000000..9b66be0f --- /dev/null +++ b/mock/requests/schedules/organizationId/get.ts @@ -0,0 +1,456 @@ +import type { RestRequestConfig } from 'mock-config-server'; + +export const getSchedulesByOrganizationId: RestRequestConfig = { + path: '/schedules/:organizationId', + method: 'get', + routes: [ + { + entities: { query: { current: 1, limit: 10 } }, + data: { + pagination: { + limit: 10, + current: 1, + count: 100 + }, + rows: [ + { + id: 'activity_001', + leading: { + id: 'leader_001', + name: 'John', + surname: 'Doe' + }, + isRegularActivity: true, + preEntry: true, + activity: { + id: 'act_001', + name: 'Yoga Class', + category: { + id: 'cat_001', + RU: 'Йога', + EN: 'Yoga' + }, + media: [ + { + id: 'media_001', + type: 'IMAGE', + flag: 'AVATAR', + url: 'https://example.com/yoga.jpg' + } + ] + }, + address: { + id: 'addr_001', + value: '123 Main St, Springfield' + }, + date: '2023-05-19T05:58:03.245Z', + numberOfSeats: 20, + weekAndTime: { + weekDay: 'MONDAY', + hourStart: 18, + minStart: 0 + }, + organizationId: 'org_001', + isDone: false + }, + { + id: 'activity_002', + leading: { + id: 'leader_002', + name: 'Jane', + surname: 'Smith' + }, + isRegularActivity: true, + preEntry: true, + activity: { + id: 'act_002', + name: 'Cooking Class', + category: { + id: 'cat_002', + RU: 'Кулинария', + EN: 'Cooking' + }, + media: [ + { + id: 'media_002', + type: 'IMAGE', + flag: 'AVATAR', + url: 'https://example.com/cooking.jpg' + } + ] + }, + address: { + id: 'addr_002', + value: '456 Oak St, Springfield' + }, + date: '2024-05-29T05:58:03.245Z', + numberOfSeats: 15, + weekAndTime: { + weekDay: 'TUESDAY', + hourStart: 17, + hourEnd: 19, + minStart: 0, + minEnd: 0 + }, + organizationId: 'org_002', + isDone: true + }, + { + id: 'activity_003', + leading: { + id: 'leader_003', + name: 'Michael', + surname: 'Brown' + }, + isRegularActivity: true, + preEntry: true, + activity: { + id: 'act_003', + name: 'Painting Workshop', + category: { + id: 'cat_003', + RU: 'Рисование', + EN: 'Painting' + }, + media: [ + { + id: 'media_003', + type: 'IMAGE', + flag: 'AVATAR', + url: 'https://example.com/painting.jpg' + } + ] + }, + address: { + id: 'addr_003', + value: '789 Birch St, Springfield' + }, + date: '2024-01-19T05:58:03.245Z', + numberOfSeats: 10, + weekAndTime: { + weekDay: 'WEDNESDAY', + hourStart: 16, + hourEnd: 18, + minStart: 30, + minEnd: 30 + }, + organizationId: 'org_003', + isDone: false + }, + { + id: 'activity_004', + leading: { + id: 'leader_004', + name: 'Emily', + surname: 'Davis' + }, + isRegularActivity: true, + preEntry: true, + activity: { + id: 'act_004', + name: 'Music Class', + category: { + id: 'cat_004', + RU: 'Музыка', + EN: 'Music' + }, + media: [ + { + id: 'media_004', + type: 'IMAGE', + flag: 'AVATAR', + url: 'https://example.com/music.jpg' + } + ] + }, + address: { + id: 'addr_004', + value: '101 Pine St, Springfield' + }, + date: '2024-05-09T05:58:03.245Z', + numberOfSeats: 25, + weekAndTime: { + weekDay: 'THURSDAY', + hourStart: 19, + hourEnd: 20, + minStart: 0, + minEnd: 0 + }, + organizationId: 'org_004', + isDone: true + }, + { + id: 'activity_005', + leading: { + id: 'leader_005', + name: 'Robert', + surname: 'Johnson' + }, + isRegularActivity: true, + preEntry: true, + activity: { + id: 'act_005', + name: 'Dance Class', + category: { + id: 'cat_005', + RU: 'Танцы', + EN: 'Dance' + }, + media: [ + { + id: 'media_005', + type: 'IMAGE', + flag: 'AVATAR', + url: 'https://example.com/dance.jpg' + } + ] + }, + address: { + id: 'addr_005', + value: '202 Maple St, Springfield' + }, + date: '2024-05-19T05:58:03.245Z', + numberOfSeats: 20, + weekAndTime: { + weekDay: 'FRIDAY', + hourStart: 18, + hourEnd: 19, + minStart: 0, + minEnd: 0 + }, + organizationId: 'org_005', + isDone: true + }, + { + id: 'activity_006', + leading: { + id: 'leader_006', + name: 'Lisa', + surname: 'Miller' + }, + isRegularActivity: true, + preEntry: true, + activity: { + id: 'act_006', + name: 'Photography Class', + category: { + id: 'cat_006', + RU: 'Фотография', + EN: 'Photography' + }, + media: [ + { + id: 'media_006', + type: 'IMAGE', + flag: 'AVATAR', + url: 'https://example.com/photography.jpg' + } + ] + }, + address: { + id: 'addr_006', + value: '303 Cedar St, Springfield' + }, + date: '2024-05-19T05:58:03.245Z', + numberOfSeats: 12, + weekAndTime: { + weekDay: 'SATURDAY', + hourStart: 14, + hourEnd: 16, + minStart: 0, + minEnd: 0 + }, + organizationId: 'org_006', + isDone: false + }, + { + id: 'activity_007', + leading: { + id: 'leader_007', + name: 'David', + surname: 'Martinez' + }, + isRegularActivity: true, + preEntry: true, + activity: { + id: 'act_007', + name: 'Martial Arts Class', + category: { + id: 'cat_007', + RU: 'Боевые искусства', + EN: 'Martial Arts' + }, + media: [ + { + id: 'media_007', + type: 'IMAGE', + flag: 'AVATAR', + url: 'https://example.com/martialarts.jpg' + } + ] + }, + address: { + id: 'addr_007', + value: '404 Walnut St, Springfield' + }, + date: '2024-05-19T05:58:03.245Z', + numberOfSeats: 30, + weekAndTime: { + weekDay: 'SUNDAY', + hourStart: 10, + hourEnd: 12, + minStart: 0, + minEnd: 0 + }, + organizationId: 'org_007', + isDone: true + }, + { + id: 'activity_008', + leading: { + id: 'leader_008', + name: 'Sarah', + surname: 'Wilson' + }, + isRegularActivity: true, + preEntry: true, + activity: { + id: 'act_008', + name: 'Gardening Club', + category: { + id: 'cat_008', + RU: 'Садоводство', + EN: 'Gardening' + }, + media: [ + { + id: 'media_008', + type: 'IMAGE', + flag: 'AVATAR', + url: 'https://example.com/gardening.jpg' + } + ] + }, + address: { + id: 'addr_008', + value: '505 Elm St, Springfield' + }, + date: '2024-05-19T05:58:03.245Z', + numberOfSeats: 10, + weekAndTime: { + weekDay: 'MONDAY', + hourStart: 9, + hourEnd: 11, + minStart: 0, + minEnd: 0 + }, + organizationId: 'org_008', + isDone: false + }, + { + id: 'activity_009', + leading: { + id: 'leader_009', + name: 'Paul', + surname: 'Garcia' + }, + isRegularActivity: true, + preEntry: true, + activity: { + id: 'act_009', + name: 'Chess Club', + category: { + id: 'cat_009', + RU: 'Шахматы', + EN: 'Chess' + }, + media: [ + { + id: 'media_009', + type: 'IMAGE', + flag: 'AVATAR', + url: 'https://example.com/chess.jpg' + } + ] + }, + address: { + id: 'addr_009', + value: '606 Ash St, Springfield' + }, + date: '2024-05-19T05:58:03.245Z', + numberOfSeats: 8, + weekAndTime: { + weekDay: 'TUESDAY', + hourStart: 18, + hourEnd: 20, + minStart: 0, + minEnd: 0 + }, + organizationId: 'org_009', + isDone: true + }, + { + id: 'activity_010', + leading: { + id: 'leader_010', + name: 'Anna', + surname: 'Clark' + }, + isRegularActivity: true, + preEntry: true, + activity: { + id: 'act_010', + name: 'Book Club', + category: { + id: 'cat_010', + RU: 'Книги', + EN: 'Books' + }, + media: [ + { + id: 'media_010', + type: 'IMAGE', + flag: 'AVATAR', + url: 'https://example.com/bookclub.jpg' + } + ] + }, + address: { + id: 'addr_010', + value: '707 Poplar St, Springfield' + }, + date: '2024-05-19T05:58:03.245Z', + numberOfSeats: 12, + weekAndTime: { + weekDay: 'WEDNESDAY', + hourStart: 17, + hourEnd: 18, + minStart: 0, + minEnd: 0 + }, + organizationId: 'org_010', + isDone: true + } + ] + } + }, + { + entities: { query: { current: 1, limit: 10, activity: 'Рисовашки' } }, + data: { + rows: [ + { + id: 'e576a58e-6e63-4a3c-9777-d57f56153f53', + activity: 'Рисовашки', + address: '9 Northport Drive', + leading: 'Christopher Nason', + date: '2024-05-18T08:28:47.932Z', + time: '12:01 AM', + registrationCount: 7, + passed: true + } + ], + pagination: { current: 1, count: 1, limit: 10 } + } + } + ] +}; diff --git a/orval.config.ts b/orval.config.ts index f189c54c..5adfde04 100644 --- a/orval.config.ts +++ b/orval.config.ts @@ -1,8 +1,8 @@ export default { 'big-quest': { - input: 'http://95.174.93.7/api-doc-json', + input: 'http://192.144.13.154/api-doc-json', output: { - schemas: 'generated/api', + schemas: 'generated-temp/api', prettier: true } } diff --git a/package.json b/package.json index 48c13ea2..640b1513 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "next-qrcode": "^2.5.1", "next-themes": "^0.2.1", "react": "^18.2.0", + "react-day-picker": "^8.10.0", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", "react-hook-form": "^7.50.1", diff --git a/src/components/comboboxes/ActivitiesCombobox/ActivitiesCombobox.tsx b/src/components/comboboxes/ActivitiesCombobox/ActivitiesCombobox.tsx new file mode 100644 index 00000000..1117dbab --- /dev/null +++ b/src/components/comboboxes/ActivitiesCombobox/ActivitiesCombobox.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { useDebounceCallback, useIntersectionObserver } from 'usehooks-ts'; + +import type { ComboboxProps } from '@/components/ui'; +import { Combobox } from '@/components/ui'; +import { useGetActivityInfiniteQuery } from '@/utils/api'; + +import { defaultConvertActivities } from './helpers/defaultConvertActivities'; + +interface ActivitiesComboboxProps + extends Omit { + organizationId: string; +} + +const ACTIVITY_SEARCH_DELAY = 200; + +export const ActivitiesCombobox = ({ organizationId, ...props }: ActivitiesComboboxProps) => { + const [query, setQuery] = React.useState(''); + const debouncedSetQuery = useDebounceCallback(setQuery, ACTIVITY_SEARCH_DELAY); + const getActivityInfiniteQuery = useGetActivityInfiniteQuery({ query, organizationId }); + + const { isIntersecting, ref } = useIntersectionObserver({ + threshold: 0.5 + }); + + React.useEffect(() => { + if (isIntersecting) getActivityInfiniteQuery.fetchNextPage(); + }, [isIntersecting]); + + return ( + <> + page.rows) + ) + : [] + } + onSearchChange={debouncedSetQuery} + loading={getActivityInfiniteQuery.isFetchingNextPage} + /> +
+ + ); +}; diff --git a/src/components/comboboxes/ActivitiesCombobox/helpers/defaultConvertActivities.ts b/src/components/comboboxes/ActivitiesCombobox/helpers/defaultConvertActivities.ts new file mode 100644 index 00000000..e5eda2e7 --- /dev/null +++ b/src/components/comboboxes/ActivitiesCombobox/helpers/defaultConvertActivities.ts @@ -0,0 +1,7 @@ +import type { ActivityListResponse } from '@/api-types'; + +export const defaultConvertActivities = (activities: ActivityListResponse[]) => + activities.map((activity) => ({ + label: activity.name, + value: activity.id + })); diff --git a/src/components/comboboxes/EmployeesCombobox/EmployeesCombobox.tsx b/src/components/comboboxes/EmployeesCombobox/EmployeesCombobox.tsx new file mode 100644 index 00000000..d7bc50bd --- /dev/null +++ b/src/components/comboboxes/EmployeesCombobox/EmployeesCombobox.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { useDebounceCallback, useIntersectionObserver } from 'usehooks-ts'; + +import type { EmployeePosition } from '@/api-types'; +import type { ComboboxProps } from '@/components/ui'; +import { Combobox } from '@/components/ui'; +import { useGetEmployeeInfiniteQuery } from '@/utils/api'; + +import { defaultConvertEmployees } from './helpers/defaultConvertEmployees'; + +interface EmployeesComboboxProps + extends Omit { + organizationId: string; + position?: EmployeePosition; +} + +const EMPLOYEES_SEARCH_DELAY = 200; + +export const EmployeesCombobox = ({ + organizationId, + position, + ...props +}: EmployeesComboboxProps) => { + const [employeeSearch, setEmployeeSearch] = React.useState(''); + const debouncedSetEmployeeSearch = useDebounceCallback(setEmployeeSearch, EMPLOYEES_SEARCH_DELAY); + + const getEmployeeInfiniteQuery = useGetEmployeeInfiniteQuery({ + position, + organizationId, + search: employeeSearch + }); + + const { ref } = useIntersectionObserver({ + threshold: 0.5, + onChange: (isIntersecting) => { + if (isIntersecting) getEmployeeInfiniteQuery.fetchNextPage(); + } + }); + + return ( + <> + page.rows) + ) + : [] + } + onSearchChange={debouncedSetEmployeeSearch} + loading={getEmployeeInfiniteQuery.isLoading} + /> +
+ + ); +}; diff --git a/src/components/comboboxes/EmployeesCombobox/helpers/defaultConvertEmployees.ts b/src/components/comboboxes/EmployeesCombobox/helpers/defaultConvertEmployees.ts new file mode 100644 index 00000000..e633e1e9 --- /dev/null +++ b/src/components/comboboxes/EmployeesCombobox/helpers/defaultConvertEmployees.ts @@ -0,0 +1,7 @@ +import type { EmployeeResponse } from '@/api-types'; + +export const defaultConvertEmployees = (employees: EmployeeResponse[]) => + employees.map((employee) => ({ + label: `${employee.lastName} ${employee.firstName} ${employee.middleName}`, + value: employee.id + })); diff --git a/src/components/comboboxes/index.ts b/src/components/comboboxes/index.ts index 1aeaa42c..1286e3e0 100644 --- a/src/components/comboboxes/index.ts +++ b/src/components/comboboxes/index.ts @@ -1 +1,3 @@ +export * from './ActivitiesCombobox/ActivitiesCombobox'; export * from './AddressCombobox/AddressCombobox'; +export * from './EmployeesCombobox/EmployeesCombobox'; diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx new file mode 100644 index 00000000..659210e2 --- /dev/null +++ b/src/components/ui/calendar.tsx @@ -0,0 +1,62 @@ +// eslint-disable-next-line eslint-comments/disable-enable-pair +/* eslint-disable react/no-unstable-nested-components */ + +'use client'; + +import * as React from 'react'; +import { DayPicker } from 'react-day-picker'; +import { ChevronLeft, ChevronRight } from 'lucide-react'; + +import { buttonVariants } from '@/components/ui/button'; +import { cn } from '@/lib/utils'; + +export type CalendarProps = React.ComponentProps; + +const Calendar = ({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) => { + return ( + , + IconRight: () => + }} + {...props} + /> + ); +}; +Calendar.displayName = 'Calendar'; + +export { Calendar }; diff --git a/src/components/ui/combobox.tsx b/src/components/ui/combobox.tsx index 1aae7c3f..03c712d8 100644 --- a/src/components/ui/combobox.tsx +++ b/src/components/ui/combobox.tsx @@ -55,7 +55,10 @@ export const Combobox = ({ loading = false }: ComboboxProps) => { const [open, setOpen] = React.useState(false); - + const label = React.useMemo( + () => (value ? items.find((item) => item.value === value)?.label : selectItemMsg), + [items, value] + ); return ( @@ -69,9 +72,8 @@ export const Combobox = ({ )} > - {!!onSearchChange && (value || selectItemMsg)} - {!onSearchChange && - (value ? items.find((item) => item.value === value)?.label : selectItemMsg)} + {!!onSearchChange && (label || selectItemMsg)} + {!onSearchChange && label} diff --git a/src/components/ui/data-table.tsx b/src/components/ui/data-table.tsx index c1f3882f..736cc92c 100644 --- a/src/components/ui/data-table.tsx +++ b/src/components/ui/data-table.tsx @@ -128,7 +128,16 @@ interface DataTableProps extends React.ComponentProps<'div'> { export const DataTable = React.forwardRef>( ( - { children, columns, rows, table, loading, pagination, ...props }: DataTableProps, + { + children, + columns, + rows, + table, + loading, + pagination, + className, + ...props + }: DataTableProps, ref ) => { const value = React.useMemo( @@ -144,7 +153,11 @@ export const DataTable = React.forwardRef>( return (
@@ -215,16 +228,17 @@ interface DataTableColumnHeaderProps extends React.ComponentProps<'div'> { columnName: string; headerLabel: string; sortable: boolean; + centered?: boolean; children: React.ReactNode; } export const DataTableColumnHeader = React.forwardRef( - ({ columnName, children, sortable = false }, ref) => { + ({ columnName, children, sortable = false, centered = false }, ref) => { const { searchParams, setSearchParam } = useSearchParams(); const sorting = searchParams.get('sort'); return ( -
+
{sortable && ( + + + + + +
+ ); +}; diff --git a/src/components/ui/date-picker.tsx b/src/components/ui/date-picker.tsx new file mode 100644 index 00000000..e1707ec2 --- /dev/null +++ b/src/components/ui/date-picker.tsx @@ -0,0 +1,41 @@ +import { format } from 'date-fns'; +import { CalendarIcon } from 'lucide-react'; + +import { I18nText } from '@/components/common'; +import { Button, Calendar, Popover, PopoverContent, PopoverTrigger } from '@/components/ui'; +import { cn } from '@/lib/utils'; + +export type DatePickerProps = { + value?: Date; + onChange: (value: Date | undefined) => void; + className?: string; +}; + +export const DatePicker = ({ onChange, value, className }: DatePickerProps) => { + return ( + + + + + + date < new Date('1900-01-01')} + initialFocus + /> + + + ); +}; diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts index cad260ea..d14fad62 100644 --- a/src/components/ui/index.ts +++ b/src/components/ui/index.ts @@ -7,6 +7,7 @@ export * from './badge'; export * from './bik-input'; export * from './breadcrumb'; export * from './button'; +export * from './calendar'; export * from './card'; export * from './carousel'; export * from './checkbox'; @@ -17,6 +18,8 @@ export * from './collapsible'; export * from './combobox'; export * from './command'; export * from './data-table'; +export * from './date-picker'; +export * from './date-picker-with-range'; export * from './dialog'; export * from './dropdown-menu'; export * from './dropzone-media'; diff --git a/src/utils/api/hooks/index.ts b/src/utils/api/hooks/index.ts index d44c70e0..3d6295f4 100644 --- a/src/utils/api/hooks/index.ts +++ b/src/utils/api/hooks/index.ts @@ -2,15 +2,18 @@ export * from './useDeleteFileByIdMutation'; export * from './useDeleteLegalAddressByIdMutation'; export * from './useDeleteOrganizationDeleteEmployeeMutation'; export * from './useGetActivityByIdQuery'; +export * from './useGetActivityInfiniteQuery'; export * from './useGetAddressQuery'; export * from './useGetAuthNewCodeMutation'; export * from './useGetCategoryQuery'; export * from './useGetChangesInfiniteQuery'; +export * from './useGetEmployeeInfiniteQuery'; export * from './useGetUserMeMutation'; export * from './usePostAuthLoginEmailMutation'; export * from './usePostChangesMutation'; export * from './usePostFileMutation'; export * from './usePostOrganizationRegisterMutation'; +export * from './usePostScheduleMutation'; export * from './usePutActivityByIdMutation'; export * from './usePutOrganizationByIdMutation'; export * from './usePutTariffByIdMutation'; diff --git a/src/utils/api/hooks/useGetActivityInfiniteQuery.ts b/src/utils/api/hooks/useGetActivityInfiniteQuery.ts new file mode 100644 index 00000000..ad74eaee --- /dev/null +++ b/src/utils/api/hooks/useGetActivityInfiniteQuery.ts @@ -0,0 +1,32 @@ +import type { InfiniteData, QueryKey } from '@tanstack/react-query'; +import { useInfiniteQuery } from '@tanstack/react-query'; + +import type { ActivityWithPaginationResponse } from '@/api-types'; + +import type { GetActivityParams } from '../requests'; +import { getActivity } from '../requests'; + +export const useGetActivityInfiniteQuery = ( + params: GetActivityParams, + settings?: InfiniteQuerySettings +) => + useInfiniteQuery< + ActivityWithPaginationResponse, + any, + InfiniteData, + QueryKey, + number + >({ + queryKey: ['getActivityList', ...Object.values(params)], + initialPageParam: params.current ?? 1, + queryFn: ({ pageParam }) => + getActivity({ + params: { ...params, current: pageParam }, + config: settings?.config + }), + getNextPageParam: (lastPage, pages) => + Math.ceil(lastPage.pagination.count / lastPage.pagination.limit) > pages.length + ? pages.length + 1 + : undefined, + ...settings?.options + }); diff --git a/src/utils/api/hooks/useGetChangesInfiniteQuery.ts b/src/utils/api/hooks/useGetChangesInfiniteQuery.ts index 732dbb5c..f02e1f7e 100644 --- a/src/utils/api/hooks/useGetChangesInfiniteQuery.ts +++ b/src/utils/api/hooks/useGetChangesInfiniteQuery.ts @@ -18,7 +18,7 @@ export const useGetChangesInfiniteQuery = ( number >({ queryKey: ['getChanges', params.current], - initialPageParam: params.current, + initialPageParam: params.current ?? 1, queryFn: ({ pageParam }) => getChanges({ params: { ...params, current: pageParam }, diff --git a/src/utils/api/hooks/useGetEmployeeInfiniteQuery.ts b/src/utils/api/hooks/useGetEmployeeInfiniteQuery.ts new file mode 100644 index 00000000..46a09704 --- /dev/null +++ b/src/utils/api/hooks/useGetEmployeeInfiniteQuery.ts @@ -0,0 +1,27 @@ +import type { InfiniteData, QueryKey } from '@tanstack/react-query'; +import { useInfiniteQuery } from '@tanstack/react-query'; + +import type { EmployeeListResponse } from '@/api-types'; +import type { GetEmployeeParams } from '@/utils/api'; +import { getEmployee } from '@/utils/api'; + +export const useGetEmployeeInfiniteQuery = ( + params: GetEmployeeParams, + settings?: InfiniteQuerySettings +) => + useInfiniteQuery, QueryKey, number>( + { + queryKey: ['getEmployee', ...Object.values(params)], + initialPageParam: params.current ?? 1, + queryFn: ({ pageParam }) => + getEmployee({ + params: { ...params, current: pageParam }, + config: settings?.config + }), + getNextPageParam: (lastPage, pages) => + Math.ceil(lastPage.pagination.count / lastPage.pagination.limit) > pages.length + ? pages.length + 1 + : undefined, + ...settings?.options + } + ); diff --git a/src/utils/api/hooks/usePostScheduleMutation.ts b/src/utils/api/hooks/usePostScheduleMutation.ts new file mode 100644 index 00000000..dc3def42 --- /dev/null +++ b/src/utils/api/hooks/usePostScheduleMutation.ts @@ -0,0 +1,14 @@ +import { useMutation } from '@tanstack/react-query'; + +import type { PostScheduleRequestConfig } from '../requests'; +import { postSchedule } from '../requests'; + +export const usePostScheduleMutation = ( + settings?: MutationSettings +) => + useMutation({ + mutationKey: ['postSchedule'], + mutationFn: ({ params, config }) => + postSchedule({ params, config: { ...settings?.config, ...config } }), + ...settings?.options + }); diff --git a/src/utils/api/requests/changes/index.ts b/src/utils/api/requests/changes/index.ts index 235a133f..95205769 100644 --- a/src/utils/api/requests/changes/index.ts +++ b/src/utils/api/requests/changes/index.ts @@ -1,11 +1,12 @@ -import type { ChangesResponse, ChangesResponseWithPagination, CreateChangesDto } from '@/api-types'; +import type { + ChangesControllerGetChangesParams, + ChangesResponse, + ChangesResponseWithPagination, + CreateChangesDto +} from '@/api-types'; import { api } from '@/utils/api/instance'; -export interface GetChangesParams { - current: number; - limit: number; - criteria: string; -} +export type GetChangesParams = ChangesControllerGetChangesParams; export type GetChangesRequestConfig = RequestConfig; diff --git a/src/utils/api/requests/employee/index.ts b/src/utils/api/requests/employee/index.ts new file mode 100644 index 00000000..e974558c --- /dev/null +++ b/src/utils/api/requests/employee/index.ts @@ -0,0 +1,12 @@ +import type { EmployeeControllerGetEmployeesParams, EmployeeListResponse } from '@/api-types'; +import { api } from '@/utils/api/instance'; + +export type GetEmployeeParams = EmployeeControllerGetEmployeesParams; + +export type GetEmployeeRequestConfig = RequestConfig; + +export const getEmployee = async ({ params, config }: GetEmployeeRequestConfig) => + api.get('employee', { + ...config, + params: { ...config?.params, ...params } + }); diff --git a/src/utils/api/requests/index.ts b/src/utils/api/requests/index.ts index 1103e70c..f4c328df 100644 --- a/src/utils/api/requests/index.ts +++ b/src/utils/api/requests/index.ts @@ -7,7 +7,8 @@ export * from './auth/login/email'; export * from './auth/new-code'; export * from './category'; export * from './changes'; -export * from './file/id/index'; +export * from './employee'; +export * from './file/id'; export * from './legal-address'; export * from './legal-address/id'; export * from './legal-addresses/legalId'; @@ -19,6 +20,8 @@ export * from './organization/edit-employee'; export * from './organization/id'; export * from './organization/id/employees'; export * from './organization/register'; +export * from './schedule'; +export * from './schedules'; export * from './tariff/id'; export * from './tariff/legalEntityId'; export * from './user'; diff --git a/src/utils/api/requests/organization/index.ts b/src/utils/api/requests/organization/index.ts index d2c0def4..72804a9d 100644 --- a/src/utils/api/requests/organization/index.ts +++ b/src/utils/api/requests/organization/index.ts @@ -8,8 +8,8 @@ import { api } from '@/utils/api/instance'; export type GetOrganizationParams = OrganizationControllerFindOrganizationsParams; export type GetOrganizationRequestConfig = RequestConfig; -export const getOrganization = async (requestConfig?: GetOrganizationRequestConfig) => - api.get( - 'organization', - requestConfig?.config - ); +export const getOrganization = async ({ params, config }: GetOrganizationRequestConfig) => + api.get('organization', { + ...config, + params: { ...config?.params, ...params } + }); diff --git a/src/utils/api/requests/schedule/index.ts b/src/utils/api/requests/schedule/index.ts new file mode 100644 index 00000000..5bca7424 --- /dev/null +++ b/src/utils/api/requests/schedule/index.ts @@ -0,0 +1,8 @@ +import type { CreateScheduleDto } from '@/api-types'; +import { api } from '@/utils/api/instance'; + +export type PostScheduleParams = CreateScheduleDto; +export type PostScheduleRequestConfig = RequestConfig; + +export const postSchedule = async ({ params, config }: PostScheduleRequestConfig) => + api.post('schedule', params, config); diff --git a/src/utils/api/requests/schedules/index.ts b/src/utils/api/requests/schedules/index.ts new file mode 100644 index 00000000..36edfbed --- /dev/null +++ b/src/utils/api/requests/schedules/index.ts @@ -0,0 +1,15 @@ +import type { ScheduleControllerGetSchedulesParams, ScheduleListResponse } from '@/api-types'; +import { api } from '@/utils/api/instance'; + +export type GetSchedulesParams = ScheduleControllerGetSchedulesParams; + +export type GetSchedulesRequestConfig = RequestConfig; + +export const getSchedules = async ({ + params: { organizationId, ...params }, + config +}: GetSchedulesRequestConfig) => + api.get('schedules', { + ...config, + params: { ...config?.params, ...params } + }); diff --git a/src/utils/helpers/addLeadingZero.ts b/src/utils/helpers/addLeadingZero.ts new file mode 100644 index 00000000..2addc642 --- /dev/null +++ b/src/utils/helpers/addLeadingZero.ts @@ -0,0 +1 @@ +export const addLeadingZero = (num: number) => num.toString().padStart(2, '0'); diff --git a/src/utils/helpers/convertLocalitiesToComboboxItems.ts b/src/utils/helpers/convertLocalitiesToComboboxItems.ts new file mode 100644 index 00000000..fc82a432 --- /dev/null +++ b/src/utils/helpers/convertLocalitiesToComboboxItems.ts @@ -0,0 +1,12 @@ +import type { AddressResponseDto } from '@/api-types'; + +export const convertLocalitiesToComboboxItems = (addresses: AddressResponseDto[]) => + addresses + .filter( + (address) => + address.city && !address.street && !address.house && !address.flat && address.cityWithType + ) + .map((address) => ({ + label: address.cityWithType, + value: address.cityWithType + })); diff --git a/src/utils/helpers/getNavigationLinksByRole.ts b/src/utils/helpers/getNavigationLinksByRole.ts index 40a8dff0..47dd5872 100644 --- a/src/utils/helpers/getNavigationLinksByRole.ts +++ b/src/utils/helpers/getNavigationLinksByRole.ts @@ -6,6 +6,8 @@ export const getNavigationLinksByUserRole = (userRole: UserResponseRolesItem) => switch (userRole) { case 'SUPERADMIN': return ORGANIZER_LINKS; + case 'ADMIN': + return ORGANIZER_LINKS; default: throw new Error('Invalid user role'); } diff --git a/src/utils/helpers/getWeekDayByIndex.ts b/src/utils/helpers/getWeekDayByIndex.ts new file mode 100644 index 00000000..abeb7a97 --- /dev/null +++ b/src/utils/helpers/getWeekDayByIndex.ts @@ -0,0 +1,3 @@ +import { WeekDayEnum } from '@/api-types'; + +export const getWeekDayByIndex = (index: number) => Object.values(WeekDayEnum)[index]; diff --git a/src/utils/helpers/index.ts b/src/utils/helpers/index.ts index e5a08700..f6251157 100644 --- a/src/utils/helpers/index.ts +++ b/src/utils/helpers/index.ts @@ -1,2 +1,4 @@ +export * from './addLeadingZero'; export * from './getMessagesByLocale'; export * from './getNavigationLinksByRole'; +export * from './getWeekDayByIndex'; diff --git a/static/locales/ru.json b/static/locales/ru.json index 93adcb4a..afcc5abc 100644 --- a/static/locales/ru.json +++ b/static/locales/ru.json @@ -9,6 +9,7 @@ "partners.addresses.title": "Адреса", "partners.activities.title": "Активности", "partners.employees.title": "Сотрудники", + "partners.schedule.title": "Расписание", "redirect.title": "Перейдите на мобильную версию", "redirect.description": "Для дальнейшего пользования приложением, откройте страницу в телефоне", @@ -64,6 +65,7 @@ "button.close": "Закрыть", "button.addActivity": "Добавить активность", "button.addEmployee": "Добавить сотрудника", + "button.addSchedule": "Добавить расписание", "button.makeCover": "Сделать обложкой", "button.updateTariff": "Сменить тариф", "button.learnDetails": "Узнать подробности", @@ -202,6 +204,21 @@ "field.name.label": "Имя", "field.surname.label": "Фамилия", "field.role.label": "Роль", + "field.datePicker.label": "Выберите дату", + + "field.chooseLocation.label": "Выбор адреса", + "field.chooseActivity.label": "Выбор активности", + "field.chooseLead.label": "Выбор ведущего", + "field.preEntry.label": "Предварительная запись", + "field.activity.label": "Активность", + "field.startAndEndDate.label": "Дата начала и окончания", + "field.date.label": "Дата", + "field.numberOfSeats.label": "Кол-во мест", + + "field.preEntry.true": "По записи", + "field.preEntry.false": "Без записи", + "field.isRegularActivity.true": "Регулярное", + "field.isRegularActivity.false": "Разовое", "field.freeActivity.label": "Бесплатные активности", "field.paidActivity.label": "Платные активности", "field.periodMonth.label": "Период", @@ -296,6 +313,14 @@ "table.column.organization.tariff": "Тариф", "table.column.organization.countDays": "Кол-во дней", + "table.column.schedule.activity": "Активность", + "table.column.schedule.address": "Адрес", + "table.column.schedule.leading": "Сотрудник", + "table.column.schedule.date": "Дата", + "table.column.schedule.time": "Время", + "table.column.schedule.numberOfSeats": "Кол-во записей", + "table.column.schedule.isDone": "Проведено", + "table.column.activities.organization": "Организация", "table.column.activities.activity": "Активность", "table.column.activities.location": "Населенный пункт", @@ -342,12 +367,13 @@ "validation.format": "Неверный формат", "validation.photo.max": "Максимальный размер фото 5MB", "validation.photo.format": "Только .jpg, .jpeg, .png и .webp форматы поддерживаются", + "validation.dateEnd.required": "Выберите дату окончания", "dialog.registerOrganization.title": "Заполните заявку", "dialog.registerOrganization.success": "Спасибо за заявку, мы свяжемся с вами в ближайшее время", "dialog.addAddress.title": "Добавление адреса", - "dialog.addAddress.form.untill": "до", + "dialog.addAddress.form.until": "до", "dropzone.image.error": "Разрешена загрузка 1 файла-изображения", "dropzone.docs.error": "Разрешена загрузка 1 файла-документа", @@ -371,6 +397,8 @@ "dialog.scanQRCode.title": "QR-код партнера", + "dialog.addSchedule.title": "Задание расписания", + "tooltip.inn": "ИНН юр. лица - 10 цифр, ИНН физ. лица - 12 цифр", "tooltip.kpp": "КПП - 9 цифр", "tooltip.ogrn": "ОГРН - 13 цифр, ОГРНИП - 15 цифр", diff --git a/yarn.lock b/yarn.lock index a77aae2f..3a1f0536 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5928,11 +5928,6 @@ crypto-random-string@^2.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== -crypto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/crypto/-/crypto-1.0.1.tgz#2af1b7cad8175d24c8a1b0778255794a21803037" - integrity sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig== - css-functions-list@^3.2.1: version "3.2.2" resolved "https://registry.yarnpkg.com/css-functions-list/-/css-functions-list-3.2.2.tgz#9a54c6dd8416ed25c1079cd88234e927526c1922" @@ -9697,11 +9692,6 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -package@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/package/-/package-1.0.1.tgz#d25a1f99e2506dcb27d6704b83dca8a312e4edcc" - integrity sha512-g6xZR6CO7okjie83sIRJodgGvaXqymfE5GLhN8N2TmZGShmHc/V23hO/vWbdnuy3D81As3pfovw72gGi42l9qA== - pako@~0.2.0: version "0.2.9" resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" @@ -10335,6 +10325,11 @@ react-colorful@^5.1.2: resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.6.1.tgz#7dc2aed2d7c72fac89694e834d179e32f3da563b" integrity sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw== +react-day-picker@^8.10.0: + version "8.10.1" + resolved "https://registry.yarnpkg.com/react-day-picker/-/react-day-picker-8.10.1.tgz#4762ec298865919b93ec09ba69621580835b8e80" + integrity sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA== + react-docgen-typescript@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz#4611055e569edc071204aadb20e1c93e1ab1659c" @@ -11331,14 +11326,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@6.0.1, strip-ansi@^5.2.0, strip-ansi@^6.0.0, strip-ansi@^6.0.1, strip-ansi@^7.0.1, strip-ansi@^7.1.0: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^5.2.0, strip-ansi@^6.0.0, strip-ansi@^6.0.1, strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -12384,16 +12372,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@7.0.0, wrap-ansi@^6.2.0, wrap-ansi@^7.0.0, wrap-ansi@^8.1.0, wrap-ansi@^9.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@7.0.0, wrap-ansi@^6.2.0, wrap-ansi@^7.0.0, wrap-ansi@^8.1.0, wrap-ansi@^9.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==