chore(WEB): CEO 대시보드 개선 및 모바일 테스트 계획 추가

- CEO 대시보드: 일일보고, 접대비, 복리후생 섹션 개선
- CEO 대시보드: 상세 모달 기능 확장
- 카드거래조회: 기능 및 타입 확장
- 알림설정: 항목 설정 다이얼로그 추가
- 회사정보관리: 컴포넌트 개선
- 모바일 오버플로우 테스트 계획서 추가 (Galaxy Fold 대응)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2026-01-09 16:02:04 +09:00
parent f92393f898
commit e4af3232dd
13 changed files with 1924 additions and 199 deletions

View File

@@ -4,12 +4,13 @@
* 알림설정 페이지
*
* 각 알림 유형별로 ON/OFF 토글, 알림 소리 선택, 이메일 알림 체크박스를 제공합니다.
* 항목 설정 기능으로 표시할 알림 카테고리/항목을 선택할 수 있습니다.
*/
import { useState } from 'react';
import { useState, useCallback } from 'react';
import { PageLayout } from '@/components/organisms/PageLayout';
import { PageHeader } from '@/components/organisms/PageHeader';
import { Bell, Save, Play } from 'lucide-react';
import { Bell, Save, Play, Settings } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardTitle } from '@/components/ui/card';
import { Checkbox } from '@/components/ui/checkbox';
@@ -22,9 +23,10 @@ import {
SelectValue,
} from '@/components/ui/select';
import { toast } from 'sonner';
import type { NotificationSettings, NotificationItem, SoundType } from './types';
import { SOUND_OPTIONS } from './types';
import type { NotificationSettings, NotificationItem, SoundType, ItemVisibilitySettings } from './types';
import { SOUND_OPTIONS, DEFAULT_ITEM_VISIBILITY } from './types';
import { saveNotificationSettings } from './actions';
import { ItemSettingsDialog } from './ItemSettingsDialog';
// 미리듣기 함수
function playPreviewSound(soundType: SoundType) {
@@ -153,9 +155,28 @@ interface NotificationSettingsManagementProps {
initialData: NotificationSettings;
}
const ITEM_VISIBILITY_STORAGE_KEY = 'notification-item-visibility';
export function NotificationSettingsManagement({ initialData }: NotificationSettingsManagementProps) {
const [settings, setSettings] = useState<NotificationSettings>(initialData);
// 항목 설정 (표시/숨김)
const [itemVisibility, setItemVisibility] = useState<ItemVisibilitySettings>(() => {
if (typeof window === 'undefined') return DEFAULT_ITEM_VISIBILITY;
const saved = localStorage.getItem(ITEM_VISIBILITY_STORAGE_KEY);
return saved ? JSON.parse(saved) : DEFAULT_ITEM_VISIBILITY;
});
// 항목 설정 모달 상태
const [isItemSettingsOpen, setIsItemSettingsOpen] = useState(false);
// 항목 설정 저장
const handleItemVisibilitySave = useCallback((newSettings: ItemVisibilitySettings) => {
setItemVisibility(newSettings);
localStorage.setItem(ITEM_VISIBILITY_STORAGE_KEY, JSON.stringify(newSettings));
toast.success('항목 설정이 저장되었습니다.');
}, []);
// 공지 알림 핸들러
const handleNoticeEnabledChange = (enabled: boolean) => {
setSettings(prev => ({
@@ -342,185 +363,245 @@ export function NotificationSettingsManagement({ initialData }: NotificationSett
icon={Bell}
/>
{/* 상단 버튼 영역 */}
<div className="flex justify-end gap-2 mb-4">
<Button
variant="outline"
onClick={() => setIsItemSettingsOpen(true)}
className="bg-orange-500 hover:bg-orange-600 text-white border-orange-500"
>
<Settings className="h-4 w-4 mr-2" />
</Button>
<Button onClick={handleSave}>
<Save className="h-4 w-4 mr-2" />
</Button>
</div>
<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>
{itemVisibility.notice.enabled && (
<NotificationSection
title="공지 알림"
enabled={settings.notice.enabled}
onEnabledChange={handleNoticeEnabledChange}
>
{itemVisibility.notice.notice && (
<NotificationItemRow
label="공지사항 알림"
item={settings.notice.notice}
onChange={(item) => handleNoticeItemChange('notice', item)}
disabled={!settings.notice.enabled}
/>
)}
{itemVisibility.notice.event && (
<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>
{itemVisibility.schedule.enabled && (
<NotificationSection
title="일정 알림"
enabled={settings.schedule.enabled}
onEnabledChange={handleScheduleEnabledChange}
>
{itemVisibility.schedule.vatReport && (
<NotificationItemRow
label="부가세 신고 알림"
item={settings.schedule.vatReport}
onChange={(item) => handleScheduleItemChange('vatReport', item)}
disabled={!settings.schedule.enabled}
/>
)}
{itemVisibility.schedule.incomeTaxReport && (
<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>
{itemVisibility.vendor.enabled && (
<NotificationSection
title="거래처 알림"
enabled={settings.vendor.enabled}
onEnabledChange={handleVendorEnabledChange}
>
{itemVisibility.vendor.newVendor && (
<NotificationItemRow
label="신규 업체 등록 알림"
item={settings.vendor.newVendor}
onChange={(item) => handleVendorItemChange('newVendor', item)}
disabled={!settings.vendor.enabled}
/>
)}
{itemVisibility.vendor.creditRating && (
<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>
{itemVisibility.attendance.enabled && (
<NotificationSection
title="근태 알림"
enabled={settings.attendance.enabled}
onEnabledChange={handleAttendanceEnabledChange}
>
{itemVisibility.attendance.annualLeave && (
<NotificationItemRow
label="연차 알림"
item={settings.attendance.annualLeave}
onChange={(item) => handleAttendanceItemChange('annualLeave', item)}
disabled={!settings.attendance.enabled}
/>
)}
{itemVisibility.attendance.clockIn && (
<NotificationItemRow
label="출근 알림"
item={settings.attendance.clockIn}
onChange={(item) => handleAttendanceItemChange('clockIn', item)}
disabled={!settings.attendance.enabled}
/>
)}
{itemVisibility.attendance.late && (
<NotificationItemRow
label="지각 알림"
item={settings.attendance.late}
onChange={(item) => handleAttendanceItemChange('late', item)}
disabled={!settings.attendance.enabled}
/>
)}
{itemVisibility.attendance.absent && (
<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}
/>
<NotificationItemRow
label="결재요청 알림"
item={settings.order.approvalRequest}
onChange={(item) => handleOrderItemChange('approvalRequest', item)}
disabled={!settings.order.enabled}
/>
</NotificationSection>
{itemVisibility.order.enabled && (
<NotificationSection
title="수주/발주 알림"
enabled={settings.order.enabled}
onEnabledChange={handleOrderEnabledChange}
>
{itemVisibility.order.salesOrder && (
<NotificationItemRow
label="수주 등록 알림"
item={settings.order.salesOrder}
onChange={(item) => handleOrderItemChange('salesOrder', item)}
disabled={!settings.order.enabled}
/>
)}
{itemVisibility.order.purchaseOrder && (
<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>
{itemVisibility.approval.enabled && (
<NotificationSection
title="전자결재 알림"
enabled={settings.approval.enabled}
onEnabledChange={handleApprovalEnabledChange}
>
{itemVisibility.approval.approvalRequest && (
<NotificationItemRow
label="결재요청 알림"
item={settings.approval.approvalRequest}
onChange={(item) => handleApprovalItemChange('approvalRequest', item)}
disabled={!settings.approval.enabled}
/>
)}
{itemVisibility.approval.draftApproved && (
<NotificationItemRow
label="기안 > 승인 알림"
item={settings.approval.draftApproved}
onChange={(item) => handleApprovalItemChange('draftApproved', item)}
disabled={!settings.approval.enabled}
/>
)}
{itemVisibility.approval.draftRejected && (
<NotificationItemRow
label="기안 > 반려 알림"
item={settings.approval.draftRejected}
onChange={(item) => handleApprovalItemChange('draftRejected', item)}
disabled={!settings.approval.enabled}
/>
)}
{itemVisibility.approval.draftCompleted && (
<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>
{itemVisibility.production.enabled && (
<NotificationSection
title="생산 알림"
enabled={settings.production.enabled}
onEnabledChange={handleProductionEnabledChange}
>
{itemVisibility.production.safetyStock && (
<NotificationItemRow
label="안전재고 알림"
item={settings.production.safetyStock}
onChange={(item) => handleProductionItemChange('safetyStock', item)}
disabled={!settings.production.enabled}
/>
)}
{itemVisibility.production.productionComplete && (
<NotificationItemRow
label="생산완료 알림"
item={settings.production.productionComplete}
onChange={(item) => handleProductionItemChange('productionComplete', item)}
disabled={!settings.production.enabled}
/>
)}
</NotificationSection>
)}
</div>
{/* 항목 설정 모달 */}
<ItemSettingsDialog
isOpen={isItemSettingsOpen}
onClose={() => setIsItemSettingsOpen(false)}
settings={itemVisibility}
onSave={handleItemVisibilitySave}
/>
</PageLayout>
);
}