UI Unification: Refactor MonthlyWorkPage to Notepad design system

This commit is contained in:
backuppc
2025-12-30 15:11:05 +09:00
parent 9f7308b549
commit 9b55cb57c9

View File

@@ -1,14 +1,20 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { import {
Calendar, CalendarDays,
Save, Save,
RefreshCw, RefreshCw,
ChevronLeft, ChevronLeft,
ChevronRight, ChevronRight,
Loader2, Loader2,
Calendar,
CheckCircle2,
XCircle,
FileText,
Clock
} from 'lucide-react'; } from 'lucide-react';
import { comms } from '@/communication'; import { comms } from '@/communication';
import { HolidayItem } from '@/types'; import { HolidayItem } from '@/types';
import { clsx } from 'clsx';
interface DayInfo extends HolidayItem { interface DayInfo extends HolidayItem {
dayOfWeek: number; dayOfWeek: number;
@@ -104,144 +110,200 @@ export function MonthlyWorkPage() {
const freeDays = holidays.filter(h => h.free).length; const freeDays = holidays.filter(h => h.free).length;
return ( return (
<div className="space-y-6"> <div className="space-y-6 animate-fade-in pb-4 h-full">
{/* 헤더 */} {/* 월별근무표 메인 카드 */}
<div className="glass-effect rounded-2xl p-6"> <div className="glass-effect rounded-3xl overflow-hidden shadow-2xl border border-white/10 flex flex-col h-full max-h-[calc(100vh-140px)]">
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4"> <div className="px-6 py-4 border-b border-white/10 flex flex-col xl:flex-row items-center justify-between gap-4 bg-white/[0.02] shrink-0">
<div className="flex items-center space-x-3"> <div className="flex items-center gap-3">
<div className="p-3 bg-primary-500/20 rounded-xl"> <div className="p-2 bg-primary-500/20 rounded-lg">
<Calendar className="w-6 h-6 text-primary-400" /> <CalendarDays className="w-5 h-5 text-primary-400" />
</div> </div>
<div> <div>
<h1 className="text-2xl font-bold text-white"></h1> <h3 className="text-lg font-bold text-white tracking-tight"></h3>
<p className="text-white/60 text-sm"> </p> <p className="text-white/30 text-[10px] uppercase font-bold tracking-widest mt-0.5">
Monthly Company Schedule
</p>
</div> </div>
</div> </div>
<div className="flex items-center space-x-4"> <div className="flex flex-wrap items-center justify-center gap-3">
{/* 월 선택 */} {/* 월 선택 컨트롤 */}
<div className="flex items-center space-x-2 bg-white/10 rounded-lg px-3 py-2"> <div className="flex items-center gap-2 bg-white/5 border border-white/10 rounded-xl px-2 py-1 h-[40px]">
<button <button
onClick={() => handleMonthChange(-1)} onClick={() => handleMonthChange(-1)}
className="p-1 hover:bg-white/10 rounded transition-colors" className="p-1.5 hover:bg-white/10 rounded-lg text-white/50 hover:text-white transition-all"
> >
<ChevronLeft className="w-5 h-5 text-white" /> <ChevronLeft className="w-4 h-4" />
</button> </button>
<input
type="month" <div className="relative">
value={month} <Calendar className="absolute left-2 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-primary-400" />
onChange={(e) => setMonth(e.target.value)} <input
className="bg-transparent text-white text-center w-32 focus:outline-none" type="month"
/> value={month}
onChange={(e) => setMonth(e.target.value)}
className="bg-transparent border-none text-xs font-bold text-white pl-7 pr-2 focus:outline-none focus:ring-0 w-28 h-full cursor-pointer"
/>
</div>
<button <button
onClick={() => handleMonthChange(1)} onClick={() => handleMonthChange(1)}
className="p-1 hover:bg-white/10 rounded transition-colors" className="p-1.5 hover:bg-white/10 rounded-lg text-white/50 hover:text-white transition-all"
> >
<ChevronRight className="w-5 h-5 text-white" /> <ChevronRight className="w-4 h-4" />
</button> </button>
</div> </div>
{/* 버튼들 */} {/* 빠른 통계 */}
<button <div className="flex items-center gap-1.5 px-3 h-[40px] bg-white/5 border border-white/10 rounded-xl">
onClick={loadData} <div className="flex items-center gap-1.5">
disabled={loading} <div className="w-1.5 h-1.5 rounded-full bg-primary-400" />
className="flex items-center space-x-2 px-4 py-2 bg-white/10 hover:bg-white/20 rounded-lg text-white transition-colors disabled:opacity-50" <span className="text-[10px] text-white/30 uppercase font-bold">Duty</span>
> <span className="text-xs font-bold text-white ml-0.5">{workDays}</span>
<RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} /> </div>
<span className="hidden sm:inline"></span> <div className="w-px h-3 bg-white/10 mx-1" />
</button> <div className="flex items-center gap-1.5">
<div className="w-1.5 h-1.5 rounded-full bg-red-400" />
<span className="text-[10px] text-white/30 uppercase font-bold">Free</span>
<span className="text-xs font-bold text-white ml-0.5">{freeDays}</span>
</div>
</div>
<button <div className="flex items-center gap-2">
onClick={handleSave} {/* 새로고침 */}
disabled={saving || !hasChanges} <button
className="flex items-center space-x-2 px-4 py-2 bg-primary-500 hover:bg-primary-600 rounded-lg text-white transition-colors disabled:opacity-50" onClick={loadData}
> disabled={loading}
{saving ? ( className="p-2 bg-white/5 hover:bg-white/10 border border-white/10 rounded-xl text-white/70 hover:text-white transition-all disabled:opacity-50 h-[40px] w-[40px] flex items-center justify-center"
<Loader2 className="w-4 h-4 animate-spin" /> title="새로고침"
) : ( >
<Save className="w-4 h-4" /> <RefreshCw className={clsx("w-4 h-4", loading && "animate-spin")} />
)} </button>
<span></span>
</button> {/* 저장 버튼 */}
<button
onClick={handleSave}
disabled={saving || !hasChanges}
className={clsx(
"flex items-center gap-2 px-4 py-2 rounded-xl font-bold text-xs transition-all active:scale-95 h-[40px]",
hasChanges
? "bg-primary-500 hover:bg-primary-600 text-white shadow-lg shadow-primary-500/20 border border-white/20"
: "bg-white/5 text-white/20 border border-white/5 cursor-not-allowed"
)}
>
{saving ? (
<Loader2 className="w-3.5 h-3.5 animate-spin" />
) : (
<Save className="w-3.5 h-3.5" />
)}
<span> </span>
</button>
</div>
</div> </div>
</div> </div>
{/* 통계 */} {/* 리스트 헤더 */}
<div className="mt-4 flex items-center space-x-6 text-sm"> <div className="bg-white/5 px-6 py-3 border-b border-white/5 flex items-center text-[10px] font-bold text-white/30 uppercase tracking-widest shrink-0">
<span className="text-white/70"> <div className="w-32 px-4"> (Date)</div>
: <span className="text-white font-semibold">{workDays}</span> <div className="w-20 px-4 text-center"></div>
</span> <div className="w-24 px-4 text-center"> </div>
<span className="text-white/70"> <div className="flex-1 px-4"> (Memo)</div>
: <span className="text-danger-400 font-semibold">{freeDays}</span>
</span>
<span className="text-white/70">
: <span className="text-white font-semibold">{holidays.length}</span>
</span>
</div> </div>
</div>
{/* 테이블 */} <div className="flex-1 overflow-y-auto custom-scrollbar divide-y divide-white/5">
<div className="glass-effect rounded-2xl overflow-hidden"> {loading ? (
{loading ? ( <div className="py-20 text-center">
<div className="flex items-center justify-center py-20"> <RefreshCw className="w-10 h-10 mx-auto mb-4 animate-spin text-primary-500/50" />
<Loader2 className="w-8 h-8 text-white animate-spin" /> <p className="text-white/50 font-medium text-sm"> ...</p>
</div> </div>
) : ( ) : holidays.length === 0 ? (
<div className="overflow-x-auto"> <div className="py-32 text-center">
<table className="w-full"> <Calendar className="w-16 h-16 mx-auto text-white/10 mb-4" />
<thead className="bg-white/10"> <p className="text-white/30 text-base font-bold"> </p>
<tr> <p className="text-white/10 text-[10px] mt-2 uppercase tracking-[0.2em]">No schedule data found</p>
<th className="px-4 py-3 text-left text-xs font-medium text-white/70 uppercase w-32"></th> </div>
<th className="px-4 py-3 text-center text-xs font-medium text-white/70 uppercase w-20"></th> ) : (
<th className="px-4 py-3 text-center text-xs font-medium text-white/70 uppercase w-24"></th> holidays.map((day) => (
<th className="px-4 py-3 text-left text-xs font-medium text-white/70 uppercase"></th> <div
</tr> key={day.idx}
</thead> className={clsx(
<tbody className="divide-y divide-white/5"> "px-6 py-2.5 hover:bg-white/[0.03] transition-all group flex items-center",
{holidays.map((day) => ( day.dayOfWeek === 0 && "bg-red-500/[0.03]",
<tr day.dayOfWeek === 6 && "bg-blue-500/[0.03]"
key={day.idx} )}
className={`hover:bg-white/5 transition-colors ${ >
day.dayOfWeek === 0 ? 'bg-danger-500/10' : {/* 날짜 */}
day.dayOfWeek === 6 ? 'bg-primary-500/10' : '' <div className="w-32 px-4">
}`} <div className="flex items-center gap-2">
> <Clock className="w-3 h-3 text-white/20" />
<td className="px-4 py-3 text-white text-sm"> <span className="text-sm font-mono tracking-tight text-white/70">
{day.pdate} {day.pdate}
</td> </span>
<td className={`px-4 py-3 text-center text-sm font-medium ${ </div>
day.dayOfWeek === 0 ? 'text-danger-400' : </div>
day.dayOfWeek === 6 ? 'text-primary-400' : 'text-white/70'
}`}> {/* 요일 */}
{day.dayName} <div className="w-20 px-4 text-center">
</td> <span className={clsx(
<td className="px-4 py-3 text-center"> "text-xs font-bold",
<button day.dayOfWeek === 0 ? "text-red-400" :
onClick={() => handleToggleFree(day.idx)} day.dayOfWeek === 6 ? "text-blue-400" : "text-white/30"
className={`w-8 h-8 rounded-lg transition-colors ${ )}>
day.free {day.dayName}
? 'bg-danger-500/20 text-danger-400 hover:bg-danger-500/30' </span>
: 'bg-white/10 text-white/40 hover:bg-white/20' </div>
}`}
> {/* 휴일지정 */}
{day.free ? 'O' : '-'} <div className="w-24 px-4 flex justify-center">
</button> <button
</td> onClick={() => handleToggleFree(day.idx)}
<td className="px-4 py-3"> className={clsx(
<input "flex items-center justify-center w-8 h-8 rounded-xl border transition-all active:scale-90",
type="text" day.free
value={day.memo || ''} ? "bg-red-500/10 border-red-500/30 text-red-400 shadow-lg shadow-red-500/10"
onChange={(e) => handleMemoChange(day.idx, e.target.value)} : "bg-white/5 border-white/5 text-white/10 hover:border-white/10 hover:text-white/30"
placeholder="메모 입력..." )}
className="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-1.5 text-white text-sm focus:outline-none focus:border-primary-500" title={day.free ? "휴일 해제" : "휴일 지정"}
/> >
</td> {day.free ? <CheckCircle2 className="w-4 h-4" /> : <XCircle className="w-4 h-4" />}
</tr> </button>
))} </div>
</tbody>
</table> {/* 메모 */}
<div className="flex-1 px-4">
<div className="relative group/input">
<FileText className="absolute left-3 top-1/2 -translate-y-1/2 w-3 h-3 text-white/10 group-focus-within/input:text-primary-500 transition-colors" />
<input
type="text"
value={day.memo || ''}
onChange={(e) => handleMemoChange(day.idx, e.target.value)}
placeholder="메모를 입력하세요..."
className="w-full bg-white/5 border border-white/5 rounded-xl pl-9 pr-4 py-1.5 text-xs text-white placeholder:text-white/5 focus:outline-none focus:bg-white/10 focus:border-primary-500/30 transition-all font-medium"
/>
</div>
</div>
</div>
))
)}
</div>
{/* 푸터 */}
<div className="px-6 py-2 flex items-center justify-between bg-white/[0.02] border-t border-white/5 shrink-0">
<div className="text-white/20 text-[9px] font-bold uppercase tracking-[0.2em] py-2">
Company Schedule Hub <span className="text-white/5 mx-2">/</span>
Month <span className="text-primary-400/50 font-mono tracking-normal">{month}</span>
</div> </div>
)} <div className="text-white/20 text-[9px] flex items-center gap-4">
<div className="flex items-center gap-1.5">
<div className="w-1 h-1 rounded-full bg-red-400" />
<span>Sunday ()</span>
</div>
<div className="flex items-center gap-1.5">
<div className="w-1 h-1 rounded-full bg-blue-400" />
<span>Saturday ()</span>
</div>
</div>
</div>
</div> </div>
</div> </div>
); );