feat: 모바일 반응형 UI 개선 및 공휴일/일정 시스템 통합
- MobileCard 접기/펼치기(collapsible) 기능 추가 및 반응형 레이아웃 개선 - DatePicker 공휴일/세무일정 색상 코딩 통합, DateTimePicker 신규 추가 - useCalendarScheduleInit 훅으로 전역 공휴일/일정 데이터 캐싱 - 전 도메인 날짜 필드 DatePicker 표준화 (104 files) - 생산대시보드/작업지시 모바일 호환성 강화 - 견적서/주문관리 반응형 그리드 적용 - 회계 모듈 기능 개선 (매입상세 결재연동, 미수금현황 조회조건 등) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,31 @@ const TYPE_KEYWORD_MAP: Record<string, CalendarScheduleType> = {
|
||||
'임시휴일': 'temporaryHoliday',
|
||||
};
|
||||
|
||||
// 대체공휴일 이름 패턴 (이름에 "대체" 포함)
|
||||
const SUBSTITUTE_HOLIDAY_PATTERN = /대체/;
|
||||
|
||||
// 공휴일 이름 패턴 (한국 법정공휴일 키워드)
|
||||
const PUBLIC_HOLIDAY_KEYWORDS = [
|
||||
'신정', '설날', '설연휴', '삼일절', '어린이날', '부처님오신날',
|
||||
'석가탄신일', '현충일', '광복절', '추석', '개천절', '한글날',
|
||||
'성탄절', '크리스마스', '제헌절', '선거',
|
||||
];
|
||||
|
||||
/**
|
||||
* companyEvent로 잘못 저장된 일정의 올바른 유형을 추정
|
||||
* - 이름에 "대체" 포함 → substituteHoliday
|
||||
* - 공휴일 키워드 매칭 → publicHoliday
|
||||
* - 매칭 안 됨 → 그대로 companyEvent 유지
|
||||
*/
|
||||
function inferCorrectType(schedule: CalendarSchedule): CalendarScheduleType {
|
||||
if (schedule.type !== 'companyEvent') return schedule.type;
|
||||
|
||||
const name = schedule.name;
|
||||
if (SUBSTITUTE_HOLIDAY_PATTERN.test(name)) return 'substituteHoliday';
|
||||
if (PUBLIC_HOLIDAY_KEYWORDS.some((kw) => name.includes(kw))) return 'publicHoliday';
|
||||
return 'companyEvent';
|
||||
}
|
||||
|
||||
function parseBulkText(text: string): BulkScheduleItem[] {
|
||||
const lines = text.split('\n').filter((line) => line.trim());
|
||||
const items: BulkScheduleItem[] = [];
|
||||
@@ -69,7 +94,9 @@ function parseBulkText(text: string): BulkScheduleItem[] {
|
||||
function schedulesToText(schedules: CalendarSchedule[]): string {
|
||||
return schedules.map((s) => {
|
||||
const datePart = s.startDate === s.endDate ? s.startDate : `${s.startDate}~${s.endDate}`;
|
||||
const typePart = s.type !== 'publicHoliday' ? ` [${SCHEDULE_TYPE_LABELS[s.type]}]` : '';
|
||||
// companyEvent로 잘못 저장된 공휴일/대체휴일은 올바른 유형으로 변환
|
||||
const correctedType = inferCorrectType(s);
|
||||
const typePart = correctedType !== 'publicHoliday' ? ` [${SCHEDULE_TYPE_LABELS[correctedType]}]` : '';
|
||||
return `${datePart} ${s.name}${typePart}`;
|
||||
}).join('\n');
|
||||
}
|
||||
|
||||
@@ -80,7 +80,10 @@ export function CalendarManagement() {
|
||||
if (statsResult.success && statsResult.data) {
|
||||
setStats(statsResult.data);
|
||||
}
|
||||
useCalendarScheduleStore.getState().setSchedulesForYear(year, loadedSchedules);
|
||||
// 캐시 무효화 후 새 데이터 설정 → 다른 컴포넌트에서 stale 데이터 방지
|
||||
const store = useCalendarScheduleStore.getState();
|
||||
store.invalidateYear(year);
|
||||
store.setSchedulesForYear(year, loadedSchedules);
|
||||
} catch {
|
||||
// 조회 실패 시 빈 상태 유지
|
||||
} finally {
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import { X, Users, Download } from 'lucide-react';
|
||||
import { Trash2, Users, Download } from 'lucide-react';
|
||||
import type { Employee, CSVEmployeeRow, CSVValidationResult } from './types';
|
||||
|
||||
interface CSVUploadPageProps {
|
||||
@@ -225,7 +225,7 @@ export function CSVUploadPage({ onUpload }: CSVUploadPageProps) {
|
||||
onClick={handleRemoveFile}
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -638,7 +638,7 @@ export function EmployeeManagement() {
|
||||
headerBadges={
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
#{globalIndex}
|
||||
{globalIndex}
|
||||
</Badge>
|
||||
<code className="text-xs bg-gray-100 px-2 py-1 rounded">
|
||||
{item.employeeCode}
|
||||
|
||||
Reference in New Issue
Block a user