feat: status updating on the server and statuses fetching

This commit is contained in:
2025-08-04 18:49:27 +04:00
parent 24de9f5446
commit c98a5cc811
9 changed files with 277 additions and 26 deletions

View File

@ -2,7 +2,6 @@
import React from "react"; import React from "react";
import { useMutation } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import { AxiosError } from "axios";
import { ScrollArea } from "@mantine/core"; import { ScrollArea } from "@mantine/core";
import Board from "@/app/deals/components/Board/Board"; import Board from "@/app/deals/components/Board/Board";
import { useBoardsContext } from "@/app/deals/contexts/BoardsContext"; import { useBoardsContext } from "@/app/deals/contexts/BoardsContext";
@ -10,17 +9,16 @@ import { BoardSchema } from "@/client";
import { updateBoardMutation } from "@/client/@tanstack/react-query.gen"; import { updateBoardMutation } from "@/client/@tanstack/react-query.gen";
import SortableDnd from "@/components/SortableDnd"; import SortableDnd from "@/components/SortableDnd";
import { notifications } from "@/lib/notifications"; import { notifications } from "@/lib/notifications";
import { ErrorBody } from "@/types/ErrorBody";
const Boards = () => { const Boards = () => {
const { boards, setSelectedBoard, refetchBoards } = useBoardsContext(); const { boards, setSelectedBoard, refetchBoards } = useBoardsContext();
const updateBoard = useMutation({ const updateBoard = useMutation({
...updateBoardMutation(), ...updateBoardMutation(),
onError: (error: AxiosError<ErrorBody>) => { onError: error => {
console.error(error); console.error(error);
notifications.error({ notifications.error({
message: error.response?.data?.detail, message: error.response?.data?.detail as string | undefined,
}); });
refetchBoards(); refetchBoards();
}, },

View File

@ -1,8 +1,25 @@
"use client"; "use client";
import { useMutation } from "@tanstack/react-query";
import StatusColumnsDnd from "@/app/deals/components/StatusColumnsDnd/StatusColumnsDnd"; 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 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 = ( const onDealDragEnd = (
dealId: number, dealId: number,
statusId: 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 ( return (
<StatusColumnsDnd <StatusColumnsDnd

View File

@ -1,21 +1,17 @@
"use client"; "use client";
import React, { import React, { createContext, FC, useContext, useEffect } from "react";
createContext,
FC,
useContext,
useEffect,
useState,
} from "react";
import { useBoardsContext } from "@/app/deals/contexts/BoardsContext"; import { useBoardsContext } from "@/app/deals/contexts/BoardsContext";
import { DealSchema, StatusSchema } from "@/client"; import { DealSchema, StatusSchema } from "@/client";
import useDealsList from "@/hooks/useDealsList"; import useDealsList from "@/hooks/useDealsList";
import useStatusesList from "@/hooks/useStatusesList";
type StatusesContextState = { type StatusesContextState = {
statuses: StatusSchema[]; statuses: StatusSchema[];
setStatuses: React.Dispatch<React.SetStateAction<StatusSchema[]>>; setStatuses: React.Dispatch<React.SetStateAction<StatusSchema[]>>;
deals: DealSchema[]; deals: DealSchema[];
setDeals: React.Dispatch<React.SetStateAction<DealSchema[]>>; setDeals: React.Dispatch<React.SetStateAction<DealSchema[]>>;
refetchStatuses: () => void;
}; };
const StatusesContext = createContext<StatusesContextState | undefined>( const StatusesContext = createContext<StatusesContextState | undefined>(
@ -23,12 +19,14 @@ const StatusesContext = createContext<StatusesContextState | undefined>(
); );
const useStatusesContextState = () => { const useStatusesContextState = () => {
const [statuses, setStatuses] = useState<StatusSchema[]>([]);
const { selectedBoard } = useBoardsContext(); const { selectedBoard } = useBoardsContext();
const { statuses, setStatuses, refetch } = useStatusesList({
boardId: selectedBoard?.id,
});
const { deals, setDeals } = useDealsList({ boardId: selectedBoard?.id }); const { deals, setDeals } = useDealsList({ boardId: selectedBoard?.id });
useEffect(() => { useEffect(() => {
setStatuses(selectedBoard?.statuses ?? []); refetch();
}, [selectedBoard]); }, [selectedBoard]);
return { return {
@ -36,6 +34,7 @@ const useStatusesContextState = () => {
setStatuses, setStatuses,
deals, deals,
setDeals, setDeals,
refetchStatuses: refetch,
}; };
}; };

View File

@ -175,7 +175,7 @@ const useDnd = (props: Props) => {
overStatusId = deal.statusId; overStatusId = deal.statusId;
} }
if (!overStatusId || activeStatusId === overStatusId) return; if (!overStatusId) return;
const newRank = getNewStatusRank(activeStatusId, overStatusId); const newRank = getNewStatusRank(activeStatusId, overStatusId);
if (!newRank) return; if (!newRank) return;

View File

@ -7,16 +7,22 @@ import {
getBoards, getBoards,
getDeals, getDeals,
getProjects, getProjects,
getStatuses,
updateBoard, updateBoard,
updateStatus,
type Options, type Options,
} from "../sdk.gen"; } from "../sdk.gen";
import type { import type {
GetBoardsData, GetBoardsData,
GetDealsData, GetDealsData,
GetProjectsData, GetProjectsData,
GetStatusesData,
UpdateBoardData, UpdateBoardData,
UpdateBoardError, UpdateBoardError,
UpdateBoardResponse2, UpdateBoardResponse2,
UpdateStatusData,
UpdateStatusError,
UpdateStatusResponse2,
} from "../types.gen"; } from "../types.gen";
export type QueryKey<TOptions extends Options> = [ export type QueryKey<TOptions extends Options> = [
@ -124,6 +130,54 @@ export const updateBoardMutation = (
return mutationOptions; return mutationOptions;
}; };
export const getStatusesQueryKey = (options: Options<GetStatusesData>) =>
createQueryKey("getStatuses", options);
/**
* Get Statuses
*/
export const getStatusesOptions = (options: Options<GetStatusesData>) => {
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<Options<UpdateStatusData>>
): UseMutationOptions<
UpdateStatusResponse2,
AxiosError<UpdateStatusError>,
Options<UpdateStatusData>
> => {
const mutationOptions: UseMutationOptions<
UpdateStatusResponse2,
AxiosError<UpdateStatusError>,
Options<UpdateStatusData>
> = {
mutationFn: async localOptions => {
const { data } = await updateStatus({
...options,
...localOptions,
throwOnError: true,
});
return data;
},
};
return mutationOptions;
};
export const getDealsQueryKey = (options: Options<GetDealsData>) => export const getDealsQueryKey = (options: Options<GetDealsData>) =>
createQueryKey("getDeals", options); createQueryKey("getDeals", options);

View File

@ -11,9 +11,15 @@ import type {
GetDealsResponses, GetDealsResponses,
GetProjectsData, GetProjectsData,
GetProjectsResponses, GetProjectsResponses,
GetStatusesData,
GetStatusesErrors,
GetStatusesResponses,
UpdateBoardData, UpdateBoardData,
UpdateBoardErrors, UpdateBoardErrors,
UpdateBoardResponses, UpdateBoardResponses,
UpdateStatusData,
UpdateStatusErrors,
UpdateStatusResponses,
} from "./types.gen"; } from "./types.gen";
export type Options< export type Options<
@ -88,6 +94,44 @@ export const updateBoard = <ThrowOnError extends boolean = false>(
}); });
}; };
/**
* Get Statuses
*/
export const getStatuses = <ThrowOnError extends boolean = false>(
options: Options<GetStatusesData, ThrowOnError>
) => {
return (options.client ?? _heyApiClient).get<
GetStatusesResponses,
GetStatusesErrors,
ThrowOnError
>({
responseType: "json",
url: "/status/{boardId}",
...options,
});
};
/**
* Update Status
*/
export const updateStatus = <ThrowOnError extends boolean = false>(
options: Options<UpdateStatusData, ThrowOnError>
) => {
return (options.client ?? _heyApiClient).patch<
UpdateStatusResponses,
UpdateStatusErrors,
ThrowOnError
>({
responseType: "json",
url: "/status/{statusId}",
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
});
};
/** /**
* Get Deals * Get Deals
*/ */

View File

@ -16,10 +16,6 @@ export type BoardSchema = {
* Lexorank * Lexorank
*/ */
lexorank: string; lexorank: string;
/**
* Statuses
*/
statuses: Array<StatusSchema>;
}; };
/** /**
@ -74,6 +70,16 @@ export type GetProjectsResponse = {
projects: Array<ProjectSchema>; projects: Array<ProjectSchema>;
}; };
/**
* GetStatusesResponse
*/
export type GetStatusesResponse = {
/**
* Statuses
*/
statuses: Array<StatusSchema>;
};
/** /**
* HTTPValidationError * HTTPValidationError
*/ */
@ -147,6 +153,37 @@ export type UpdateBoardSchema = {
lexorank?: string | null; 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 * ValidationError
*/ */
@ -225,10 +262,6 @@ export type UpdateBoardData = {
}; };
export type UpdateBoardErrors = { export type UpdateBoardErrors = {
/**
* Item not found
*/
404: unknown;
/** /**
* Validation Error * Validation Error
*/ */
@ -247,6 +280,68 @@ export type UpdateBoardResponses = {
export type UpdateBoardResponse2 = export type UpdateBoardResponse2 =
UpdateBoardResponses[keyof UpdateBoardResponses]; 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 = { export type GetDealsData = {
body?: never; body?: never;
path: { path: {

View File

@ -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<StatusSchema[]>([]);
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;

View File

@ -1,3 +0,0 @@
export type ErrorBody = {
detail?: string;
};