From c98a5cc811bb262942cc44328d91b7e2cdfc79d1 Mon Sep 17 00:00:00 2001 From: AlexSserb Date: Mon, 4 Aug 2025 18:49:27 +0400 Subject: [PATCH] feat: status updating on the server and statuses fetching --- src/app/deals/components/Boards/Boards.tsx | 6 +- .../StatusColumns/StatusColumns.tsx | 37 +++++- src/app/deals/contexts/StatusesContext.tsx | 17 ++- src/app/deals/hooks/useDnd.ts | 2 +- src/client/@tanstack/react-query.gen.ts | 54 +++++++++ src/client/sdk.gen.ts | 44 +++++++ src/client/types.gen.ts | 111 ++++++++++++++++-- src/hooks/useStatusesList.ts | 29 +++++ src/types/ErrorBody.ts | 3 - 9 files changed, 277 insertions(+), 26 deletions(-) create mode 100644 src/hooks/useStatusesList.ts delete mode 100644 src/types/ErrorBody.ts diff --git a/src/app/deals/components/Boards/Boards.tsx b/src/app/deals/components/Boards/Boards.tsx index ced04d4..585a363 100644 --- a/src/app/deals/components/Boards/Boards.tsx +++ b/src/app/deals/components/Boards/Boards.tsx @@ -2,7 +2,6 @@ import React from "react"; import { useMutation } from "@tanstack/react-query"; -import { AxiosError } from "axios"; import { ScrollArea } from "@mantine/core"; import Board from "@/app/deals/components/Board/Board"; import { useBoardsContext } from "@/app/deals/contexts/BoardsContext"; @@ -10,17 +9,16 @@ import { BoardSchema } from "@/client"; import { updateBoardMutation } from "@/client/@tanstack/react-query.gen"; import SortableDnd from "@/components/SortableDnd"; import { notifications } from "@/lib/notifications"; -import { ErrorBody } from "@/types/ErrorBody"; const Boards = () => { const { boards, setSelectedBoard, refetchBoards } = useBoardsContext(); const updateBoard = useMutation({ ...updateBoardMutation(), - onError: (error: AxiosError) => { + onError: error => { console.error(error); notifications.error({ - message: error.response?.data?.detail, + message: error.response?.data?.detail as string | undefined, }); refetchBoards(); }, diff --git a/src/app/deals/components/StatusColumns/StatusColumns.tsx b/src/app/deals/components/StatusColumns/StatusColumns.tsx index 6b7a9c2..bd3f6a3 100644 --- a/src/app/deals/components/StatusColumns/StatusColumns.tsx +++ b/src/app/deals/components/StatusColumns/StatusColumns.tsx @@ -1,8 +1,25 @@ "use client"; +import { useMutation } from "@tanstack/react-query"; import StatusColumnsDnd from "@/app/deals/components/StatusColumnsDnd/StatusColumnsDnd"; +import { useStatusesContext } from "@/app/deals/contexts/StatusesContext"; +import { updateStatusMutation } from "@/client/@tanstack/react-query.gen"; +import { notifications } from "@/lib/notifications"; const StatusColumns = () => { + const { refetchStatuses } = useStatusesContext(); + + const updateStatus = useMutation({ + ...updateStatusMutation(), + onError: error => { + console.error(error); + notifications.error({ + message: error.response?.data?.detail as string | undefined, + }); + refetchStatuses(); + }, + }); + const onDealDragEnd = ( dealId: number, statusId: number, @@ -19,7 +36,25 @@ const StatusColumns = () => { ); }; - const onStatusDragEnd = (statusId: number, lexorank: string) => {}; + const onStatusDragEnd = (statusId: number, lexorank: string) => { + console.log( + "onStatusDragEnd, statusId:", + statusId, + ", lexo:", + lexorank + ); + + updateStatus.mutate({ + path: { + statusId, + }, + body: { + status: { + lexorank, + }, + }, + }); + }; return ( >; deals: DealSchema[]; setDeals: React.Dispatch>; + refetchStatuses: () => void; }; const StatusesContext = createContext( @@ -23,12 +19,14 @@ const StatusesContext = createContext( ); const useStatusesContextState = () => { - const [statuses, setStatuses] = useState([]); const { selectedBoard } = useBoardsContext(); + const { statuses, setStatuses, refetch } = useStatusesList({ + boardId: selectedBoard?.id, + }); const { deals, setDeals } = useDealsList({ boardId: selectedBoard?.id }); useEffect(() => { - setStatuses(selectedBoard?.statuses ?? []); + refetch(); }, [selectedBoard]); return { @@ -36,6 +34,7 @@ const useStatusesContextState = () => { setStatuses, deals, setDeals, + refetchStatuses: refetch, }; }; diff --git a/src/app/deals/hooks/useDnd.ts b/src/app/deals/hooks/useDnd.ts index cbf68a2..7829729 100644 --- a/src/app/deals/hooks/useDnd.ts +++ b/src/app/deals/hooks/useDnd.ts @@ -175,7 +175,7 @@ const useDnd = (props: Props) => { overStatusId = deal.statusId; } - if (!overStatusId || activeStatusId === overStatusId) return; + if (!overStatusId) return; const newRank = getNewStatusRank(activeStatusId, overStatusId); if (!newRank) return; diff --git a/src/client/@tanstack/react-query.gen.ts b/src/client/@tanstack/react-query.gen.ts index 08ff511..99840d2 100644 --- a/src/client/@tanstack/react-query.gen.ts +++ b/src/client/@tanstack/react-query.gen.ts @@ -7,16 +7,22 @@ import { getBoards, getDeals, getProjects, + getStatuses, updateBoard, + updateStatus, type Options, } from "../sdk.gen"; import type { GetBoardsData, GetDealsData, GetProjectsData, + GetStatusesData, UpdateBoardData, UpdateBoardError, UpdateBoardResponse2, + UpdateStatusData, + UpdateStatusError, + UpdateStatusResponse2, } from "../types.gen"; export type QueryKey = [ @@ -124,6 +130,54 @@ export const updateBoardMutation = ( return mutationOptions; }; +export const getStatusesQueryKey = (options: Options) => + createQueryKey("getStatuses", options); + +/** + * Get Statuses + */ +export const getStatusesOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getStatuses({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: getStatusesQueryKey(options), + }); +}; + +/** + * Update Status + */ +export const updateStatusMutation = ( + options?: Partial> +): UseMutationOptions< + UpdateStatusResponse2, + AxiosError, + Options +> => { + const mutationOptions: UseMutationOptions< + UpdateStatusResponse2, + AxiosError, + Options + > = { + mutationFn: async localOptions => { + const { data } = await updateStatus({ + ...options, + ...localOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + export const getDealsQueryKey = (options: Options) => createQueryKey("getDeals", options); diff --git a/src/client/sdk.gen.ts b/src/client/sdk.gen.ts index 9be8e4e..902c094 100644 --- a/src/client/sdk.gen.ts +++ b/src/client/sdk.gen.ts @@ -11,9 +11,15 @@ import type { GetDealsResponses, GetProjectsData, GetProjectsResponses, + GetStatusesData, + GetStatusesErrors, + GetStatusesResponses, UpdateBoardData, UpdateBoardErrors, UpdateBoardResponses, + UpdateStatusData, + UpdateStatusErrors, + UpdateStatusResponses, } from "./types.gen"; export type Options< @@ -88,6 +94,44 @@ export const updateBoard = ( }); }; +/** + * Get Statuses + */ +export const getStatuses = ( + options: Options +) => { + return (options.client ?? _heyApiClient).get< + GetStatusesResponses, + GetStatusesErrors, + ThrowOnError + >({ + responseType: "json", + url: "/status/{boardId}", + ...options, + }); +}; + +/** + * Update Status + */ +export const updateStatus = ( + options: Options +) => { + return (options.client ?? _heyApiClient).patch< + UpdateStatusResponses, + UpdateStatusErrors, + ThrowOnError + >({ + responseType: "json", + url: "/status/{statusId}", + ...options, + headers: { + "Content-Type": "application/json", + ...options.headers, + }, + }); +}; + /** * Get Deals */ diff --git a/src/client/types.gen.ts b/src/client/types.gen.ts index 7ac4861..23742e6 100644 --- a/src/client/types.gen.ts +++ b/src/client/types.gen.ts @@ -16,10 +16,6 @@ export type BoardSchema = { * Lexorank */ lexorank: string; - /** - * Statuses - */ - statuses: Array; }; /** @@ -74,6 +70,16 @@ export type GetProjectsResponse = { projects: Array; }; +/** + * GetStatusesResponse + */ +export type GetStatusesResponse = { + /** + * Statuses + */ + statuses: Array; +}; + /** * HTTPValidationError */ @@ -147,6 +153,37 @@ export type UpdateBoardSchema = { lexorank?: string | null; }; +/** + * UpdateStatusRequest + */ +export type UpdateStatusRequest = { + status: UpdateStatusSchema; +}; + +/** + * UpdateStatusResponse + */ +export type UpdateStatusResponse = { + /** + * Message + */ + message: string; +}; + +/** + * UpdateStatusSchema + */ +export type UpdateStatusSchema = { + /** + * Name + */ + name?: string | null; + /** + * Lexorank + */ + lexorank?: string | null; +}; + /** * ValidationError */ @@ -225,10 +262,6 @@ export type UpdateBoardData = { }; export type UpdateBoardErrors = { - /** - * Item not found - */ - 404: unknown; /** * Validation Error */ @@ -247,6 +280,68 @@ export type UpdateBoardResponses = { export type UpdateBoardResponse2 = UpdateBoardResponses[keyof UpdateBoardResponses]; +export type GetStatusesData = { + body?: never; + path: { + /** + * Boardid + */ + boardId: number; + }; + query?: never; + url: "/status/{boardId}"; +}; + +export type GetStatusesErrors = { + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type GetStatusesError = GetStatusesErrors[keyof GetStatusesErrors]; + +export type GetStatusesResponses = { + /** + * Successful Response + */ + 200: GetStatusesResponse; +}; + +export type GetStatusesResponse2 = + GetStatusesResponses[keyof GetStatusesResponses]; + +export type UpdateStatusData = { + body: UpdateStatusRequest; + path: { + /** + * Statusid + */ + statusId: number; + }; + query?: never; + url: "/status/{statusId}"; +}; + +export type UpdateStatusErrors = { + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type UpdateStatusError = UpdateStatusErrors[keyof UpdateStatusErrors]; + +export type UpdateStatusResponses = { + /** + * Successful Response + */ + 200: UpdateStatusResponse; +}; + +export type UpdateStatusResponse2 = + UpdateStatusResponses[keyof UpdateStatusResponses]; + export type GetDealsData = { body?: never; path: { diff --git a/src/hooks/useStatusesList.ts b/src/hooks/useStatusesList.ts new file mode 100644 index 0000000..1fd8e94 --- /dev/null +++ b/src/hooks/useStatusesList.ts @@ -0,0 +1,29 @@ +import { useEffect, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { StatusSchema } from "@/client"; +import { getStatusesOptions } from "@/client/@tanstack/react-query.gen"; + +type Props = { + boardId?: number; +}; + +const useStatusesList = ({ boardId }: Props) => { + const [statuses, setStatuses] = useState([]); + + const { data, refetch, isLoading } = useQuery({ + ...getStatusesOptions({ path: { boardId: boardId! } }), + enabled: boardId !== undefined, + }); + + useEffect(() => { + if (boardId === undefined) { + setStatuses([]); + } else if (data?.statuses) { + setStatuses(data.statuses); + } + }, [data?.statuses, boardId]); + + return { statuses, setStatuses, refetch, isLoading }; +}; + +export default useStatusesList; diff --git a/src/types/ErrorBody.ts b/src/types/ErrorBody.ts deleted file mode 100644 index dfc1c40..0000000 --- a/src/types/ErrorBody.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type ErrorBody = { - detail?: string; -};