diff --git a/src/app/deals/components/Board/Board.tsx b/src/app/deals/components/Board/Board.tsx index bb61c0d..df4863f 100644 --- a/src/app/deals/components/Board/Board.tsx +++ b/src/app/deals/components/Board/Board.tsx @@ -1,6 +1,6 @@ import React, { FC } from "react"; import { Box } from "@mantine/core"; -import { BoardSchema } from "@/types/BoardSchema"; +import { BoardSchema } from "@/client"; type Props = { board: BoardSchema; diff --git a/src/app/deals/components/Boards/Boards.tsx b/src/app/deals/components/Boards/Boards.tsx index e570612..bd7e954 100644 --- a/src/app/deals/components/Boards/Boards.tsx +++ b/src/app/deals/components/Boards/Boards.tsx @@ -4,11 +4,11 @@ import React from "react"; import { ScrollArea } from "@mantine/core"; import Board from "@/app/deals/components/Board/Board"; import { useBoardsContext } from "@/app/deals/contexts/BoardsContext"; +import { BoardSchema } from "@/client"; import SortableDnd from "@/components/SortableDnd"; -import { BoardSchema } from "@/types/BoardSchema"; const Boards = () => { - const { boards } = useBoardsContext(); + const { boards, setSelectedBoard } = useBoardsContext(); const renderBoard = (board: BoardSchema) => { return ; @@ -18,6 +18,11 @@ const Boards = () => { console.log("onDragEnd:", itemId, newLexorank); }; + const selectBoard = (board: BoardSchema) => { + console.log("Board selecting:", board); + setSelectedBoard(board); + }; + return ( { initialItems={boards} renderItem={renderBoard} onDragEnd={onDragEnd} + onItemClick={selectBoard} rowStyle={{ flexWrap: "nowrap" }} /> diff --git a/src/app/deals/components/DndOverlay/DndOverlay.tsx b/src/app/deals/components/DndOverlay/DndOverlay.tsx index a431224..ba6006b 100644 --- a/src/app/deals/components/DndOverlay/DndOverlay.tsx +++ b/src/app/deals/components/DndOverlay/DndOverlay.tsx @@ -4,7 +4,7 @@ import DealCard from "@/app/deals/components/DealCard/DealCard"; import StatusColumn from "@/app/deals/components/StatusColumn/StatusColumn"; import { useStatusesContext } from "@/app/deals/contexts/StatusesContext"; import { DealSchema } from "@/types/DealSchema"; -import { StatusSchema } from "@/types/StatusSchema"; +import { StatusSchema } from "@/client"; type Props = { activeDeal: DealSchema | null; diff --git a/src/app/deals/components/StatusColumn/StatusColumn.tsx b/src/app/deals/components/StatusColumn/StatusColumn.tsx index d1199c2..380f3f3 100644 --- a/src/app/deals/components/StatusColumn/StatusColumn.tsx +++ b/src/app/deals/components/StatusColumn/StatusColumn.tsx @@ -6,8 +6,8 @@ import { } from "@dnd-kit/sortable"; import { Box, Stack, Text } from "@mantine/core"; import DealContainer from "@/app/deals/components/DealContainer/DealContainer"; +import { StatusSchema } from "@/client"; import { DealSchema } from "@/types/DealSchema"; -import { StatusSchema } from "@/types/StatusSchema"; import { sortByLexorank } from "@/utils/lexorank"; type Props = { diff --git a/src/app/deals/contexts/BoardsContext.tsx b/src/app/deals/contexts/BoardsContext.tsx index afdc0e6..d913e61 100644 --- a/src/app/deals/contexts/BoardsContext.tsx +++ b/src/app/deals/contexts/BoardsContext.tsx @@ -8,8 +8,8 @@ import React, { useState, } from "react"; import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext"; -import useBoards from "@/app/deals/hooks/useBoards"; -import { BoardSchema } from "@/types/BoardSchema"; +import { BoardSchema } from "@/client"; +import useBoardsList from "@/hooks/useBoardsList"; type BoardsContextState = { boards: BoardSchema[]; @@ -21,11 +21,11 @@ type BoardsContextState = { const BoardsContext = createContext(undefined); const useBoardsContextState = () => { - const { boards, setBoards } = useBoards(); + const { selectedProject: project } = useProjectsContext(); + const { boards, setBoards } = useBoardsList({ projectId: project?.id }); const [selectedBoard, setSelectedBoard] = useState( null ); - const { selectedProject: project } = useProjectsContext(); useEffect(() => { if (boards.length > 0 && selectedBoard === null) { diff --git a/src/app/deals/contexts/StatusesContext.tsx b/src/app/deals/contexts/StatusesContext.tsx index 3d8f8a9..3693199 100644 --- a/src/app/deals/contexts/StatusesContext.tsx +++ b/src/app/deals/contexts/StatusesContext.tsx @@ -1,11 +1,16 @@ "use client"; -import React, { createContext, FC, useContext } from "react"; +import React, { + createContext, + FC, + useContext, + useEffect, + useState, +} from "react"; import { useBoardsContext } from "@/app/deals/contexts/BoardsContext"; import useDeals from "@/app/deals/hooks/useDeals"; -import useStatuses from "@/app/deals/hooks/useStatuses"; +import { StatusSchema } from "@/client"; import { DealSchema } from "@/types/DealSchema"; -import { StatusSchema } from "@/types/StatusSchema"; type StatusesContextState = { statuses: StatusSchema[]; @@ -19,10 +24,14 @@ const StatusesContext = createContext( ); const useStatusesContextState = () => { - const { statuses, setStatuses } = useStatuses(); + const [statuses, setStatuses] = useState([]); const { deals, setDeals } = useDeals(); const { selectedBoard } = useBoardsContext(); + useEffect(() => { + setStatuses(selectedBoard?.statuses ?? []); + }, [selectedBoard]); + return { statuses, setStatuses, diff --git a/src/app/deals/hooks/useBoards.ts b/src/app/deals/hooks/useBoards.ts index c8b69c8..b475145 100644 --- a/src/app/deals/hooks/useBoards.ts +++ b/src/app/deals/hooks/useBoards.ts @@ -1,19 +1,9 @@ -import { useEffect, useState } from "react"; -import { BoardSchema } from "@/types/BoardSchema"; +import { useState } from "react"; +import { BoardSchema } from "@/client"; const useBoards = () => { const [boards, setBoards] = useState([]); - useEffect(() => { - setBoards([ - { id: 1, name: "1 Item", rank: "0|aaaaaa:" }, - { id: 2, name: "2 Item", rank: "0|gggggg:" }, - { id: 3, name: "3 Item", rank: "0|mmmmmm:" }, - { id: 4, name: "4 Item", rank: "0|ssssss:" }, - { id: 5, name: "5 Item", rank: "0|zzzzzz:" }, - ]); - }, []); - return { boards, setBoards, diff --git a/src/app/deals/hooks/useDeals.ts b/src/app/deals/hooks/useDeals.ts index c1f59a7..3708b54 100644 --- a/src/app/deals/hooks/useDeals.ts +++ b/src/app/deals/hooks/useDeals.ts @@ -9,19 +9,19 @@ const useDeals = () => { { id: 1, name: "Deal 1", - rank: "0|gggggg:", + lexorank: "0|gggggg:", statusId: 1, }, { id: 2, name: "Deal 2", - rank: "0|mmmmmm:", + lexorank: "0|mmmmmm:", statusId: 1, }, { id: 3, name: "Deal 3", - rank: "0|ssssss:", + lexorank: "0|ssssss:", statusId: 2, }, ]; diff --git a/src/app/deals/hooks/useDnd.ts b/src/app/deals/hooks/useDnd.ts index 00d3206..d30e49f 100644 --- a/src/app/deals/hooks/useDnd.ts +++ b/src/app/deals/hooks/useDnd.ts @@ -4,8 +4,8 @@ import { throttle } from "lodash"; import { useStatusesContext } from "@/app/deals/contexts/StatusesContext"; import useGetNewRank from "@/app/deals/hooks/useGetNewRank"; import { getStatusId, isStatusId } from "@/app/deals/utils/statusId"; +import { StatusSchema } from "@/client"; import { DealSchema } from "@/types/DealSchema"; -import { StatusSchema } from "@/types/StatusSchema"; import { sortByLexorank } from "@/utils/lexorank"; type Props = { @@ -73,7 +73,7 @@ const useDnd = (props: Props) => { ? { ...deal, statusId: overStatusId, - rank: newLexorank || deal.rank, + lexorank: newLexorank || deal.lexorank, } : deal ) @@ -100,7 +100,7 @@ const useDnd = (props: Props) => { throttledSetStatuses(statuses => statuses.map(status => status.id === activeStatusId - ? { ...status, rank: newRank } + ? { ...status, lexorank: newRank } : status ) ); diff --git a/src/app/deals/hooks/useGetNewRank.ts b/src/app/deals/hooks/useGetNewRank.ts index da86b02..3067aff 100644 --- a/src/app/deals/hooks/useGetNewRank.ts +++ b/src/app/deals/hooks/useGetNewRank.ts @@ -20,10 +20,10 @@ const useGetNewRank = () => { : [overDealIndex, overDealIndex + 1]; const leftLexorank = - leftIndex >= 0 ? LexoRank.parse(deals[leftIndex].rank) : null; + leftIndex >= 0 ? LexoRank.parse(deals[leftIndex].lexorank) : null; const rightLexorank = rightIndex < deals.length - ? LexoRank.parse(deals[rightIndex].rank) + ? LexoRank.parse(deals[rightIndex].lexorank) : null; return getNewLexorank(leftLexorank, rightLexorank).toString(); @@ -35,9 +35,11 @@ const useGetNewRank = () => { ) => { const leftLexorank = overDealIndex > 0 - ? LexoRank.parse(statusDeals[overDealIndex - 1].rank) + ? LexoRank.parse(statusDeals[overDealIndex - 1].lexorank) : null; - const rightLexorank = LexoRank.parse(statusDeals[overDealIndex].rank); + const rightLexorank = LexoRank.parse( + statusDeals[overDealIndex].lexorank + ); return getNewLexorank(leftLexorank, rightLexorank).toString(); }; @@ -59,10 +61,12 @@ const useGetNewRank = () => { : [overIndex, overIndex + 1]; const leftLexorank = - leftIndex >= 0 ? LexoRank.parse(statuses[leftIndex].rank) : null; + leftIndex >= 0 + ? LexoRank.parse(statuses[leftIndex].lexorank) + : null; const rightLexorank = rightIndex < statuses.length - ? LexoRank.parse(statuses[rightIndex].rank) + ? LexoRank.parse(statuses[rightIndex].lexorank) : null; return getNewLexorank(leftLexorank, rightLexorank).toString(); diff --git a/src/app/deals/hooks/useStatuses.ts b/src/app/deals/hooks/useStatuses.ts deleted file mode 100644 index 7c3def4..0000000 --- a/src/app/deals/hooks/useStatuses.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { useEffect, useState } from "react"; -import { StatusSchema } from "@/types/StatusSchema"; - -const useStatuses = () => { - const [statuses, setStatuses] = useState([]); - - useEffect(() => { - setStatuses([ - { id: 1, name: "Todo", rank: "0|aaaaaa:" }, - { id: 2, name: "In progress", rank: "0|gggggg:" }, - { id: 3, name: "Testing", rank: "0|mmmmmm:" }, - { id: 4, name: "Ready", rank: "0|ssssss:" }, - ]); - }, []); - - return { - statuses, - setStatuses, - }; -}; - -export default useStatuses; diff --git a/src/client/@tanstack/react-query.gen.ts b/src/client/@tanstack/react-query.gen.ts index a630ccb..5ce22f1 100644 --- a/src/client/@tanstack/react-query.gen.ts +++ b/src/client/@tanstack/react-query.gen.ts @@ -2,8 +2,8 @@ import { queryOptions } from "@tanstack/react-query"; import { client as _heyApiClient } from "../client.gen"; -import { getProjects, type Options } from "../sdk.gen"; -import type { GetProjectsData } from "../types.gen"; +import { getBoards, getProjects, type Options } from "../sdk.gen"; +import type { GetBoardsData, GetProjectsData } from "../types.gen"; export type QueryKey = [ Pick & { @@ -61,3 +61,24 @@ export const getProjectsOptions = (options?: Options) => { queryKey: getProjectsQueryKey(options), }); }; + +export const getBoardsQueryKey = (options: Options) => + createQueryKey("getBoards", options); + +/** + * Get Boards + */ +export const getBoardsOptions = (options: Options) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getBoards({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: getBoardsQueryKey(options), + }); +}; diff --git a/src/client/sdk.gen.ts b/src/client/sdk.gen.ts index 22a0ae4..6cada35 100644 --- a/src/client/sdk.gen.ts +++ b/src/client/sdk.gen.ts @@ -2,7 +2,13 @@ import type { Client, Options as ClientOptions, TDataShape } from "./client"; import { client as _heyApiClient } from "./client.gen"; -import type { GetProjectsData, GetProjectsResponses } from "./types.gen"; +import type { + GetBoardsData, + GetBoardsErrors, + GetBoardsResponses, + GetProjectsData, + GetProjectsResponses, +} from "./types.gen"; export type Options< TData extends TDataShape = TDataShape, @@ -37,3 +43,20 @@ export const getProjects = ( ...options, }); }; + +/** + * Get Boards + */ +export const getBoards = ( + options: Options +) => { + return (options.client ?? _heyApiClient).get< + GetBoardsResponses, + GetBoardsErrors, + ThrowOnError + >({ + responseType: "json", + url: "/board/{project_id}", + ...options, + }); +}; diff --git a/src/client/types.gen.ts b/src/client/types.gen.ts index 4ad913d..88f77d8 100644 --- a/src/client/types.gen.ts +++ b/src/client/types.gen.ts @@ -1,5 +1,37 @@ // This file is auto-generated by @hey-api/openapi-ts +/** + * BoardSchema + */ +export type BoardSchema = { + /** + * Name + */ + name: string; + /** + * Id + */ + id: number; + /** + * Lexorank + */ + lexorank: string; + /** + * Statuses + */ + statuses: Array; +}; + +/** + * GetBoardsResponse + */ +export type GetBoardsResponse = { + /** + * Boards + */ + boards: Array; +}; + /** * GetProjectsResponse */ @@ -10,6 +42,16 @@ export type GetProjectsResponse = { projects: Array; }; +/** + * HTTPValidationError + */ +export type HttpValidationError = { + /** + * Detail + */ + detail?: Array; +}; + /** * ProjectSchema */ @@ -24,6 +66,42 @@ export type ProjectSchema = { id: number; }; +/** + * StatusSchema + */ +export type StatusSchema = { + /** + * Name + */ + name: string; + /** + * Id + */ + id: number; + /** + * Lexorank + */ + lexorank: string; +}; + +/** + * ValidationError + */ +export type ValidationError = { + /** + * Location + */ + loc: Array; + /** + * Message + */ + msg: string; + /** + * Error Type + */ + type: string; +}; + export type GetProjectsData = { body?: never; path?: never; @@ -41,6 +119,36 @@ export type GetProjectsResponses = { export type GetProjectsResponse2 = GetProjectsResponses[keyof GetProjectsResponses]; +export type GetBoardsData = { + body?: never; + path: { + /** + * Project Id + */ + project_id: number; + }; + query?: never; + url: "/board/{project_id}"; +}; + +export type GetBoardsErrors = { + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type GetBoardsError = GetBoardsErrors[keyof GetBoardsErrors]; + +export type GetBoardsResponses = { + /** + * Successful Response + */ + 200: GetBoardsResponse; +}; + +export type GetBoardsResponse2 = GetBoardsResponses[keyof GetBoardsResponses]; + export type ClientOptions = { baseURL: "http://localhost:8000" | (string & {}); }; diff --git a/src/components/SortableDnd/SortableDnd.tsx b/src/components/SortableDnd/SortableDnd.tsx index 5de115d..bc08877 100644 --- a/src/components/SortableDnd/SortableDnd.tsx +++ b/src/components/SortableDnd/SortableDnd.tsx @@ -7,34 +7,25 @@ import React, { useMemo, useState, } from "react"; -import { - Active, - DndContext, - DragEndEvent, - KeyboardSensor, - PointerSensor, - useSensor, - useSensors, -} from "@dnd-kit/core"; -import { - SortableContext, - sortableKeyboardCoordinates, -} from "@dnd-kit/sortable"; +import { Active, DndContext, DragEndEvent } from "@dnd-kit/core"; +import { SortableContext } from "@dnd-kit/sortable"; import { LexoRank } from "lexorank"; -import { Group } from "@mantine/core"; +import { Box, Group } from "@mantine/core"; +import useDndSensors from "@/app/deals/hooks/useSensors"; import { SortableItem } from "@/components/SortableDnd/SortableItem"; import { SortableOverlay } from "@/components/SortableDnd/SortableOverlay"; import { getNewLexorank, sortByLexorank } from "@/utils/lexorank"; type BaseItem = { id: number; - rank: string; + lexorank: string; }; type Props = { initialItems: T[]; renderItem: (item: T) => ReactNode; onDragEnd: (itemId: number, newLexorank: string) => void; + onItemClick: (item: T) => void; rowStyle?: CSSProperties; itemStyle?: CSSProperties; }; @@ -43,6 +34,7 @@ const SortableDnd = ({ initialItems, renderItem, onDragEnd, + onItemClick, rowStyle, itemStyle, }: Props) => { @@ -57,12 +49,7 @@ const SortableDnd = ({ setItems(sortByLexorank(initialItems)); }, [initialItems]); - const sensors = useSensors( - useSensor(PointerSensor), - useSensor(KeyboardSensor, { - coordinateGetter: sortableKeyboardCoordinates, - }) - ); + const sensors = useDndSensors(); const onDragEndLocal = ({ active, over }: DragEndEvent) => { if (over && active.id !== over?.id && activeItem) { @@ -81,10 +68,12 @@ const SortableDnd = ({ } const leftLexorank: LexoRank | null = - leftIndex >= 0 ? LexoRank.parse(items[leftIndex].rank) : null; + leftIndex >= 0 + ? LexoRank.parse(items[leftIndex].lexorank) + : null; const rightLexorank: LexoRank | null = rightIndex < items.length - ? LexoRank.parse(items[rightIndex].rank) + ? LexoRank.parse(items[rightIndex].lexorank) : null; const newLexorank = getNewLexorank( @@ -92,7 +81,7 @@ const SortableDnd = ({ rightLexorank ).toString(); - items[activeIndex].rank = newLexorank; + items[activeIndex].lexorank = newLexorank; onDragEnd(items[activeIndex].id, newLexorank); const sortedItems = sortByLexorank(items); setItems([...sortedItems]); @@ -112,12 +101,19 @@ const SortableDnd = ({ style={rowStyle} role="application"> {items.map((item, index) => ( - - {renderItem(item)} - + onClick={e => { + e.preventDefault(); + e.stopPropagation(); + onItemClick(item); + }}> + + {renderItem(item)} + + ))} diff --git a/src/components/SortableDnd/SortableItem.tsx b/src/components/SortableDnd/SortableItem.tsx index 2f3e3c5..8504db5 100644 --- a/src/components/SortableDnd/SortableItem.tsx +++ b/src/components/SortableDnd/SortableItem.tsx @@ -9,7 +9,11 @@ type Props = { itemStyle?: CSSProperties; }; -export const SortableItem = ({ children, id }: PropsWithChildren) => { +export const SortableItem = ({ + children, + itemStyle, + id, +}: PropsWithChildren) => { const { attributes, isDragging, @@ -33,6 +37,7 @@ export const SortableItem = ({ children, id }: PropsWithChildren) => { opacity: isDragging ? 0.4 : undefined, transform: CSS.Translate.toString(transform), transition, + ...itemStyle, }; return ( diff --git a/src/hooks/useBoardsList.ts b/src/hooks/useBoardsList.ts new file mode 100644 index 0000000..6b4cb00 --- /dev/null +++ b/src/hooks/useBoardsList.ts @@ -0,0 +1,29 @@ +import { useEffect, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { BoardSchema } from "@/client"; +import { getBoardsOptions } from "@/client/@tanstack/react-query.gen"; + +type Props = { + projectId?: number; +}; + +const useBoardsList = ({ projectId }: Props) => { + const [boards, setBoards] = useState([]); + + const { data, refetch, isLoading } = useQuery({ + ...getBoardsOptions({ path: { project_id: projectId! } }), + enabled: projectId !== undefined, + }); + + useEffect(() => { + if (projectId === undefined) { + setBoards([]); + } else if (data?.boards) { + setBoards(data.boards); + } + }, [data?.boards, projectId]); + + return { boards, setBoards, refetch, isLoading }; +}; + +export default useBoardsList; diff --git a/src/types/BoardSchema.ts b/src/types/BoardSchema.ts deleted file mode 100644 index 021f1fc..0000000 --- a/src/types/BoardSchema.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type BoardSchema = { - id: number; - name: string; - rank: string; -}; diff --git a/src/types/DealSchema.ts b/src/types/DealSchema.ts index a4f7656..f1c6c54 100644 --- a/src/types/DealSchema.ts +++ b/src/types/DealSchema.ts @@ -1,6 +1,6 @@ export type DealSchema = { id: number; name: string; - rank: string; + lexorank: string; statusId: number; }; diff --git a/src/types/StatusSchema.ts b/src/types/StatusSchema.ts deleted file mode 100644 index 1f44b73..0000000 --- a/src/types/StatusSchema.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type StatusSchema = { - id: number; - name: string; - rank: string; -}; diff --git a/src/utils/lexorank.ts b/src/utils/lexorank.ts index 8909ada..d201a46 100644 --- a/src/utils/lexorank.ts +++ b/src/utils/lexorank.ts @@ -1,17 +1,17 @@ import { LexoRank } from "lexorank"; type LexorankSortable = { - rank: string; + lexorank: string; }; export function compareByLexorank( a: T, b: T ): -1 | 1 | 0 { - if (a.rank < b.rank) { + if (a.lexorank < b.lexorank) { return -1; } - if (a.rank > b.rank) { + if (a.lexorank > b.lexorank) { return 1; } return 0;