Skip to content
Snippets Groups Projects
Commit ae61dd03 authored by 석찬 윤's avatar 석찬 윤
Browse files

feat: 스케쥴 페이지모바일뷰 개발 (#8)

parent a019b0ce
Branches feat/#8
No related tags found
No related merge requests found
import React from "react";
const Schedule = () => {
return <></>;
};
export default Schedule;
import React, { useState, useRef } from "react";
const generateTimeSlots = () => {
const timeSlots = [];
for (let hour = 0; hour < 24; hour++) {
for (let min = 0; min < 60; min += 15) {
timeSlots.push(
`${hour.toString().padStart(2, "0")}:${min.toString().padStart(2, "0")}`
);
}
}
return timeSlots;
};
const days = ["", "", "", "", "", "", ""];
const SchedulePage = () => {
const timeSlots = generateTimeSlots();
const [schedules, setSchedules] = useState([]);
const isDragging = useRef(false);
const [draggedSlots, setDraggedSlots] = useState([]);
const [inputTitle, setInputTitle] = useState("");
const [isEditing, setIsEditing] = useState(false);
const [editMode, setEditMode] = useState(false);
const handleMouseDown = (slotIndex) => {
if (!editMode) return;
isDragging.current = true;
if (draggedSlots.includes(slotIndex)) {
setDraggedSlots((prev) => prev.filter((slot) => slot !== slotIndex));
return;
}
setDraggedSlots((prev) => [...new Set([...prev, slotIndex])]);
const schedule = schedules.find((s) => s.time_idx === slotIndex);
if (schedule) {
setInputTitle(schedule.title);
setIsEditing(true);
} else {
setInputTitle("");
setIsEditing(false);
}
};
const handleMouseEnter = (slotIndex) => {
if (!editMode || !isDragging.current) return;
setDraggedSlots((prev) => [...new Set([...prev, slotIndex])]);
};
const handleMouseUp = () => {
isDragging.current = false;
};
const handleMouseLeave = () => {
isDragging.current = false;
};
const handleSave = () => {
if (!inputTitle.trim()) return;
const newSchedules = [...schedules];
draggedSlots.forEach((slotIndex) => {
const existingIndex = newSchedules.findIndex(
(s) => s.time_idx === slotIndex
);
if (existingIndex === -1) {
newSchedules.push({
id: Date.now(),
time_idx: slotIndex,
title: inputTitle,
is_fixed: false,
});
} else {
newSchedules[existingIndex].title = inputTitle;
}
});
setSchedules(newSchedules);
setDraggedSlots([]);
setInputTitle("");
setIsEditing(false);
};
const handleDelete = () => {
const newSchedules = schedules.filter(
(s) => !draggedSlots.includes(s.time_idx)
);
setSchedules(newSchedules);
setDraggedSlots([]);
setInputTitle("");
setIsEditing(false);
};
return (
<div className="min-h-screen bg-gray-50">
{/* Edit Mode Toggle */}
<div className="flex items-center justify-between p-4 bg-white shadow">
<h1 className="text-lg font-bold mobile:text-base">Schedule</h1>
<label className="flex items-center space-x-3">
<span className="text-lg font-bold mobile:text-sm">Edit Mode</span>
<div
className={`relative w-12 h-6 rounded-full cursor-pointer transition-colors ${
editMode ? "bg-gradient-pink" : "bg-gray-300"
}`}
onClick={() => setEditMode((prev) => !prev)}
>
<div
className={`absolute top-0.5 left-0.5 h-5 w-5 rounded-full bg-white transition-transform ${
editMode ? "translate-x-6" : ""
}`}
></div>
</div>
</label>
</div>
{/* Sticky Container (Edit Mode가 on일 때만 표시) */}
{editMode && (
<div className="sticky top-0 flex flex-col gap-2 p-4 bg-white shadow tablet:flex-row tablet:justify-between">
<input
type="text"
value={inputTitle}
onChange={(e) => setInputTitle(e.target.value)}
placeholder="Enter schedule title"
className="flex-1 p-2 border rounded"
/>
<div className="flex space-x-2">
<button
onClick={handleSave}
className="flex-1 px-4 py-2 text-white rounded bg-gradient-pink disabled:opacity-50"
disabled={!draggedSlots.length}
>
{isEditing ? "Update" : "Save"}
</button>
<button
onClick={handleDelete}
className="flex-1 px-4 py-2 text-white rounded bg-gradient-purple disabled:opacity-50"
disabled={!draggedSlots.length}
>
Delete
</button>
</div>
</div>
)}
{/* Schedule Grid */}
<div className="p-4">
<div className="overflow-auto scrollbar-hide">
<div className="tablet:w-[960px] tablet:min-w-[800px] grid grid-cols-[64px,repeat(7,1fr)] gap-2">
{/* Header */}
<div className="min-w-[54px] p-2 font-bold text-center bg-gray-200 select-none">
Time
</div>
{days.map((day) => (
<div
key={day}
className="p-2 font-bold text-center bg-gray-200 select-none"
>
{day}
</div>
))}
{/* Time Slots */}
{timeSlots.map((time, rowIndex) => (
<React.Fragment key={rowIndex}>
{/* Time Column */}
<div className="min-w-[54px] p-2 text-sm font-bold text-center bg-gray-100 select-none">
{time}
</div>
{days.map((_, colIndex) => {
const slotIndex = colIndex * timeSlots.length + rowIndex;
const isSelected = draggedSlots.includes(slotIndex);
const schedule = schedules.find(
(s) => s.time_idx === slotIndex
);
return (
<div
key={slotIndex}
className={`p-2 border rounded cursor-pointer ${
editMode
? isSelected
? "bg-tertiary-300"
: schedule
? "bg-primary-500 text-white"
: "bg-white"
: schedule
? "bg-primary-500 text-white"
: "bg-gray-50"
}`}
onMouseDown={() => handleMouseDown(slotIndex)}
onMouseEnter={() => handleMouseEnter(slotIndex)}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseLeave}
>
{schedule && !isSelected ? schedule.title : ""}
</div>
);
})}
</React.Fragment>
))}
</div>
</div>
</div>
</div>
);
};
export default SchedulePage;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment