refactor: moved dnd part from Funnel into FunnelDnd
This commit is contained in:
@ -6,6 +6,7 @@ type Props = {
|
||||
};
|
||||
|
||||
const DealCard = ({ deal }: Props) => {
|
||||
console.log("deal");
|
||||
return <Card>{deal.name}</Card>;
|
||||
};
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import React, { FC, useMemo } from "react";
|
||||
import { Box } from "@mantine/core";
|
||||
import DealCard from "@/app/deals/components/DealCard/DealCard";
|
||||
import SortableItem from "@/components/dnd/SortableItem";
|
||||
import { DealSchema } from "@/lib/client";
|
||||
import { SortableItem } from "@/components/dnd/SortableDnd/SortableItem";
|
||||
|
||||
type Props = {
|
||||
deal: DealSchema;
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
import React from "react";
|
||||
import { defaultDropAnimation, DragOverlay } from "@dnd-kit/core";
|
||||
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, StatusSchema } from "@/lib/client";
|
||||
|
||||
type Props = {
|
||||
activeDeal: DealSchema | null;
|
||||
activeStatus: StatusSchema | null;
|
||||
};
|
||||
|
||||
const DndOverlay = ({ activeStatus, activeDeal }: Props) => {
|
||||
const { deals } = useStatusesContext();
|
||||
|
||||
return (
|
||||
<DragOverlay dropAnimation={defaultDropAnimation}>
|
||||
<div style={{ cursor: "grabbing" }}>
|
||||
{activeDeal ? (
|
||||
<DealCard deal={activeDeal} />
|
||||
) : activeStatus ? (
|
||||
<StatusColumn
|
||||
id={`${activeStatus.id}-status`}
|
||||
status={activeStatus}
|
||||
deals={deals.filter(
|
||||
deal => deal.statusId === activeStatus.id
|
||||
)}
|
||||
isDragging
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</DragOverlay>
|
||||
);
|
||||
};
|
||||
|
||||
export default DndOverlay;
|
||||
68
src/app/deals/components/Funnel/Funnel.tsx
Normal file
68
src/app/deals/components/Funnel/Funnel.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
"use client";
|
||||
|
||||
import React, { FC, ReactNode } from "react";
|
||||
import DealCard from "@/app/deals/components/DealCard/DealCard";
|
||||
import DealContainer from "@/app/deals/components/DealContainer/DealContainer";
|
||||
import StatusColumnWrapper from "@/app/deals/components/StatusColumnWrapper/StatusColumnWrapper";
|
||||
import { useStatusesContext } from "@/app/deals/contexts/StatusesContext";
|
||||
import useDealsAndStatusesDnd from "@/app/deals/hooks/useDealsAndStatusesDnd";
|
||||
import FunnelDnd from "@/components/dnd/FunnelDnd/FunnelDnd";
|
||||
import { DealSchema, StatusSchema } from "@/lib/client";
|
||||
import { sortByLexorank } from "@/utils/lexorank";
|
||||
|
||||
const Funnel: FC = () => {
|
||||
const { deals } = useStatusesContext();
|
||||
|
||||
const {
|
||||
sortedStatuses,
|
||||
handleDragStart,
|
||||
handleDragOver,
|
||||
handleDragEnd,
|
||||
activeStatus,
|
||||
activeDeal,
|
||||
} = useDealsAndStatusesDnd();
|
||||
|
||||
return (
|
||||
<FunnelDnd
|
||||
containers={sortedStatuses}
|
||||
items={deals}
|
||||
onDragStart={handleDragStart}
|
||||
onDragOver={handleDragOver}
|
||||
onDragEnd={handleDragEnd}
|
||||
getContainerId={(status: StatusSchema) => `${status.id}-status`}
|
||||
getItemsByContainer={(status: StatusSchema, items: DealSchema[]) =>
|
||||
sortByLexorank(
|
||||
items.filter(deal => deal.statusId === status.id)
|
||||
)
|
||||
}
|
||||
renderContainer={(
|
||||
status: StatusSchema,
|
||||
funnelColumnComponent: ReactNode
|
||||
) => (
|
||||
<StatusColumnWrapper
|
||||
status={status}
|
||||
isDragging={activeStatus?.id === status.id}>
|
||||
{funnelColumnComponent}
|
||||
</StatusColumnWrapper>
|
||||
)}
|
||||
renderItem={(deal: DealSchema) => (
|
||||
<DealContainer
|
||||
key={deal.id}
|
||||
deal={deal}
|
||||
/>
|
||||
)}
|
||||
activeContainer={activeStatus}
|
||||
activeItem={activeDeal}
|
||||
renderItemOverlay={(deal: DealSchema) => <DealCard deal={deal} />}
|
||||
renderContainerOverlay={(status: StatusSchema, children) => (
|
||||
<StatusColumnWrapper
|
||||
status={status}
|
||||
isDragging>
|
||||
{children}
|
||||
</StatusColumnWrapper>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Funnel;
|
||||
@ -1,67 +0,0 @@
|
||||
import React, { useMemo } from "react";
|
||||
import { useDroppable } from "@dnd-kit/core";
|
||||
import {
|
||||
SortableContext,
|
||||
verticalListSortingStrategy,
|
||||
} from "@dnd-kit/sortable";
|
||||
import { Box, Stack, Text } from "@mantine/core";
|
||||
import DealContainer from "@/app/deals/components/DealContainer/DealContainer";
|
||||
import { DealSchema, StatusSchema } from "@/lib/client";
|
||||
import { sortByLexorank } from "@/utils/lexorank";
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
status: StatusSchema;
|
||||
deals: DealSchema[];
|
||||
isDragging?: boolean;
|
||||
};
|
||||
|
||||
const StatusColumn = ({ id, status, deals, isDragging }: Props) => {
|
||||
const { setNodeRef } = useDroppable({ id });
|
||||
const sortedDeals = useMemo(
|
||||
() => sortByLexorank(deals.filter(deal => deal.statusId === status.id)),
|
||||
[deals]
|
||||
);
|
||||
|
||||
const columnBody = useMemo(() => {
|
||||
return (
|
||||
<SortableContext
|
||||
id={id}
|
||||
items={sortedDeals}
|
||||
strategy={verticalListSortingStrategy}>
|
||||
<Stack
|
||||
gap={"xs"}
|
||||
ref={setNodeRef}>
|
||||
{sortedDeals.map(deal => (
|
||||
<DealContainer
|
||||
key={deal.id}
|
||||
deal={deal}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</SortableContext>
|
||||
);
|
||||
}, [sortedDeals]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
style={{
|
||||
backgroundColor: "#eee",
|
||||
padding: 2,
|
||||
width: "15vw",
|
||||
minWidth: 150,
|
||||
}}>
|
||||
<Text
|
||||
style={{
|
||||
cursor: "grab",
|
||||
userSelect: "none",
|
||||
opacity: isDragging ? 0.5 : 1,
|
||||
}}>
|
||||
{status.name}
|
||||
</Text>
|
||||
{columnBody}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusColumn;
|
||||
@ -0,0 +1,33 @@
|
||||
import React, { ReactNode } from "react";
|
||||
import { Box, Text } from "@mantine/core";
|
||||
import { StatusSchema } from "@/lib/client";
|
||||
|
||||
type Props = {
|
||||
status: StatusSchema;
|
||||
isDragging?: boolean;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const StatusColumnWrapper = ({ status, children, isDragging = false }: Props) => {
|
||||
return (
|
||||
<Box
|
||||
style={{
|
||||
backgroundColor: "#eee",
|
||||
padding: 2,
|
||||
width: "15vw",
|
||||
minWidth: 150,
|
||||
}}>
|
||||
<Text
|
||||
style={{
|
||||
cursor: "grab",
|
||||
userSelect: "none",
|
||||
opacity: isDragging ? 0.5 : 1,
|
||||
}}>
|
||||
{status.name}
|
||||
</Text>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusColumnWrapper;
|
||||
@ -1,76 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import StatusColumnsDnd from "@/app/deals/components/StatusColumnsDnd/StatusColumnsDnd";
|
||||
import { useStatusesContext } from "@/app/deals/contexts/StatusesContext";
|
||||
import {
|
||||
updateDealMutation,
|
||||
updateStatusMutation,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
import { notifications } from "@/lib/notifications";
|
||||
|
||||
const StatusColumns = () => {
|
||||
const { refetchStatuses, refetchDeals } = useStatusesContext();
|
||||
|
||||
const updateStatus = useMutation({
|
||||
...updateStatusMutation(),
|
||||
onError: error => {
|
||||
console.error(error);
|
||||
notifications.error({
|
||||
message: error.response?.data?.detail as string | undefined,
|
||||
});
|
||||
refetchStatuses();
|
||||
},
|
||||
});
|
||||
|
||||
const updateDeals = useMutation({
|
||||
...updateDealMutation(),
|
||||
onError: error => {
|
||||
console.error(error);
|
||||
notifications.error({
|
||||
message: error.response?.data?.detail as string | undefined,
|
||||
});
|
||||
refetchDeals();
|
||||
},
|
||||
});
|
||||
|
||||
const onDealDragEnd = (
|
||||
dealId: number,
|
||||
statusId: number,
|
||||
lexorank?: string
|
||||
) => {
|
||||
updateDeals.mutate({
|
||||
path: {
|
||||
dealId,
|
||||
},
|
||||
body: {
|
||||
deal: {
|
||||
statusId,
|
||||
lexorank,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onStatusDragEnd = (statusId: number, lexorank: string) => {
|
||||
updateStatus.mutate({
|
||||
path: {
|
||||
statusId,
|
||||
},
|
||||
body: {
|
||||
status: {
|
||||
lexorank,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<StatusColumnsDnd
|
||||
onDealDragEnd={onDealDragEnd}
|
||||
onStatusDragEnd={onStatusDragEnd}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusColumns;
|
||||
@ -1,80 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import React, { FC } from "react";
|
||||
import { closestCorners, DndContext } from "@dnd-kit/core";
|
||||
import {
|
||||
horizontalListSortingStrategy,
|
||||
SortableContext,
|
||||
} from "@dnd-kit/sortable";
|
||||
import { Group, ScrollArea } from "@mantine/core";
|
||||
import DndOverlay from "@/app/deals/components/DndOverlay/DndOverlay";
|
||||
import StatusColumn from "@/app/deals/components/StatusColumn/StatusColumn";
|
||||
import { useStatusesContext } from "@/app/deals/contexts/StatusesContext";
|
||||
import useDealsAndStatusesDnd from "@/app/deals/hooks/useDealsAndStatusesDnd";
|
||||
import { SortableItem } from "@/components/dnd/SortableDnd/SortableItem";
|
||||
import useDndSensors from "../../hooks/useSensors";
|
||||
|
||||
type Props = {
|
||||
onDealDragEnd: (
|
||||
dealId: number,
|
||||
statusId: number,
|
||||
lexorank?: string
|
||||
) => void;
|
||||
onStatusDragEnd: (statusId: number, lexorank: string) => void;
|
||||
};
|
||||
|
||||
const StatusColumnsDnd: FC<Props> = props => {
|
||||
const { deals } = useStatusesContext();
|
||||
|
||||
const {
|
||||
sortedStatuses,
|
||||
handleDragStart,
|
||||
handleDragOver,
|
||||
handleDragEnd,
|
||||
activeStatus,
|
||||
activeDeal,
|
||||
} = useDealsAndStatusesDnd(props);
|
||||
|
||||
const sensors = useDndSensors();
|
||||
|
||||
return (
|
||||
<ScrollArea
|
||||
offsetScrollbars={"x"}
|
||||
scrollbarSize={"0.5rem"}>
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCorners}
|
||||
onDragStart={handleDragStart}
|
||||
onDragOver={handleDragOver}
|
||||
onDragEnd={handleDragEnd}>
|
||||
<SortableContext
|
||||
items={sortedStatuses.map(status => `${status.id}-status`)}
|
||||
strategy={horizontalListSortingStrategy}>
|
||||
<Group
|
||||
gap={"xs"}
|
||||
wrap={"nowrap"}
|
||||
align={"start"}>
|
||||
{sortedStatuses.map(status => (
|
||||
<SortableItem
|
||||
key={status.id}
|
||||
id={`${status.id}-status`}>
|
||||
<StatusColumn
|
||||
id={`${status.id}-status`}
|
||||
status={status}
|
||||
deals={deals}
|
||||
isDragging={activeStatus?.id === status.id}
|
||||
/>
|
||||
</SortableItem>
|
||||
))}
|
||||
<DndOverlay
|
||||
activeStatus={activeStatus}
|
||||
activeDeal={activeDeal}
|
||||
/>
|
||||
</Group>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
</ScrollArea>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusColumnsDnd;
|
||||
@ -1,17 +1,43 @@
|
||||
"use client";
|
||||
|
||||
import React, { createContext, FC, useContext, useEffect } from "react";
|
||||
import { useMutation, UseMutationResult } from "@tanstack/react-query";
|
||||
import { AxiosError } from "axios";
|
||||
import { useBoardsContext } from "@/app/deals/contexts/BoardsContext";
|
||||
import { DealSchema, StatusSchema } from "@/lib/client";
|
||||
import useDealsList from "@/hooks/useDealsList";
|
||||
import useStatusesList from "@/hooks/useStatusesList";
|
||||
import {
|
||||
DealSchema,
|
||||
HttpValidationError,
|
||||
Options,
|
||||
StatusSchema,
|
||||
UpdateDealData,
|
||||
UpdateDealResponse,
|
||||
UpdateStatusData,
|
||||
UpdateStatusResponse,
|
||||
} from "@/lib/client";
|
||||
import {
|
||||
updateDealMutation,
|
||||
updateStatusMutation,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
import { notifications } from "@/lib/notifications";
|
||||
|
||||
type StatusesContextState = {
|
||||
statuses: StatusSchema[];
|
||||
setStatuses: React.Dispatch<React.SetStateAction<StatusSchema[]>>;
|
||||
updateStatus: UseMutationResult<
|
||||
UpdateStatusResponse,
|
||||
AxiosError<HttpValidationError>,
|
||||
Options<UpdateStatusData>
|
||||
>;
|
||||
refetchStatuses: () => void;
|
||||
deals: DealSchema[];
|
||||
setDeals: React.Dispatch<React.SetStateAction<DealSchema[]>>;
|
||||
refetchStatuses: () => void;
|
||||
updateDeal: UseMutationResult<
|
||||
UpdateDealResponse,
|
||||
AxiosError<HttpValidationError>,
|
||||
Options<UpdateDealData>
|
||||
>;
|
||||
refetchDeals: () => void;
|
||||
};
|
||||
|
||||
@ -39,12 +65,36 @@ const useStatusesContextState = () => {
|
||||
refetchStatuses();
|
||||
}, [selectedBoard]);
|
||||
|
||||
const updateStatus = useMutation({
|
||||
...updateStatusMutation(),
|
||||
onError: error => {
|
||||
console.error(error);
|
||||
notifications.error({
|
||||
message: error.response?.data?.detail as string | undefined,
|
||||
});
|
||||
refetchStatuses();
|
||||
},
|
||||
});
|
||||
|
||||
const updateDeal = useMutation({
|
||||
...updateDealMutation(),
|
||||
onError: error => {
|
||||
console.error(error);
|
||||
notifications.error({
|
||||
message: error.response?.data?.detail as string | undefined,
|
||||
});
|
||||
refetchDeals();
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
statuses,
|
||||
setStatuses,
|
||||
updateStatus,
|
||||
refetchStatuses,
|
||||
deals,
|
||||
setDeals,
|
||||
refetchStatuses,
|
||||
updateDeal,
|
||||
refetchDeals,
|
||||
};
|
||||
};
|
||||
|
||||
@ -7,19 +7,11 @@ import { getStatusId, isStatusId } from "@/app/deals/utils/statusId";
|
||||
import { DealSchema, StatusSchema } from "@/lib/client";
|
||||
import { sortByLexorank } from "@/utils/lexorank";
|
||||
|
||||
type Props = {
|
||||
onDealDragEnd: (
|
||||
dealId: number,
|
||||
statusId: number,
|
||||
lexorank?: string
|
||||
) => void;
|
||||
onStatusDragEnd: (statusId: number, lexorank: string) => void;
|
||||
};
|
||||
|
||||
const useDealsAndStatusesDnd = (props: Props) => {
|
||||
const useDealsAndStatusesDnd = () => {
|
||||
const [activeDeal, setActiveDeal] = useState<DealSchema | null>(null);
|
||||
const [activeStatus, setActiveStatus] = useState<StatusSchema | null>(null);
|
||||
const { statuses, deals, setDeals, setStatuses } = useStatusesContext();
|
||||
const { statuses, deals, setDeals, setStatuses, updateDeal, updateStatus } =
|
||||
useStatusesContext();
|
||||
const sortedStatuses = useMemo(() => sortByLexorank(statuses), [statuses]);
|
||||
|
||||
const {
|
||||
@ -173,7 +165,20 @@ const useDealsAndStatusesDnd = (props: Props) => {
|
||||
const newRank = getNewStatusRank(activeStatusId, overStatusId);
|
||||
if (!newRank) return;
|
||||
|
||||
props.onStatusDragEnd?.(activeStatusId, newRank);
|
||||
onStatusDragEnd?.(activeStatusId, newRank);
|
||||
};
|
||||
|
||||
const onStatusDragEnd = (statusId: number, lexorank: string) => {
|
||||
updateStatus.mutate({
|
||||
path: {
|
||||
statusId,
|
||||
},
|
||||
body: {
|
||||
status: {
|
||||
lexorank,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleDealDragEnd = (activeId: number | string, over: Over) => {
|
||||
@ -189,7 +194,25 @@ const useDealsAndStatusesDnd = (props: Props) => {
|
||||
);
|
||||
if (!overStatusId) return;
|
||||
|
||||
props.onDealDragEnd(activeDealId, overStatusId, newLexorank);
|
||||
onDealDragEnd(activeDealId, overStatusId, newLexorank);
|
||||
};
|
||||
|
||||
const onDealDragEnd = (
|
||||
dealId: number,
|
||||
statusId: number,
|
||||
lexorank?: string
|
||||
) => {
|
||||
updateDeal.mutate({
|
||||
path: {
|
||||
dealId,
|
||||
},
|
||||
body: {
|
||||
deal: {
|
||||
statusId,
|
||||
lexorank,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleDragStart = ({ active }: DragStartEvent) => {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Divider } from "@mantine/core";
|
||||
import Boards from "@/app/deals/components/Boards/Boards";
|
||||
import Funnel from "@/app/deals/components/Funnel/Funnel";
|
||||
import Header from "@/app/deals/components/Header/Header";
|
||||
import StatusColumns from "@/app/deals/components/StatusColumns/StatusColumns";
|
||||
import { BoardsContextProvider } from "@/app/deals/contexts/BoardsContext";
|
||||
import { ProjectsContextProvider } from "@/app/deals/contexts/ProjectsContext";
|
||||
import { StatusesContextProvider } from "@/app/deals/contexts/StatusesContext";
|
||||
@ -18,7 +18,7 @@ export default function DealsPage() {
|
||||
<Boards />
|
||||
<Divider my={"xl"} />
|
||||
<StatusesContextProvider>
|
||||
<StatusColumns />
|
||||
<Funnel />
|
||||
</StatusesContextProvider>
|
||||
</BoardsContextProvider>
|
||||
</ProjectsContextProvider>
|
||||
|
||||
Reference in New Issue
Block a user