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
60 changes: 60 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Deploy to GitHub Pages

on:
push:
branches:
- main
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: "pages"
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: latest

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build
run: pnpm run build

- name: Setup Pages
uses: actions/configure-pages@v4

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: './dist'

deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

80 changes: 72 additions & 8 deletions src/ScheduleContext.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
import React, { createContext, PropsWithChildren, useContext, useState } from "react";
import { Schedule } from "./types.ts";
import dummyScheduleMap from "./dummyScheduleMap.ts";
import React, {
createContext,
PropsWithChildren,
useContext,
useState,
useMemo,
useCallback,
} from 'react';
import { Schedule } from './types.ts';
import dummyScheduleMap from './dummyScheduleMap.ts';

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

const ScheduleContext = createContext<ScheduleContextType | undefined>(undefined);
// schedulesMap만 제공하는 Context
const ScheduleMapContext = createContext<
Record<string, Schedule[]> | undefined
>(undefined);

// setSchedulesMap만 제공하는 Context (드래그 시 리렌더링 방지)
const ScheduleMapActionsContext = createContext<
React.Dispatch<React.SetStateAction<Record<string, Schedule[]>>> | undefined
>(undefined);

// 기존 호환성을 위한 통합 Context
const ScheduleContext = createContext<ScheduleContextType | undefined>(
undefined
);

export const useScheduleContext = () => {
const context = useContext(ScheduleContext);
Expand All @@ -17,12 +39,54 @@ export const useScheduleContext = () => {
return context;
};

// setSchedulesMap만 필요한 경우: schedulesMap 변경 시 리렌더링되지 않음
export const useSetSchedulesMap = () => {
const context = useContext(ScheduleMapActionsContext);
if (context === undefined) {
throw new Error(
'useSetSchedulesMap must be used within a ScheduleProvider'
);
}
return context;
};

// schedulesMap만 필요한 경우
export const useSchedulesMap = () => {
const context = useContext(ScheduleMapContext);
if (context === undefined) {
throw new Error('useSchedulesMap must be used within a ScheduleProvider');
}
return context;
};

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

// setSchedulesMap을 안정적인 함수로 메모이제이션
const stableSetSchedulesMap = useCallback(
(value: React.SetStateAction<Record<string, Schedule[]>>) => {
setSchedulesMap(value);
},
[]
);

// Context value를 메모이제이션하여 불필요한 리렌더링 방지
const value = useMemo(
() => ({
schedulesMap,
setSchedulesMap: stableSetSchedulesMap,
}),
[schedulesMap, stableSetSchedulesMap]
);

return (
<ScheduleContext.Provider value={{ schedulesMap, setSchedulesMap }}>
{children}
<ScheduleContext.Provider value={value}>
<ScheduleMapContext.Provider value={schedulesMap}>
<ScheduleMapActionsContext.Provider value={stableSetSchedulesMap}>
{children}
</ScheduleMapActionsContext.Provider>
</ScheduleMapContext.Provider>
</ScheduleContext.Provider>
);
};
95 changes: 62 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 { PropsWithChildren, useCallback } from 'react';
import { CellSize, DAY_LABELS } from './constants.ts';
import { useSetSchedulesMap } from './ScheduleContext.tsx';

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 setSchedulesMap = useSetSchedulesMap();
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
Expand All @@ -39,32 +56,44 @@ 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 handleDragEnd = useCallback(
(event: any) => {
const { active, delta } = event;
const { x, y } = delta;
const [tableId, index] = active.id.split(':');
const moveDayIndex = Math.floor(x / 80);
const moveTimeIndex = Math.floor(y / 30);

setSchedulesMap((prev) => {
const schedule = prev[tableId][Number(index)];
const nowDayIndex = DAY_LABELS.indexOf(
schedule.day as (typeof DAY_LABELS)[number]
);

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),
}
})
})
};
...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),
};
}),
};
});
},
[setSchedulesMap]
);

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