Compare commits
4 Commits
main
...
fix/bugs-f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1da2c834a | ||
|
|
57cab6209d | ||
|
|
60002a7169 | ||
|
|
8b0f93bf23 |
1
.github/workflows/deploy.yml
vendored
1
.github/workflows/deploy.yml
vendored
@ -98,5 +98,6 @@ jobs:
|
|||||||
cd /home/${{ secrets.VPS_USER }}/zapishis && \
|
cd /home/${{ secrets.VPS_USER }}/zapishis && \
|
||||||
docker login -u ${{ secrets.DOCKERHUB_USERNAME }} -p ${{ secrets.DOCKERHUB_TOKEN }} && \
|
docker login -u ${{ secrets.DOCKERHUB_USERNAME }} -p ${{ secrets.DOCKERHUB_TOKEN }} && \
|
||||||
docker compose pull && \
|
docker compose pull && \
|
||||||
|
docker compose down && \
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
"
|
"
|
||||||
|
|||||||
@ -1,36 +1,8 @@
|
|||||||
'use server';
|
import * as customers from './server/customers';
|
||||||
|
import { wrapClientAction } from '@/utils/actions';
|
||||||
|
|
||||||
import { useService } from './lib/service';
|
export const addMasters = wrapClientAction(customers.addMasters);
|
||||||
import { CustomersService } from '@repo/graphql/api/customers';
|
export const getClients = wrapClientAction(customers.getClients);
|
||||||
|
export const getCustomer = wrapClientAction(customers.getCustomer);
|
||||||
const getService = useService(CustomersService);
|
export const getMasters = wrapClientAction(customers.getMasters);
|
||||||
|
export const updateCustomer = wrapClientAction(customers.updateCustomer);
|
||||||
export async function addMasters(...variables: Parameters<CustomersService['addMasters']>) {
|
|
||||||
const service = await getService();
|
|
||||||
|
|
||||||
return service.addMasters(...variables);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getClients(...variables: Parameters<CustomersService['getClients']>) {
|
|
||||||
const service = await getService();
|
|
||||||
|
|
||||||
return service.getClients(...variables);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getCustomer(...variables: Parameters<CustomersService['getCustomer']>) {
|
|
||||||
const service = await getService();
|
|
||||||
|
|
||||||
return service.getCustomer(...variables);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getMasters(...variables: Parameters<CustomersService['getMasters']>) {
|
|
||||||
const service = await getService();
|
|
||||||
|
|
||||||
return service.getMasters(...variables);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateCustomer(...variables: Parameters<CustomersService['updateCustomer']>) {
|
|
||||||
const service = await getService();
|
|
||||||
|
|
||||||
return service.updateCustomer(...variables);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,30 +1,7 @@
|
|||||||
'use server';
|
import * as orders from './server/orders';
|
||||||
|
import { wrapClientAction } from '@/utils/actions';
|
||||||
|
|
||||||
import { useService } from './lib/service';
|
export const createOrder = wrapClientAction(orders.createOrder);
|
||||||
import { OrdersService } from '@repo/graphql/api/orders';
|
export const getOrder = wrapClientAction(orders.getOrder);
|
||||||
|
export const getOrders = wrapClientAction(orders.getOrders);
|
||||||
const getServicesService = useService(OrdersService);
|
export const updateOrder = wrapClientAction(orders.updateOrder);
|
||||||
|
|
||||||
export async function createOrder(...variables: Parameters<OrdersService['createOrder']>) {
|
|
||||||
const service = await getServicesService();
|
|
||||||
|
|
||||||
return service.createOrder(...variables);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getOrder(...variables: Parameters<OrdersService['getOrder']>) {
|
|
||||||
const service = await getServicesService();
|
|
||||||
|
|
||||||
return service.getOrder(...variables);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getOrders(...variables: Parameters<OrdersService['getOrders']>) {
|
|
||||||
const service = await getServicesService();
|
|
||||||
|
|
||||||
return service.getOrders(...variables);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateOrder(...variables: Parameters<OrdersService['updateOrder']>) {
|
|
||||||
const service = await getServicesService();
|
|
||||||
|
|
||||||
return service.updateOrder(...variables);
|
|
||||||
}
|
|
||||||
|
|||||||
37
apps/web/actions/api/server/customers.ts
Normal file
37
apps/web/actions/api/server/customers.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
'use server';
|
||||||
|
|
||||||
|
import { useService } from '../lib/service';
|
||||||
|
import { wrapServerAction } from '@/utils/actions';
|
||||||
|
import { CustomersService } from '@repo/graphql/api/customers';
|
||||||
|
|
||||||
|
const getService = useService(CustomersService);
|
||||||
|
|
||||||
|
export async function addMasters(...variables: Parameters<CustomersService['addMasters']>) {
|
||||||
|
const service = await getService();
|
||||||
|
|
||||||
|
return wrapServerAction(() => service.addMasters(...variables));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getClients(...variables: Parameters<CustomersService['getClients']>) {
|
||||||
|
const service = await getService();
|
||||||
|
|
||||||
|
return wrapServerAction(() => service.getClients(...variables));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCustomer(...variables: Parameters<CustomersService['getCustomer']>) {
|
||||||
|
const service = await getService();
|
||||||
|
|
||||||
|
return wrapServerAction(() => service.getCustomer(...variables));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMasters(...variables: Parameters<CustomersService['getMasters']>) {
|
||||||
|
const service = await getService();
|
||||||
|
|
||||||
|
return wrapServerAction(() => service.getMasters(...variables));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateCustomer(...variables: Parameters<CustomersService['updateCustomer']>) {
|
||||||
|
const service = await getService();
|
||||||
|
|
||||||
|
return wrapServerAction(() => service.updateCustomer(...variables));
|
||||||
|
}
|
||||||
31
apps/web/actions/api/server/orders.ts
Normal file
31
apps/web/actions/api/server/orders.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
'use server';
|
||||||
|
|
||||||
|
import { useService } from '../lib/service';
|
||||||
|
import { wrapServerAction } from '@/utils/actions';
|
||||||
|
import { OrdersService } from '@repo/graphql/api/orders';
|
||||||
|
|
||||||
|
const getServicesService = useService(OrdersService);
|
||||||
|
|
||||||
|
export async function createOrder(...variables: Parameters<OrdersService['createOrder']>) {
|
||||||
|
const service = await getServicesService();
|
||||||
|
|
||||||
|
return wrapServerAction(() => service.createOrder(...variables));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getOrder(...variables: Parameters<OrdersService['getOrder']>) {
|
||||||
|
const service = await getServicesService();
|
||||||
|
|
||||||
|
return wrapServerAction(() => service.getOrder(...variables));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getOrders(...variables: Parameters<OrdersService['getOrders']>) {
|
||||||
|
const service = await getServicesService();
|
||||||
|
|
||||||
|
return wrapServerAction(() => service.getOrders(...variables));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateOrder(...variables: Parameters<OrdersService['updateOrder']>) {
|
||||||
|
const service = await getServicesService();
|
||||||
|
|
||||||
|
return wrapServerAction(() => service.updateOrder(...variables));
|
||||||
|
}
|
||||||
19
apps/web/actions/api/server/services.ts
Normal file
19
apps/web/actions/api/server/services.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
'use server';
|
||||||
|
|
||||||
|
import { useService } from '../lib/service';
|
||||||
|
import { wrapServerAction } from '@/utils/actions';
|
||||||
|
import { ServicesService } from '@repo/graphql/api/services';
|
||||||
|
|
||||||
|
const getServicesService = useService(ServicesService);
|
||||||
|
|
||||||
|
export async function getService(...variables: Parameters<ServicesService['getService']>) {
|
||||||
|
const service = await getServicesService();
|
||||||
|
|
||||||
|
return wrapServerAction(() => service.getService(...variables));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getServices(...variables: Parameters<ServicesService['getServices']>) {
|
||||||
|
const service = await getServicesService();
|
||||||
|
|
||||||
|
return wrapServerAction(() => service.getServices(...variables));
|
||||||
|
}
|
||||||
45
apps/web/actions/api/server/slots.ts
Normal file
45
apps/web/actions/api/server/slots.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
'use server';
|
||||||
|
|
||||||
|
import { useService } from '../lib/service';
|
||||||
|
import { wrapServerAction } from '@/utils/actions';
|
||||||
|
import { SlotsService } from '@repo/graphql/api/slots';
|
||||||
|
|
||||||
|
const getService = useService(SlotsService);
|
||||||
|
|
||||||
|
export async function createSlot(...variables: Parameters<SlotsService['createSlot']>) {
|
||||||
|
const service = await getService();
|
||||||
|
|
||||||
|
return wrapServerAction(() => service.createSlot(...variables));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteSlot(...variables: Parameters<SlotsService['deleteSlot']>) {
|
||||||
|
const service = await getService();
|
||||||
|
|
||||||
|
return wrapServerAction(() => service.deleteSlot(...variables));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAvailableTimeSlots(
|
||||||
|
...variables: Parameters<SlotsService['getAvailableTimeSlots']>
|
||||||
|
) {
|
||||||
|
const service = await getService();
|
||||||
|
|
||||||
|
return wrapServerAction(() => service.getAvailableTimeSlots(...variables));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSlot(...variables: Parameters<SlotsService['getSlot']>) {
|
||||||
|
const service = await getService();
|
||||||
|
|
||||||
|
return wrapServerAction(() => service.getSlot(...variables));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSlots(...variables: Parameters<SlotsService['getSlots']>) {
|
||||||
|
const service = await getService();
|
||||||
|
|
||||||
|
return wrapServerAction(() => service.getSlots(...variables));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateSlot(...variables: Parameters<SlotsService['updateSlot']>) {
|
||||||
|
const service = await getService();
|
||||||
|
|
||||||
|
return wrapServerAction(() => service.updateSlot(...variables));
|
||||||
|
}
|
||||||
@ -1,18 +1,5 @@
|
|||||||
'use server';
|
import * as services from './server/services';
|
||||||
|
import { wrapClientAction } from '@/utils/actions';
|
||||||
|
|
||||||
import { useService } from './lib/service';
|
export const getServices = wrapClientAction(services.getServices);
|
||||||
import { ServicesService } from '@repo/graphql/api/services';
|
export const getService = wrapClientAction(services.getService);
|
||||||
|
|
||||||
const getServicesService = useService(ServicesService);
|
|
||||||
|
|
||||||
export async function getService(...variables: Parameters<ServicesService['getService']>) {
|
|
||||||
const service = await getServicesService();
|
|
||||||
|
|
||||||
return service.getService(...variables);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getServices(...variables: Parameters<ServicesService['getServices']>) {
|
|
||||||
const service = await getServicesService();
|
|
||||||
|
|
||||||
return service.getServices(...variables);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,44 +1,9 @@
|
|||||||
'use server';
|
import * as slots from './server/slots';
|
||||||
|
import { wrapClientAction } from '@/utils/actions';
|
||||||
|
|
||||||
import { useService } from './lib/service';
|
export const getSlot = wrapClientAction(slots.getSlot);
|
||||||
import { SlotsService } from '@repo/graphql/api/slots';
|
export const getSlots = wrapClientAction(slots.getSlots);
|
||||||
|
export const createSlot = wrapClientAction(slots.createSlot);
|
||||||
const getService = useService(SlotsService);
|
export const updateSlot = wrapClientAction(slots.updateSlot);
|
||||||
|
export const deleteSlot = wrapClientAction(slots.deleteSlot);
|
||||||
export async function createSlot(...variables: Parameters<SlotsService['createSlot']>) {
|
export const getAvailableTimeSlots = wrapClientAction(slots.getAvailableTimeSlots);
|
||||||
const service = await getService();
|
|
||||||
|
|
||||||
return service.createSlot(...variables);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteSlot(...variables: Parameters<SlotsService['deleteSlot']>) {
|
|
||||||
const service = await getService();
|
|
||||||
|
|
||||||
return service.deleteSlot(...variables);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAvailableTimeSlots(
|
|
||||||
...variables: Parameters<SlotsService['getAvailableTimeSlots']>
|
|
||||||
) {
|
|
||||||
const service = await getService();
|
|
||||||
|
|
||||||
return service.getAvailableTimeSlots(...variables);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getSlot(...variables: Parameters<SlotsService['getSlot']>) {
|
|
||||||
const service = await getService();
|
|
||||||
|
|
||||||
return service.getSlot(...variables);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getSlots(...variables: Parameters<SlotsService['getSlots']>) {
|
|
||||||
const service = await getService();
|
|
||||||
|
|
||||||
return service.getSlots(...variables);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateSlot(...variables: Parameters<SlotsService['updateSlot']>) {
|
|
||||||
const service = await getService();
|
|
||||||
|
|
||||||
return service.updateSlot(...variables);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
import { OrderCard } from '@/components/shared/order-card';
|
import { OrderCard } from '@/components/shared/order-card';
|
||||||
import { useCustomerQuery, useIsMaster } from '@/hooks/api/customers';
|
import { useCustomerQuery, useIsMaster } from '@/hooks/api/customers';
|
||||||
import { useOrdersQuery } from '@/hooks/api/orders';
|
import { useOrdersInfiniteQuery } from '@/hooks/api/orders';
|
||||||
import { useDateTimeStore } from '@/stores/datetime';
|
import { useDateTimeStore } from '@/stores/datetime';
|
||||||
|
import { Button } from '@repo/ui/components/ui/button';
|
||||||
import { getDateUTCRange } from '@repo/utils/datetime-format';
|
import { getDateUTCRange } from '@repo/utils/datetime-format';
|
||||||
|
|
||||||
export function ClientsOrdersList() {
|
export function ClientsOrdersList() {
|
||||||
@ -14,7 +15,12 @@ export function ClientsOrdersList() {
|
|||||||
const selectedDate = useDateTimeStore((store) => store.date);
|
const selectedDate = useDateTimeStore((store) => store.date);
|
||||||
const { endOfDay, startOfDay } = getDateUTCRange(selectedDate).day();
|
const { endOfDay, startOfDay } = getDateUTCRange(selectedDate).day();
|
||||||
|
|
||||||
const { data: { orders } = {}, isLoading } = useOrdersQuery(
|
const {
|
||||||
|
data: { pages } = {},
|
||||||
|
fetchNextPage,
|
||||||
|
hasNextPage,
|
||||||
|
isLoading,
|
||||||
|
} = useOrdersInfiniteQuery(
|
||||||
{
|
{
|
||||||
filters: {
|
filters: {
|
||||||
slot: {
|
slot: {
|
||||||
@ -31,19 +37,23 @@ export function ClientsOrdersList() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pagination: {
|
|
||||||
limit: selectedDate ? undefined : 10,
|
|
||||||
},
|
},
|
||||||
},
|
{ enabled: Boolean(customer?.documentId) && isMaster },
|
||||||
Boolean(customer?.documentId) && isMaster,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const orders = pages?.flatMap((page) => page.orders) ?? [];
|
||||||
|
|
||||||
if (!orders?.length || isLoading || !isMaster) return null;
|
if (!orders?.length || isLoading || !isMaster) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-2">
|
<div className="flex flex-col space-y-2">
|
||||||
<h1 className="font-bold">Записи клиентов</h1>
|
<h1 className="font-bold">Записи клиентов</h1>
|
||||||
{orders?.map((order) => order && <OrderCard key={order.documentId} showDate {...order} />)}
|
{orders?.map((order) => order && <OrderCard key={order.documentId} showDate {...order} />)}
|
||||||
|
{hasNextPage && (
|
||||||
|
<Button onClick={() => fetchNextPage()} variant="ghost">
|
||||||
|
Загрузить еще
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -54,7 +64,12 @@ export function OrdersList() {
|
|||||||
const selectedDate = useDateTimeStore((store) => store.date);
|
const selectedDate = useDateTimeStore((store) => store.date);
|
||||||
const { endOfDay, startOfDay } = getDateUTCRange(selectedDate).day();
|
const { endOfDay, startOfDay } = getDateUTCRange(selectedDate).day();
|
||||||
|
|
||||||
const { data: { orders } = {}, isLoading } = useOrdersQuery(
|
const {
|
||||||
|
data: { pages } = {},
|
||||||
|
fetchNextPage,
|
||||||
|
hasNextPage,
|
||||||
|
isLoading,
|
||||||
|
} = useOrdersInfiniteQuery(
|
||||||
{
|
{
|
||||||
filters: {
|
filters: {
|
||||||
client: {
|
client: {
|
||||||
@ -71,13 +86,12 @@ export function OrdersList() {
|
|||||||
: undefined,
|
: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pagination: {
|
|
||||||
limit: selectedDate ? undefined : 10,
|
|
||||||
},
|
},
|
||||||
},
|
{ enabled: Boolean(customer?.documentId) },
|
||||||
Boolean(customer?.documentId),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const orders = pages?.flatMap((page) => page.orders) ?? [];
|
||||||
|
|
||||||
if (!orders?.length || isLoading) return null;
|
if (!orders?.length || isLoading) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -87,6 +101,11 @@ export function OrdersList() {
|
|||||||
(order) =>
|
(order) =>
|
||||||
order && <OrderCard avatarSource="master" key={order.documentId} showDate {...order} />,
|
order && <OrderCard avatarSource="master" key={order.documentId} showDate {...order} />,
|
||||||
)}
|
)}
|
||||||
|
{hasNextPage && (
|
||||||
|
<Button onClick={() => fetchNextPage()} variant="ghost">
|
||||||
|
Загрузить еще
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,8 @@
|
|||||||
import { OrderCard } from '../shared/order-card';
|
import { OrderCard } from '../shared/order-card';
|
||||||
import { type ProfileProps } from './types';
|
import { type ProfileProps } from './types';
|
||||||
import { useCustomerQuery, useIsMaster } from '@/hooks/api/customers';
|
import { useCustomerQuery, useIsMaster } from '@/hooks/api/customers';
|
||||||
import { useOrdersQuery } from '@/hooks/api/orders';
|
import { useOrdersInfiniteQuery } from '@/hooks/api/orders';
|
||||||
|
import { Button } from '@repo/ui/components/ui/button';
|
||||||
|
|
||||||
export function ProfileOrdersList({ telegramId }: Readonly<ProfileProps>) {
|
export function ProfileOrdersList({ telegramId }: Readonly<ProfileProps>) {
|
||||||
const { data: { customer } = {} } = useCustomerQuery();
|
const { data: { customer } = {} } = useCustomerQuery();
|
||||||
@ -11,7 +12,12 @@ export function ProfileOrdersList({ telegramId }: Readonly<ProfileProps>) {
|
|||||||
|
|
||||||
const { data: { customer: profile } = {} } = useCustomerQuery({ telegramId });
|
const { data: { customer: profile } = {} } = useCustomerQuery({ telegramId });
|
||||||
|
|
||||||
const { data: { orders } = {}, isLoading } = useOrdersQuery(
|
const {
|
||||||
|
data: { pages } = {},
|
||||||
|
fetchNextPage,
|
||||||
|
hasNextPage,
|
||||||
|
isLoading,
|
||||||
|
} = useOrdersInfiniteQuery(
|
||||||
{
|
{
|
||||||
filters: {
|
filters: {
|
||||||
client: {
|
client: {
|
||||||
@ -27,13 +33,12 @@ export function ProfileOrdersList({ telegramId }: Readonly<ProfileProps>) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pagination: {
|
|
||||||
limit: 5,
|
|
||||||
},
|
},
|
||||||
},
|
{ enabled: Boolean(profile?.documentId) && Boolean(customer?.documentId) },
|
||||||
Boolean(profile?.documentId) && Boolean(customer?.documentId),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const orders = pages?.flatMap((page) => page.orders) ?? [];
|
||||||
|
|
||||||
if (!orders?.length || isLoading) return null;
|
if (!orders?.length || isLoading) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -50,6 +55,11 @@ export function ProfileOrdersList({ telegramId }: Readonly<ProfileProps>) {
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
|
{hasNextPage && (
|
||||||
|
<Button onClick={() => fetchNextPage()} variant="ghost">
|
||||||
|
Загрузить еще
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
|
/* eslint-disable unicorn/prevent-abbreviations */
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { createOrder, getOrder, getOrders, updateOrder } from '@/actions/api/orders';
|
import { createOrder, getOrder, getOrders, updateOrder } from '@/actions/api/orders';
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
export const useOrderQuery = ({ documentId }: Parameters<typeof getOrder>[0]) =>
|
export const useOrderQuery = ({ documentId }: Parameters<typeof getOrder>[0]) =>
|
||||||
useQuery({
|
useQuery({
|
||||||
@ -37,6 +38,33 @@ export const useOrdersQuery = (variables: Parameters<typeof getOrders>[0], enabl
|
|||||||
staleTime: 60 * 1_000,
|
staleTime: 60 * 1_000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const useOrdersInfiniteQuery = (
|
||||||
|
variables: Omit<Parameters<typeof getOrders>[0], 'pagination'>,
|
||||||
|
{ enabled = true, pageSize = 5 },
|
||||||
|
) => {
|
||||||
|
const queryFn = ({ pageParam: page = 1 }) =>
|
||||||
|
getOrders({
|
||||||
|
...variables,
|
||||||
|
pagination: {
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return useInfiniteQuery({
|
||||||
|
enabled,
|
||||||
|
getNextPageParam: (lastPage, _allPages, lastPageParameter) => {
|
||||||
|
if (!lastPage?.orders?.length) return undefined;
|
||||||
|
|
||||||
|
return lastPageParameter + 1;
|
||||||
|
},
|
||||||
|
initialPageParam: 1,
|
||||||
|
queryFn,
|
||||||
|
queryKey: ['orders', variables, 'infinite'],
|
||||||
|
staleTime: 60 * 1_000,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const useOrderMutation = ({
|
export const useOrderMutation = ({
|
||||||
documentId,
|
documentId,
|
||||||
}: Pick<Parameters<typeof updateOrder>[0], 'documentId'>) => {
|
}: Pick<Parameters<typeof updateOrder>[0], 'documentId'>) => {
|
||||||
|
|||||||
23
apps/web/utils/actions.ts
Normal file
23
apps/web/utils/actions.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/* eslint-disable unicorn/prevent-abbreviations */
|
||||||
|
|
||||||
|
export function wrapClientAction<Args extends unknown[], Return>(
|
||||||
|
fn: (...args: Args) => Promise<{ data?: Return; error?: string; ok: boolean }>,
|
||||||
|
): (...args: Args) => Promise<Return> {
|
||||||
|
return async (...args: Args): Promise<Return> => {
|
||||||
|
const res = await fn(...args);
|
||||||
|
if (!res.ok) throw new Error(res.error ?? 'Неизвестная ошибка');
|
||||||
|
return res.data as Return;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function wrapServerAction<T>(
|
||||||
|
action: () => Promise<T>,
|
||||||
|
): Promise<{ data: T; ok: true } | { error: string; ok: false }> {
|
||||||
|
try {
|
||||||
|
const data = await action();
|
||||||
|
return { data, ok: true };
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : 'Неизвестная ошибка сервера';
|
||||||
|
return { error: message, ok: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,11 @@ services:
|
|||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
restart: always
|
restart: always
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:3000/"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 5
|
||||||
# ports:
|
# ports:
|
||||||
# - 3000:3000
|
# - 3000:3000
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@ -1,108 +0,0 @@
|
|||||||
/* eslint-disable complexity */
|
|
||||||
import * as GQL from '../types';
|
|
||||||
import { notifyByTelegramId } from '../utils/notify';
|
|
||||||
import { BaseService } from './base';
|
|
||||||
import { CustomersService } from './customers';
|
|
||||||
import { OrdersService } from './orders';
|
|
||||||
import { ServicesService } from './services';
|
|
||||||
import { SlotsService } from './slots';
|
|
||||||
import { type VariablesOf } from '@graphql-typed-document-node/core';
|
|
||||||
import { formatDate, formatTime, getMinutes, sumTime } from '@repo/utils/datetime-format';
|
|
||||||
|
|
||||||
const STATE_MAP = {
|
|
||||||
approved: 'Одобрено',
|
|
||||||
cancelled: 'Отменено',
|
|
||||||
cancelling: 'Отменяется',
|
|
||||||
completed: 'Завершено',
|
|
||||||
created: 'Создано',
|
|
||||||
scheduled: 'Запланировано',
|
|
||||||
unknown: 'Неизвестно',
|
|
||||||
};
|
|
||||||
export class NotifyService extends BaseService {
|
|
||||||
async orderCreated(
|
|
||||||
variables: VariablesOf<typeof GQL.CreateOrderDocument>,
|
|
||||||
createdByMaster: boolean,
|
|
||||||
) {
|
|
||||||
const customersService = new CustomersService(this.customer);
|
|
||||||
const slotsService = new SlotsService(this.customer);
|
|
||||||
const servicesService = new ServicesService(this.customer);
|
|
||||||
|
|
||||||
const slotId = String(variables.input.slot ?? '');
|
|
||||||
const serviceId = String(variables.input.services?.[0] ?? '');
|
|
||||||
const clientId = String(variables.input.client ?? '');
|
|
||||||
|
|
||||||
if (!serviceId || !clientId || !slotId) return;
|
|
||||||
|
|
||||||
let emoji = '🆕';
|
|
||||||
let confirmText = '';
|
|
||||||
if (createdByMaster) {
|
|
||||||
emoji = '✅';
|
|
||||||
confirmText = ' и подтверждена';
|
|
||||||
}
|
|
||||||
|
|
||||||
const { slot } = await slotsService.getSlot({ documentId: slotId });
|
|
||||||
const { service } = await servicesService.getService({ documentId: serviceId });
|
|
||||||
const { customer: master } = await customersService.getCustomer({
|
|
||||||
documentId: slot?.master?.documentId ?? '',
|
|
||||||
});
|
|
||||||
const { customer: client } = await customersService.getCustomer({ documentId: clientId });
|
|
||||||
|
|
||||||
if (!slot?.datetime_start || !variables.input.datetime_start || !service?.duration) return;
|
|
||||||
|
|
||||||
const slotDate = formatDate(slot?.datetime_start).user();
|
|
||||||
const timeStartString = formatTime(variables.input.datetime_start).user();
|
|
||||||
const timeEndString = formatTime(
|
|
||||||
sumTime(variables.input.datetime_start, getMinutes(service?.duration)),
|
|
||||||
).user();
|
|
||||||
|
|
||||||
// Мастеру
|
|
||||||
if (master?.telegramId) {
|
|
||||||
const message = `${emoji} <b>Запись создана${confirmText}!</b>\n<b>Дата:</b> ${slotDate}\n<b>Время:</b> ${timeStartString} - ${timeEndString}\n<b>Клиент:</b> ${client?.name ?? '-'}\n<b>Услуга:</b> ${service?.name ?? '-'}`;
|
|
||||||
await notifyByTelegramId(String(master.telegramId), message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Клиенту
|
|
||||||
if (client?.telegramId) {
|
|
||||||
const message = `${emoji} <b>Запись создана${confirmText}!</b>\n<b>Дата:</b> ${slotDate}\n<b>Время:</b> ${timeStartString} - ${timeEndString}\n<b>Мастер:</b> ${master?.name ?? '-'}\n<b>Услуга:</b> ${service?.name ?? '-'}`;
|
|
||||||
await notifyByTelegramId(String(client.telegramId), message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async orderUpdated(variables: VariablesOf<typeof GQL.UpdateOrderDocument>) {
|
|
||||||
// Получаем order через OrdersService
|
|
||||||
const ordersService = new OrdersService(this.customer);
|
|
||||||
const { order } = await ordersService.getOrder({ documentId: variables.documentId });
|
|
||||||
if (!order) return;
|
|
||||||
|
|
||||||
const slot = order.slot;
|
|
||||||
if (!slot) return;
|
|
||||||
|
|
||||||
const service = order.services[0];
|
|
||||||
const master = slot?.master;
|
|
||||||
const client = order.client;
|
|
||||||
|
|
||||||
const orderStateString = STATE_MAP[order.state || 'unknown'];
|
|
||||||
const slotDate = formatDate(slot?.datetime_start).user();
|
|
||||||
const timeStartString = formatTime(order.datetime_start ?? '').user();
|
|
||||||
const timeEndString = formatTime(order.datetime_end ?? '').user();
|
|
||||||
|
|
||||||
let emoji = '✏️';
|
|
||||||
if (order.state === GQL.Enum_Order_State.Cancelled) {
|
|
||||||
emoji = '❌';
|
|
||||||
} else if (order.state === GQL.Enum_Order_State.Approved) {
|
|
||||||
emoji = '✅';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Мастеру
|
|
||||||
if (master?.telegramId) {
|
|
||||||
const message = `${emoji} <b>Запись изменена!</b>\n<b>Дата:</b> ${slotDate}\n<b>Время:</b> ${timeStartString} - ${timeEndString}\n<b>Клиент:</b> ${client?.name ?? '-'}\n<b>Услуга:</b> ${service?.name ?? '-'}\n<b>Статус:</b> ${orderStateString}`;
|
|
||||||
await notifyByTelegramId(String(master.telegramId), message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Клиенту
|
|
||||||
if (client?.telegramId) {
|
|
||||||
const message = `${emoji} <b>Запись изменена!</b>\n<b>Дата:</b> ${slotDate}\n<b>Время:</b> ${timeStartString} - ${timeEndString}\n<b>Мастер:</b> ${master?.name ?? '-'}\n<b>Услуга:</b> ${service?.name ?? '-'}\n<b>Статус:</b> ${orderStateString}`;
|
|
||||||
await notifyByTelegramId(String(client.telegramId), message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -2,7 +2,6 @@ import { getClientWithToken } from '../apollo/client';
|
|||||||
import * as GQL from '../types';
|
import * as GQL from '../types';
|
||||||
import { BaseService } from './base';
|
import { BaseService } from './base';
|
||||||
import { CustomersService } from './customers';
|
import { CustomersService } from './customers';
|
||||||
import { NotifyService } from './notify';
|
|
||||||
import { ServicesService } from './services';
|
import { ServicesService } from './services';
|
||||||
import { type VariablesOf } from '@graphql-typed-document-node/core';
|
import { type VariablesOf } from '@graphql-typed-document-node/core';
|
||||||
import { isCustomerMaster } from '@repo/utils/customer';
|
import { isCustomerMaster } from '@repo/utils/customer';
|
||||||
@ -64,10 +63,6 @@ export class OrdersService extends BaseService {
|
|||||||
const error = mutationResult.errors?.at(0);
|
const error = mutationResult.errors?.at(0);
|
||||||
if (error) throw new Error(error.message);
|
if (error) throw new Error(error.message);
|
||||||
|
|
||||||
// Уведомление об создании заказа
|
|
||||||
const notifyService = new NotifyService(this.customer);
|
|
||||||
notifyService.orderCreated(variables, isCustomerMaster(customer));
|
|
||||||
|
|
||||||
return mutationResult.data;
|
return mutationResult.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,10 +131,6 @@ export class OrdersService extends BaseService {
|
|||||||
const error = mutationResult.errors?.at(0);
|
const error = mutationResult.errors?.at(0);
|
||||||
if (error) throw new Error(error.message);
|
if (error) throw new Error(error.message);
|
||||||
|
|
||||||
// Уведомление об изменении заказа
|
|
||||||
const notifyService = new NotifyService(this.customer);
|
|
||||||
notifyService.orderUpdated(variables);
|
|
||||||
|
|
||||||
return mutationResult.data;
|
return mutationResult.data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,6 @@
|
|||||||
"dayjs": "catalog:",
|
"dayjs": "catalog:",
|
||||||
"jsonwebtoken": "catalog:",
|
"jsonwebtoken": "catalog:",
|
||||||
"radashi": "catalog:",
|
"radashi": "catalog:",
|
||||||
"telegraf": "catalog:",
|
|
||||||
"vite-tsconfig-paths": "catalog:",
|
"vite-tsconfig-paths": "catalog:",
|
||||||
"vitest": "catalog:",
|
"vitest": "catalog:",
|
||||||
"zod": "catalog:"
|
"zod": "catalog:"
|
||||||
|
|||||||
@ -317,9 +317,7 @@ export type OrderFiltersInput = {
|
|||||||
not?: InputMaybe<OrderFiltersInput>;
|
not?: InputMaybe<OrderFiltersInput>;
|
||||||
or?: InputMaybe<Array<InputMaybe<OrderFiltersInput>>>;
|
or?: InputMaybe<Array<InputMaybe<OrderFiltersInput>>>;
|
||||||
order_number?: InputMaybe<IntFilterInput>;
|
order_number?: InputMaybe<IntFilterInput>;
|
||||||
price?: InputMaybe<IntFilterInput>;
|
|
||||||
publishedAt?: InputMaybe<DateTimeFilterInput>;
|
publishedAt?: InputMaybe<DateTimeFilterInput>;
|
||||||
service_description?: InputMaybe<StringFilterInput>;
|
|
||||||
services?: InputMaybe<ServiceFiltersInput>;
|
services?: InputMaybe<ServiceFiltersInput>;
|
||||||
slot?: InputMaybe<SlotFiltersInput>;
|
slot?: InputMaybe<SlotFiltersInput>;
|
||||||
state?: InputMaybe<StringFilterInput>;
|
state?: InputMaybe<StringFilterInput>;
|
||||||
@ -332,9 +330,7 @@ export type OrderInput = {
|
|||||||
datetime_end?: InputMaybe<Scalars['DateTime']['input']>;
|
datetime_end?: InputMaybe<Scalars['DateTime']['input']>;
|
||||||
datetime_start?: InputMaybe<Scalars['DateTime']['input']>;
|
datetime_start?: InputMaybe<Scalars['DateTime']['input']>;
|
||||||
order_number?: InputMaybe<Scalars['Int']['input']>;
|
order_number?: InputMaybe<Scalars['Int']['input']>;
|
||||||
price?: InputMaybe<Scalars['Int']['input']>;
|
|
||||||
publishedAt?: InputMaybe<Scalars['DateTime']['input']>;
|
publishedAt?: InputMaybe<Scalars['DateTime']['input']>;
|
||||||
service_description?: InputMaybe<Scalars['String']['input']>;
|
|
||||||
services?: InputMaybe<Array<InputMaybe<Scalars['ID']['input']>>>;
|
services?: InputMaybe<Array<InputMaybe<Scalars['ID']['input']>>>;
|
||||||
slot?: InputMaybe<Scalars['ID']['input']>;
|
slot?: InputMaybe<Scalars['ID']['input']>;
|
||||||
state?: InputMaybe<Enum_Order_State>;
|
state?: InputMaybe<Enum_Order_State>;
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
import { bot } from './telegram';
|
|
||||||
|
|
||||||
export async function notifyByTelegramId(telegramId: string | undefined, message: string) {
|
|
||||||
if (!telegramId) return;
|
|
||||||
|
|
||||||
await bot.telegram.sendMessage(telegramId, message, {
|
|
||||||
parse_mode: 'HTML',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
import { env as environment } from '../config/env';
|
|
||||||
import { Telegraf } from 'telegraf';
|
|
||||||
|
|
||||||
export const bot = new Telegraf(environment.BOT_TOKEN);
|
|
||||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@ -299,9 +299,6 @@ importers:
|
|||||||
radashi:
|
radashi:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 12.6.0
|
version: 12.6.0
|
||||||
telegraf:
|
|
||||||
specifier: 'catalog:'
|
|
||||||
version: 4.16.3
|
|
||||||
vite-tsconfig-paths:
|
vite-tsconfig-paths:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 5.1.4(typescript@5.8.3)(vite@5.4.19(@types/node@24.0.10))
|
version: 5.1.4(typescript@5.8.3)(vite@5.4.19(@types/node@24.0.10))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user