feat: 신규 페이지 구현 및 HR/설정 기능 개선

신규 페이지:
- 회계관리: 거래처, 예상비용, 청구서, 발주서
- 게시판: 공지사항, 자료실, 커뮤니티
- 고객센터: 문의/FAQ
- 설정: 계정, 알림, 출퇴근, 팝업, 구독, 결제내역
- 리포트 (차트 시각화)
- 개발자 테스트 URL 페이지

기능 개선:
- HR 직원관리/휴가관리/카드관리 강화
- IntegratedListTemplateV2 확장
- AuthenticatedLayout 패딩 표준화
- 로그인 페이지 UI 개선

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2025-12-19 19:12:34 +09:00
parent d742c0ce26
commit c6b605200d
213 changed files with 32644 additions and 775 deletions

View File

@@ -0,0 +1,452 @@
'use client';
/**
* 알림설정 페이지
*
* 각 알림 유형별로 ON/OFF 토글과 이메일 알림 체크박스를 제공합니다.
*
* TODO: API 연동 시 작업 사항
* - GET /api/settings/notifications: 설정값 조회
* - PUT /api/settings/notifications: 설정값 저장
*/
import { useState } from 'react';
import { PageLayout } from '@/components/organisms/PageLayout';
import { PageHeader } from '@/components/organisms/PageHeader';
import { Bell, Save } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardTitle } from '@/components/ui/card';
import { Checkbox } from '@/components/ui/checkbox';
import { Switch } from '@/components/ui/switch';
import { toast } from 'sonner';
import type { NotificationSettings, NotificationItem } from './types';
import { DEFAULT_NOTIFICATION_SETTINGS } from './types';
// 알림 항목 컴포넌트
interface NotificationItemRowProps {
label: string;
item: NotificationItem;
onChange: (item: NotificationItem) => void;
disabled?: boolean;
}
function NotificationItemRow({ label, item, onChange, disabled }: NotificationItemRowProps) {
return (
<div className="flex items-center justify-between py-3 border-b last:border-b-0">
<div className="flex items-center gap-4 flex-1">
<span className="text-sm min-w-[160px]">{label}</span>
<label className="flex items-center gap-2 cursor-pointer">
<Checkbox
checked={item.email}
onCheckedChange={(checked) =>
onChange({ ...item, email: checked === true })
}
disabled={disabled || !item.enabled}
/>
<span className="text-sm text-muted-foreground"></span>
</label>
</div>
<Switch
checked={item.enabled}
onCheckedChange={(checked) =>
onChange({ ...item, enabled: checked, email: checked ? item.email : false })
}
disabled={disabled}
/>
</div>
);
}
// 알림 섹션 컴포넌트
interface NotificationSectionProps {
title: string;
enabled: boolean;
onEnabledChange: (enabled: boolean) => void;
children: React.ReactNode;
}
function NotificationSection({ title, enabled, onEnabledChange, children }: NotificationSectionProps) {
console.log(`[NotificationSection] ${title} enabled:`, enabled);
return (
<Card>
<div className="flex items-center justify-between px-6 pt-6 pb-3">
<CardTitle className="text-base font-medium">{title}</CardTitle>
<Switch
checked={enabled}
onCheckedChange={(checked) => {
console.log(`[Switch] ${title} clicked:`, checked);
onEnabledChange(checked);
}}
/>
</div>
<CardContent className="pt-0">
<div className="pl-4">
{children}
</div>
</CardContent>
</Card>
);
}
export function NotificationSettingsManagement() {
const [settings, setSettings] = useState<NotificationSettings>(DEFAULT_NOTIFICATION_SETTINGS);
// 공지 알림 핸들러
const handleNoticeEnabledChange = (enabled: boolean) => {
setSettings(prev => ({
...prev,
notice: {
...prev.notice,
enabled,
...(enabled ? {} : {
notice: { ...prev.notice.notice, enabled: false, email: false },
event: { ...prev.notice.event, enabled: false, email: false },
}),
},
}));
};
const handleNoticeItemChange = (key: 'notice' | 'event', item: NotificationItem) => {
setSettings(prev => ({
...prev,
notice: { ...prev.notice, [key]: item },
}));
};
// 일정 알림 핸들러
const handleScheduleEnabledChange = (enabled: boolean) => {
setSettings(prev => ({
...prev,
schedule: {
...prev.schedule,
enabled,
...(enabled ? {} : {
vatReport: { ...prev.schedule.vatReport, enabled: false, email: false },
incomeTaxReport: { ...prev.schedule.incomeTaxReport, enabled: false, email: false },
}),
},
}));
};
const handleScheduleItemChange = (key: 'vatReport' | 'incomeTaxReport', item: NotificationItem) => {
setSettings(prev => ({
...prev,
schedule: { ...prev.schedule, [key]: item },
}));
};
// 거래처 알림 핸들러
const handleVendorEnabledChange = (enabled: boolean) => {
setSettings(prev => ({
...prev,
vendor: {
...prev.vendor,
enabled,
...(enabled ? {} : {
newVendor: { ...prev.vendor.newVendor, enabled: false, email: false },
creditRating: { ...prev.vendor.creditRating, enabled: false, email: false },
}),
},
}));
};
const handleVendorItemChange = (key: 'newVendor' | 'creditRating', item: NotificationItem) => {
setSettings(prev => ({
...prev,
vendor: { ...prev.vendor, [key]: item },
}));
};
// 근태 알림 핸들러
const handleAttendanceEnabledChange = (enabled: boolean) => {
setSettings(prev => ({
...prev,
attendance: {
...prev.attendance,
enabled,
...(enabled ? {} : {
annualLeave: { ...prev.attendance.annualLeave, enabled: false, email: false },
clockIn: { ...prev.attendance.clockIn, enabled: false, email: false },
late: { ...prev.attendance.late, enabled: false, email: false },
absent: { ...prev.attendance.absent, enabled: false, email: false },
}),
},
}));
};
const handleAttendanceItemChange = (
key: 'annualLeave' | 'clockIn' | 'late' | 'absent',
item: NotificationItem
) => {
setSettings(prev => ({
...prev,
attendance: { ...prev.attendance, [key]: item },
}));
};
// 수주/발주 알림 핸들러
const handleOrderEnabledChange = (enabled: boolean) => {
setSettings(prev => ({
...prev,
order: {
...prev.order,
enabled,
...(enabled ? {} : {
salesOrder: { ...prev.order.salesOrder, enabled: false, email: false },
purchaseOrder: { ...prev.order.purchaseOrder, enabled: false, email: false },
}),
},
}));
};
const handleOrderItemChange = (key: 'salesOrder' | 'purchaseOrder', item: NotificationItem) => {
setSettings(prev => ({
...prev,
order: { ...prev.order, [key]: item },
}));
};
// 전자결재 알림 핸들러
const handleApprovalEnabledChange = (enabled: boolean) => {
setSettings(prev => ({
...prev,
approval: {
...prev.approval,
enabled,
...(enabled ? {} : {
approvalRequest: { ...prev.approval.approvalRequest, enabled: false, email: false },
draftApproved: { ...prev.approval.draftApproved, enabled: false, email: false },
draftRejected: { ...prev.approval.draftRejected, enabled: false, email: false },
draftCompleted: { ...prev.approval.draftCompleted, enabled: false, email: false },
}),
},
}));
};
const handleApprovalItemChange = (
key: 'approvalRequest' | 'draftApproved' | 'draftRejected' | 'draftCompleted',
item: NotificationItem
) => {
setSettings(prev => ({
...prev,
approval: { ...prev.approval, [key]: item },
}));
};
// 생산 알림 핸들러
const handleProductionEnabledChange = (enabled: boolean) => {
setSettings(prev => ({
...prev,
production: {
...prev.production,
enabled,
...(enabled ? {} : {
safetyStock: { ...prev.production.safetyStock, enabled: false, email: false },
productionComplete: { ...prev.production.productionComplete, enabled: false, email: false },
}),
},
}));
};
const handleProductionItemChange = (
key: 'safetyStock' | 'productionComplete',
item: NotificationItem
) => {
setSettings(prev => ({
...prev,
production: { ...prev.production, [key]: item },
}));
};
// 저장
const handleSave = () => {
// TODO: API 호출로 설정 저장
console.log('저장할 알림 설정:', settings);
toast.success('알림 설정이 저장되었습니다.');
};
return (
<PageLayout>
<PageHeader
title="알림설정"
description="알림 설정을 관리합니다."
icon={Bell}
/>
<div className="space-y-4">
{/* 공지 알림 */}
<NotificationSection
title="공지 알림"
enabled={settings.notice.enabled}
onEnabledChange={handleNoticeEnabledChange}
>
<NotificationItemRow
label="공지사항 알림"
item={settings.notice.notice}
onChange={(item) => handleNoticeItemChange('notice', item)}
disabled={!settings.notice.enabled}
/>
<NotificationItemRow
label="이벤트 알림"
item={settings.notice.event}
onChange={(item) => handleNoticeItemChange('event', item)}
disabled={!settings.notice.enabled}
/>
</NotificationSection>
{/* 일정 알림 */}
<NotificationSection
title="일정 알림"
enabled={settings.schedule.enabled}
onEnabledChange={handleScheduleEnabledChange}
>
<NotificationItemRow
label="부가세 신고 알림"
item={settings.schedule.vatReport}
onChange={(item) => handleScheduleItemChange('vatReport', item)}
disabled={!settings.schedule.enabled}
/>
<NotificationItemRow
label="종합소득세 신고 알림"
item={settings.schedule.incomeTaxReport}
onChange={(item) => handleScheduleItemChange('incomeTaxReport', item)}
disabled={!settings.schedule.enabled}
/>
</NotificationSection>
{/* 거래처 알림 */}
<NotificationSection
title="거래처 알림"
enabled={settings.vendor.enabled}
onEnabledChange={handleVendorEnabledChange}
>
<NotificationItemRow
label="신규 업체 등록 알림"
item={settings.vendor.newVendor}
onChange={(item) => handleVendorItemChange('newVendor', item)}
disabled={!settings.vendor.enabled}
/>
<NotificationItemRow
label="신용등급 등록 알림"
item={settings.vendor.creditRating}
onChange={(item) => handleVendorItemChange('creditRating', item)}
disabled={!settings.vendor.enabled}
/>
</NotificationSection>
{/* 근태 알림 */}
<NotificationSection
title="근태 알림"
enabled={settings.attendance.enabled}
onEnabledChange={handleAttendanceEnabledChange}
>
<NotificationItemRow
label="연차 알림"
item={settings.attendance.annualLeave}
onChange={(item) => handleAttendanceItemChange('annualLeave', item)}
disabled={!settings.attendance.enabled}
/>
<NotificationItemRow
label="출근 알림"
item={settings.attendance.clockIn}
onChange={(item) => handleAttendanceItemChange('clockIn', item)}
disabled={!settings.attendance.enabled}
/>
<NotificationItemRow
label="지각 알림"
item={settings.attendance.late}
onChange={(item) => handleAttendanceItemChange('late', item)}
disabled={!settings.attendance.enabled}
/>
<NotificationItemRow
label="결근 알림"
item={settings.attendance.absent}
onChange={(item) => handleAttendanceItemChange('absent', item)}
disabled={!settings.attendance.enabled}
/>
</NotificationSection>
{/* 수주/발주 알림 */}
<NotificationSection
title="수주/발주 알림"
enabled={settings.order.enabled}
onEnabledChange={handleOrderEnabledChange}
>
<NotificationItemRow
label="수주 등록 알림"
item={settings.order.salesOrder}
onChange={(item) => handleOrderItemChange('salesOrder', item)}
disabled={!settings.order.enabled}
/>
<NotificationItemRow
label="발주 알림"
item={settings.order.purchaseOrder}
onChange={(item) => handleOrderItemChange('purchaseOrder', item)}
disabled={!settings.order.enabled}
/>
</NotificationSection>
{/* 전자결재 알림 */}
<NotificationSection
title="전자결재 알림"
enabled={settings.approval.enabled}
onEnabledChange={handleApprovalEnabledChange}
>
<NotificationItemRow
label="결재요청 알림"
item={settings.approval.approvalRequest}
onChange={(item) => handleApprovalItemChange('approvalRequest', item)}
disabled={!settings.approval.enabled}
/>
<NotificationItemRow
label="기안 > 승인 알림"
item={settings.approval.draftApproved}
onChange={(item) => handleApprovalItemChange('draftApproved', item)}
disabled={!settings.approval.enabled}
/>
<NotificationItemRow
label="기안 > 반려 알림"
item={settings.approval.draftRejected}
onChange={(item) => handleApprovalItemChange('draftRejected', item)}
disabled={!settings.approval.enabled}
/>
<NotificationItemRow
label="기안 > 완료 알림"
item={settings.approval.draftCompleted}
onChange={(item) => handleApprovalItemChange('draftCompleted', item)}
disabled={!settings.approval.enabled}
/>
</NotificationSection>
{/* 생산 알림 */}
<NotificationSection
title="생산 알림"
enabled={settings.production.enabled}
onEnabledChange={handleProductionEnabledChange}
>
<NotificationItemRow
label="안전재고 알림"
item={settings.production.safetyStock}
onChange={(item) => handleProductionItemChange('safetyStock', item)}
disabled={!settings.production.enabled}
/>
<NotificationItemRow
label="생산완료 알림"
item={settings.production.productionComplete}
onChange={(item) => handleProductionItemChange('productionComplete', item)}
disabled={!settings.production.enabled}
/>
</NotificationSection>
{/* 저장 버튼 */}
<div className="flex justify-end pt-4">
<Button onClick={handleSave} size="lg">
<Save className="h-4 w-4 mr-2" />
</Button>
</div>
</div>
</PageLayout>
);
}

View File

@@ -0,0 +1,121 @@
/**
* 알림 설정 타입 정의
*/
// 개별 알림 항목 설정
export interface NotificationItem {
enabled: boolean;
email: boolean;
}
// 공지 알림 설정
export interface NoticeNotificationSettings {
enabled: boolean; // 섹션 전체 토글
notice: NotificationItem; // 공지사항 알림
event: NotificationItem; // 이벤트 알림
}
// 일정 알림 설정
export interface ScheduleNotificationSettings {
enabled: boolean;
vatReport: NotificationItem; // 부가세 신고 알림
incomeTaxReport: NotificationItem; // 종합소득세 신고 알림
}
// 거래처 알림 설정
export interface VendorNotificationSettings {
enabled: boolean;
newVendor: NotificationItem; // 신규 업체 등록 알림
creditRating: NotificationItem; // 신용등급 등록 알림
}
// 근태 알림 설정
export interface AttendanceNotificationSettings {
enabled: boolean;
annualLeave: NotificationItem; // 연차 알림
clockIn: NotificationItem; // 출근 알림
late: NotificationItem; // 지각 알림
absent: NotificationItem; // 결근 알림
}
// 수주/발주 알림 설정
export interface OrderNotificationSettings {
enabled: boolean;
salesOrder: NotificationItem; // 수주 등록 알림
purchaseOrder: NotificationItem; // 발주 알림
}
// 전자결재 알림 설정
export interface ApprovalNotificationSettings {
enabled: boolean;
approvalRequest: NotificationItem; // 결재요청 알림
draftApproved: NotificationItem; // 기안 > 승인 알림
draftRejected: NotificationItem; // 기안 > 반려 알림
draftCompleted: NotificationItem; // 기안 > 완료 알림
}
// 생산 알림 설정
export interface ProductionNotificationSettings {
enabled: boolean;
safetyStock: NotificationItem; // 안전재고 알림
productionComplete: NotificationItem; // 생산완료 알림
}
// 전체 알림 설정
export interface NotificationSettings {
notice: NoticeNotificationSettings;
schedule: ScheduleNotificationSettings;
vendor: VendorNotificationSettings;
attendance: AttendanceNotificationSettings;
order: OrderNotificationSettings;
approval: ApprovalNotificationSettings;
production: ProductionNotificationSettings;
}
// 기본값
export const DEFAULT_NOTIFICATION_ITEM: NotificationItem = {
enabled: false,
email: false,
};
export const DEFAULT_NOTIFICATION_SETTINGS: NotificationSettings = {
notice: {
enabled: false,
notice: { enabled: false, email: false },
event: { enabled: true, email: false },
},
schedule: {
enabled: false,
vatReport: { enabled: false, email: false },
incomeTaxReport: { enabled: true, email: false },
},
vendor: {
enabled: false,
newVendor: { enabled: false, email: false },
creditRating: { enabled: true, email: false },
},
attendance: {
enabled: false,
annualLeave: { enabled: false, email: false },
clockIn: { enabled: true, email: false },
late: { enabled: false, email: false },
absent: { enabled: true, email: false },
},
order: {
enabled: false,
salesOrder: { enabled: false, email: false },
purchaseOrder: { enabled: true, email: false },
},
approval: {
enabled: false,
approvalRequest: { enabled: false, email: false },
draftApproved: { enabled: true, email: false },
draftRejected: { enabled: false, email: false },
draftCompleted: { enabled: true, email: false },
},
production: {
enabled: false,
safetyStock: { enabled: false, email: false },
productionComplete: { enabled: true, email: false },
},
};