Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { ChakraProvider } from "@chakra-ui/react";
import { ScheduleProvider } from "./ScheduleContext.tsx";
import { ScheduleTables } from "./ScheduleTables.tsx";
import { ScheduleTables } from "./components/ScheduleTables/index.tsx";
import ScheduleDndProvider from "./ScheduleDndProvider.tsx";

function App() {

return (
<ChakraProvider>
<ScheduleProvider>
<ScheduleDndProvider>
<ScheduleTables/>
<ScheduleTables />
</ScheduleDndProvider>
</ScheduleProvider>
</ChakraProvider>
Expand Down
64 changes: 47 additions & 17 deletions src/ScheduleContext.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,58 @@
import React, { createContext, PropsWithChildren, useContext, useState } from "react";
import { Schedule } from "./types.ts";
import dummyScheduleMap from "./dummyScheduleMap.ts";
/* eslint-disable react-refresh/only-export-components */
import React, {
createContext,
PropsWithChildren,
useContext,
useState,
} from "react";
import dummyScheduleMap from "./dummyScheduleMap";
import { Schedule } from "./types";

interface ScheduleContextType {
schedulesMap: Record<string, Schedule[]>;
setSchedulesMap: React.Dispatch<React.SetStateAction<Record<string, Schedule[]>>>;
}
type SchedulesMap = Record<string, Schedule[]>;
type SetSchedulesMap = React.Dispatch<React.SetStateAction<SchedulesMap>>;

const ScheduleContext = createContext<ScheduleContextType | undefined>(undefined);
const ScheduleQueryContext = createContext<SchedulesMap | undefined>(undefined);
const ScheduleCommandContext = createContext<SetSchedulesMap | undefined>(
undefined
);

export const useScheduleContext = () => {
const context = useContext(ScheduleContext);
if (context === undefined) {
throw new Error('useSchedule must be used within a ScheduleProvider');
ScheduleQueryContext.displayName = "ScheduleQueryContext";
ScheduleCommandContext.displayName = "ScheduleCommandContext";

const useContextSafely = <T,>(
context: React.Context<T | undefined>,
contextName: string
): T => {
const value = useContext(context);
if (value === undefined) {
throw new Error(
`${contextName}를 사용하려면 ScheduleProvider 내부에서 사용해야 합니다`
);
}
return context;
return value;
};

export const useScheduleQueryContext = () =>
useContextSafely(ScheduleQueryContext, "Schedule Query");

export const useScheduleCommandContext = () =>
useContextSafely(ScheduleCommandContext, "Schedule Command");

export const useScheduleContext = () => ({
schedulesMap: useScheduleQueryContext(),
setSchedulesMap: useScheduleCommandContext(),
});

export const ScheduleProvider = ({ children }: PropsWithChildren) => {
const [schedulesMap, setSchedulesMap] = useState<Record<string, Schedule[]>>(dummyScheduleMap);
const [schedulesMap, setSchedulesMap] = useState<SchedulesMap>(
() => dummyScheduleMap
);

return (
<ScheduleContext.Provider value={{ schedulesMap, setSchedulesMap }}>
{children}
</ScheduleContext.Provider>
<ScheduleCommandContext.Provider value={setSchedulesMap}>
<ScheduleQueryContext.Provider value={schedulesMap}>
{children}
</ScheduleQueryContext.Provider>
</ScheduleCommandContext.Provider>
);
};
94 changes: 61 additions & 33 deletions src/ScheduleDndProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { DndContext, Modifier, PointerSensor, useSensor, useSensors } from "@dnd-kit/core";
import { PropsWithChildren } from "react";
import { CellSize, DAY_LABELS } from "./constants.ts";
import { useScheduleContext } from "./ScheduleContext.tsx";
import {
DndContext,
Modifier,
PointerSensor,
useSensor,
useSensors,
} from "@dnd-kit/core";
import { memo, PropsWithChildren } from "react";
import { CellSize, DAY_LABELS } from "./constants";
import { useScheduleCommandContext } from "./ScheduleContext";

function createSnapModifier(): Modifier {
return ({ transform, containerNodeRect, draggingNodeRect }) => {
Expand All @@ -17,19 +23,30 @@ function createSnapModifier(): Modifier {
const maxX = containerRight - right;
const maxY = containerBottom - bottom;


return ({
return {
...transform,
x: Math.min(Math.max(Math.round(transform.x / CellSize.WIDTH) * CellSize.WIDTH, minX), maxX),
y: Math.min(Math.max(Math.round(transform.y / CellSize.HEIGHT) * CellSize.HEIGHT, minY), maxY),
})
x: Math.min(
Math.max(
Math.round(transform.x / CellSize.WIDTH) * CellSize.WIDTH,
minX
),
maxX
),
y: Math.min(
Math.max(
Math.round(transform.y / CellSize.HEIGHT) * CellSize.HEIGHT,
minY
),
maxY
),
};
};
}

const modifiers = [createSnapModifier()]
const modifiers = [createSnapModifier()];

export default function ScheduleDndProvider({ children }: PropsWithChildren) {
const { schedulesMap, setSchedulesMap } = useScheduleContext();
const ScheduleDndProvider = memo(({ children }: PropsWithChildren) => {
const setSchedulesMap = useScheduleCommandContext();
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
Expand All @@ -38,34 +55,45 @@ export default function ScheduleDndProvider({ children }: PropsWithChildren) {
})
);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const handleDragEnd = (event: any) => {
const { active, delta } = event;
const { x, y } = delta;
const [tableId, index] = active.id.split(':');
const schedule = schedulesMap[tableId][index];
const nowDayIndex = DAY_LABELS.indexOf(schedule.day as typeof DAY_LABELS[number])
const moveDayIndex = Math.floor(x / 80);
const moveTimeIndex = Math.floor(y / 30);
const [tableId, index] = active.id.split(":");

setSchedulesMap({
...schedulesMap,
[tableId]: schedulesMap[tableId].map((targetSchedule, targetIndex) => {
if (targetIndex !== Number(index)) {
return { ...targetSchedule }
}
return {
...targetSchedule,
day: DAY_LABELS[nowDayIndex + moveDayIndex],
range: targetSchedule.range.map(time => time + moveTimeIndex),
}
})
})
setSchedulesMap((prev) => {
const schedule = prev[tableId][index];
const nowDayIndex = DAY_LABELS.indexOf(
schedule.day as (typeof DAY_LABELS)[number]
);
const moveDayIndex = Math.floor(x / 80);
const moveTimeIndex = Math.floor(y / 30);
return {
...prev,
[tableId]: prev[tableId].map((targetSchedule, targetIndex) => {
if (targetIndex !== Number(index)) {
return { ...targetSchedule };
}
return {
...targetSchedule,
day: DAY_LABELS[nowDayIndex + moveDayIndex],
range: targetSchedule.range.map((time) => time + moveTimeIndex),
};
}),
};
});
};

return (
<DndContext sensors={sensors} onDragEnd={handleDragEnd} modifiers={modifiers}>
<DndContext
sensors={sensors}
onDragEnd={handleDragEnd}
modifiers={modifiers}
>
{children}
</DndContext>
);
}
});

ScheduleDndProvider.displayName = "ScheduleDndProvider";

export default ScheduleDndProvider;
180 changes: 0 additions & 180 deletions src/ScheduleTable.tsx

This file was deleted.

Loading