chore(WEB): CEO 대시보드 개선 및 모바일 테스트 계획 추가
- CEO 대시보드: 일일보고, 접대비, 복리후생 섹션 개선 - CEO 대시보드: 상세 모달 기능 확장 - 카드거래조회: 기능 및 타입 확장 - 알림설정: 항목 설정 다이얼로그 추가 - 회사정보관리: 컴포넌트 개선 - 모바일 오버플로우 테스트 계획서 추가 (Galaxy Fold 대응) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -393,8 +393,8 @@ export function CompanyInfoManagement() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 담당자명 / 담당자 연락처 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* 담당자명 / 담당자 연락처 - 임시 주석처리 (추후 사용 가능) */}
|
||||
{/* <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="managerName">담당자명</Label>
|
||||
<Input
|
||||
@@ -415,7 +415,7 @@ export function CompanyInfoManagement() {
|
||||
disabled={!isEditMode}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
{/* 사업자등록증 / 사업자등록번호 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* 알림설정 항목 설정 모달
|
||||
*
|
||||
* 각 알림 카테고리와 항목의 표시/숨김을 설정합니다.
|
||||
*/
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import { X } from 'lucide-react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import type { ItemVisibilitySettings } from './types';
|
||||
|
||||
interface ItemSettingsDialogProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
settings: ItemVisibilitySettings;
|
||||
onSave: (settings: ItemVisibilitySettings) => void;
|
||||
}
|
||||
|
||||
// 카테고리 섹션 컴포넌트
|
||||
interface CategorySectionProps {
|
||||
title: string;
|
||||
enabled: boolean;
|
||||
onEnabledChange: (enabled: boolean) => void;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
function CategorySection({ title, enabled, onEnabledChange, children }: CategorySectionProps) {
|
||||
return (
|
||||
<div className="bg-gray-800 rounded-lg overflow-hidden">
|
||||
{/* 카테고리 헤더 */}
|
||||
<div className="flex items-center justify-between px-4 py-3">
|
||||
<span className="text-white font-medium">{title}</span>
|
||||
<Switch
|
||||
checked={enabled}
|
||||
onCheckedChange={onEnabledChange}
|
||||
className="data-[state=checked]:bg-blue-500"
|
||||
/>
|
||||
</div>
|
||||
{/* 하위 항목 */}
|
||||
<div className="bg-gray-700 px-4 py-2 space-y-2">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 항목 행 컴포넌트
|
||||
interface ItemRowProps {
|
||||
label: string;
|
||||
checked: boolean;
|
||||
onChange: (checked: boolean) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
function ItemRow({ label, checked, onChange, disabled }: ItemRowProps) {
|
||||
return (
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<span className="text-gray-300 text-sm">{label}</span>
|
||||
<Switch
|
||||
checked={checked}
|
||||
onCheckedChange={onChange}
|
||||
disabled={disabled}
|
||||
className="data-[state=checked]:bg-blue-500 scale-90"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ItemSettingsDialog({ isOpen, onClose, settings, onSave }: ItemSettingsDialogProps) {
|
||||
const [localSettings, setLocalSettings] = useState<ItemVisibilitySettings>(settings);
|
||||
|
||||
// 모달 열릴 때 설정 동기화
|
||||
const handleOpenChange = useCallback((open: boolean) => {
|
||||
if (open) {
|
||||
setLocalSettings(settings);
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
}, [settings, onClose]);
|
||||
|
||||
// 카테고리 전체 토글
|
||||
const handleCategoryToggle = useCallback((
|
||||
category: keyof ItemVisibilitySettings,
|
||||
enabled: boolean
|
||||
) => {
|
||||
setLocalSettings(prev => {
|
||||
const categorySettings = prev[category];
|
||||
const updatedCategory: Record<string, boolean> = { enabled };
|
||||
|
||||
// 모든 하위 항목도 같이 토글
|
||||
Object.keys(categorySettings).forEach(key => {
|
||||
if (key !== 'enabled') {
|
||||
updatedCategory[key] = enabled;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
...prev,
|
||||
[category]: updatedCategory as typeof categorySettings,
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
// 개별 항목 토글
|
||||
const handleItemToggle = useCallback((
|
||||
category: keyof ItemVisibilitySettings,
|
||||
item: string,
|
||||
checked: boolean
|
||||
) => {
|
||||
setLocalSettings(prev => ({
|
||||
...prev,
|
||||
[category]: {
|
||||
...prev[category],
|
||||
[item]: checked,
|
||||
},
|
||||
}));
|
||||
}, []);
|
||||
|
||||
// 저장
|
||||
const handleSave = useCallback(() => {
|
||||
onSave(localSettings);
|
||||
onClose();
|
||||
}, [localSettings, onSave, onClose]);
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
|
||||
<DialogContent className="!w-[400px] !max-w-[400px] max-h-[80vh] overflow-y-auto p-0 bg-gray-900 border-gray-700">
|
||||
{/* 헤더 */}
|
||||
<DialogHeader className="sticky top-0 bg-gray-900 z-10 px-4 py-3 border-b border-gray-700">
|
||||
<div className="flex items-center justify-between">
|
||||
<DialogTitle className="text-white font-medium">항목 설정</DialogTitle>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="p-1 hover:bg-gray-800 rounded transition-colors"
|
||||
>
|
||||
<X className="h-5 w-5 text-gray-400" />
|
||||
</button>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="p-4 space-y-3">
|
||||
{/* 공지 알림 */}
|
||||
<CategorySection
|
||||
title="공지 알림"
|
||||
enabled={localSettings.notice.enabled}
|
||||
onEnabledChange={(enabled) => handleCategoryToggle('notice', enabled)}
|
||||
>
|
||||
<ItemRow
|
||||
label="공지사항 알림"
|
||||
checked={localSettings.notice.notice}
|
||||
onChange={(checked) => handleItemToggle('notice', 'notice', checked)}
|
||||
disabled={!localSettings.notice.enabled}
|
||||
/>
|
||||
<ItemRow
|
||||
label="이벤트 알림"
|
||||
checked={localSettings.notice.event}
|
||||
onChange={(checked) => handleItemToggle('notice', 'event', checked)}
|
||||
disabled={!localSettings.notice.enabled}
|
||||
/>
|
||||
</CategorySection>
|
||||
|
||||
{/* 일정 알림 */}
|
||||
<CategorySection
|
||||
title="일정 알림"
|
||||
enabled={localSettings.schedule.enabled}
|
||||
onEnabledChange={(enabled) => handleCategoryToggle('schedule', enabled)}
|
||||
>
|
||||
<ItemRow
|
||||
label="부가세 신고 알림"
|
||||
checked={localSettings.schedule.vatReport}
|
||||
onChange={(checked) => handleItemToggle('schedule', 'vatReport', checked)}
|
||||
disabled={!localSettings.schedule.enabled}
|
||||
/>
|
||||
<ItemRow
|
||||
label="종합소득세 신고 알림"
|
||||
checked={localSettings.schedule.incomeTaxReport}
|
||||
onChange={(checked) => handleItemToggle('schedule', 'incomeTaxReport', checked)}
|
||||
disabled={!localSettings.schedule.enabled}
|
||||
/>
|
||||
</CategorySection>
|
||||
|
||||
{/* 거래처 알림 */}
|
||||
<CategorySection
|
||||
title="거래처 알림"
|
||||
enabled={localSettings.vendor.enabled}
|
||||
onEnabledChange={(enabled) => handleCategoryToggle('vendor', enabled)}
|
||||
>
|
||||
<ItemRow
|
||||
label="신규 업체 등록 알림"
|
||||
checked={localSettings.vendor.newVendor}
|
||||
onChange={(checked) => handleItemToggle('vendor', 'newVendor', checked)}
|
||||
disabled={!localSettings.vendor.enabled}
|
||||
/>
|
||||
<ItemRow
|
||||
label="신용등급 알림"
|
||||
checked={localSettings.vendor.creditRating}
|
||||
onChange={(checked) => handleItemToggle('vendor', 'creditRating', checked)}
|
||||
disabled={!localSettings.vendor.enabled}
|
||||
/>
|
||||
</CategorySection>
|
||||
|
||||
{/* 근태 알림 */}
|
||||
<CategorySection
|
||||
title="근태 알림"
|
||||
enabled={localSettings.attendance.enabled}
|
||||
onEnabledChange={(enabled) => handleCategoryToggle('attendance', enabled)}
|
||||
>
|
||||
<ItemRow
|
||||
label="연차 알림"
|
||||
checked={localSettings.attendance.annualLeave}
|
||||
onChange={(checked) => handleItemToggle('attendance', 'annualLeave', checked)}
|
||||
disabled={!localSettings.attendance.enabled}
|
||||
/>
|
||||
<ItemRow
|
||||
label="출근 알림"
|
||||
checked={localSettings.attendance.clockIn}
|
||||
onChange={(checked) => handleItemToggle('attendance', 'clockIn', checked)}
|
||||
disabled={!localSettings.attendance.enabled}
|
||||
/>
|
||||
<ItemRow
|
||||
label="지각 알림"
|
||||
checked={localSettings.attendance.late}
|
||||
onChange={(checked) => handleItemToggle('attendance', 'late', checked)}
|
||||
disabled={!localSettings.attendance.enabled}
|
||||
/>
|
||||
<ItemRow
|
||||
label="결근 알림"
|
||||
checked={localSettings.attendance.absent}
|
||||
onChange={(checked) => handleItemToggle('attendance', 'absent', checked)}
|
||||
disabled={!localSettings.attendance.enabled}
|
||||
/>
|
||||
</CategorySection>
|
||||
|
||||
{/* 수주/발주 알림 */}
|
||||
<CategorySection
|
||||
title="수주/발주 알림"
|
||||
enabled={localSettings.order.enabled}
|
||||
onEnabledChange={(enabled) => handleCategoryToggle('order', enabled)}
|
||||
>
|
||||
<ItemRow
|
||||
label="수주 알림"
|
||||
checked={localSettings.order.salesOrder}
|
||||
onChange={(checked) => handleItemToggle('order', 'salesOrder', checked)}
|
||||
disabled={!localSettings.order.enabled}
|
||||
/>
|
||||
<ItemRow
|
||||
label="발주 알림"
|
||||
checked={localSettings.order.purchaseOrder}
|
||||
onChange={(checked) => handleItemToggle('order', 'purchaseOrder', checked)}
|
||||
disabled={!localSettings.order.enabled}
|
||||
/>
|
||||
</CategorySection>
|
||||
|
||||
{/* 전자결재 알림 */}
|
||||
<CategorySection
|
||||
title="전자결재 알림"
|
||||
enabled={localSettings.approval.enabled}
|
||||
onEnabledChange={(enabled) => handleCategoryToggle('approval', enabled)}
|
||||
>
|
||||
<ItemRow
|
||||
label="결재요청 알림"
|
||||
checked={localSettings.approval.approvalRequest}
|
||||
onChange={(checked) => handleItemToggle('approval', 'approvalRequest', checked)}
|
||||
disabled={!localSettings.approval.enabled}
|
||||
/>
|
||||
<ItemRow
|
||||
label="기안 > 승인 알림"
|
||||
checked={localSettings.approval.draftApproved}
|
||||
onChange={(checked) => handleItemToggle('approval', 'draftApproved', checked)}
|
||||
disabled={!localSettings.approval.enabled}
|
||||
/>
|
||||
<ItemRow
|
||||
label="기안 > 반려 알림"
|
||||
checked={localSettings.approval.draftRejected}
|
||||
onChange={(checked) => handleItemToggle('approval', 'draftRejected', checked)}
|
||||
disabled={!localSettings.approval.enabled}
|
||||
/>
|
||||
<ItemRow
|
||||
label="기안 > 완료 알림"
|
||||
checked={localSettings.approval.draftCompleted}
|
||||
onChange={(checked) => handleItemToggle('approval', 'draftCompleted', checked)}
|
||||
disabled={!localSettings.approval.enabled}
|
||||
/>
|
||||
</CategorySection>
|
||||
|
||||
{/* 생산 알림 */}
|
||||
<CategorySection
|
||||
title="생산 알림"
|
||||
enabled={localSettings.production.enabled}
|
||||
onEnabledChange={(enabled) => handleCategoryToggle('production', enabled)}
|
||||
>
|
||||
<ItemRow
|
||||
label="안전재고 알림"
|
||||
checked={localSettings.production.safetyStock}
|
||||
onChange={(checked) => handleItemToggle('production', 'safetyStock', checked)}
|
||||
disabled={!localSettings.production.enabled}
|
||||
/>
|
||||
<ItemRow
|
||||
label="생산완료 알림"
|
||||
checked={localSettings.production.productionComplete}
|
||||
onChange={(checked) => handleItemToggle('production', 'productionComplete', checked)}
|
||||
disabled={!localSettings.production.enabled}
|
||||
/>
|
||||
</CategorySection>
|
||||
</div>
|
||||
|
||||
{/* 하단 버튼 */}
|
||||
<div className="sticky bottom-0 bg-gray-900 px-4 py-3 border-t border-gray-700 flex justify-center gap-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onClose}
|
||||
className="bg-gray-700 border-gray-600 text-white hover:bg-gray-600 min-w-[80px]"
|
||||
>
|
||||
취소
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
className="bg-gray-700 text-white hover:bg-gray-600 min-w-[80px]"
|
||||
>
|
||||
저장
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -112,6 +112,72 @@ export interface NotificationSettings {
|
||||
production: ProductionNotificationSettings;
|
||||
}
|
||||
|
||||
// ===== 항목 설정 (표시/숨김) 타입 =====
|
||||
|
||||
// 공지 알림 항목 설정
|
||||
export interface NoticeItemVisibility {
|
||||
enabled: boolean;
|
||||
notice: boolean; // 공지사항 알림
|
||||
event: boolean; // 이벤트 알림
|
||||
}
|
||||
|
||||
// 일정 알림 항목 설정
|
||||
export interface ScheduleItemVisibility {
|
||||
enabled: boolean;
|
||||
vatReport: boolean; // 부가세 신고 알림
|
||||
incomeTaxReport: boolean; // 종합소득세 신고 알림
|
||||
}
|
||||
|
||||
// 거래처 알림 항목 설정
|
||||
export interface VendorItemVisibility {
|
||||
enabled: boolean;
|
||||
newVendor: boolean; // 신규 업체 등록 알림
|
||||
creditRating: boolean; // 신용등급 알림
|
||||
}
|
||||
|
||||
// 근태 알림 항목 설정
|
||||
export interface AttendanceItemVisibility {
|
||||
enabled: boolean;
|
||||
annualLeave: boolean; // 연차 알림
|
||||
clockIn: boolean; // 출근 알림
|
||||
late: boolean; // 지각 알림
|
||||
absent: boolean; // 결근 알림
|
||||
}
|
||||
|
||||
// 수주/발주 알림 항목 설정
|
||||
export interface OrderItemVisibility {
|
||||
enabled: boolean;
|
||||
salesOrder: boolean; // 수주 알림
|
||||
purchaseOrder: boolean; // 발주 알림
|
||||
}
|
||||
|
||||
// 전자결재 알림 항목 설정
|
||||
export interface ApprovalItemVisibility {
|
||||
enabled: boolean;
|
||||
approvalRequest: boolean; // 결재요청 알림
|
||||
draftApproved: boolean; // 기안 > 승인 알림
|
||||
draftRejected: boolean; // 기안 > 반려 알림
|
||||
draftCompleted: boolean; // 기안 > 완료 알림
|
||||
}
|
||||
|
||||
// 생산 알림 항목 설정
|
||||
export interface ProductionItemVisibility {
|
||||
enabled: boolean;
|
||||
safetyStock: boolean; // 안전재고 알림
|
||||
productionComplete: boolean; // 생산완료 알림
|
||||
}
|
||||
|
||||
// 전체 항목 설정
|
||||
export interface ItemVisibilitySettings {
|
||||
notice: NoticeItemVisibility;
|
||||
schedule: ScheduleItemVisibility;
|
||||
vendor: VendorItemVisibility;
|
||||
attendance: AttendanceItemVisibility;
|
||||
order: OrderItemVisibility;
|
||||
approval: ApprovalItemVisibility;
|
||||
production: ProductionItemVisibility;
|
||||
}
|
||||
|
||||
// 기본값
|
||||
export const DEFAULT_NOTIFICATION_ITEM: NotificationItem = {
|
||||
enabled: false,
|
||||
@@ -160,4 +226,47 @@ export const DEFAULT_NOTIFICATION_SETTINGS: NotificationSettings = {
|
||||
safetyStock: { enabled: false, email: false, soundType: 'default' },
|
||||
productionComplete: { enabled: true, email: false, soundType: 'sam_voice' },
|
||||
},
|
||||
};
|
||||
|
||||
// 항목 설정 기본값 (모두 표시)
|
||||
export const DEFAULT_ITEM_VISIBILITY: ItemVisibilitySettings = {
|
||||
notice: {
|
||||
enabled: true,
|
||||
notice: true,
|
||||
event: true,
|
||||
},
|
||||
schedule: {
|
||||
enabled: true,
|
||||
vatReport: true,
|
||||
incomeTaxReport: true,
|
||||
},
|
||||
vendor: {
|
||||
enabled: true,
|
||||
newVendor: true,
|
||||
creditRating: true,
|
||||
},
|
||||
attendance: {
|
||||
enabled: true,
|
||||
annualLeave: true,
|
||||
clockIn: true,
|
||||
late: true,
|
||||
absent: true,
|
||||
},
|
||||
order: {
|
||||
enabled: true,
|
||||
salesOrder: true,
|
||||
purchaseOrder: true,
|
||||
},
|
||||
approval: {
|
||||
enabled: true,
|
||||
approvalRequest: true,
|
||||
draftApproved: true,
|
||||
draftRejected: true,
|
||||
draftCompleted: true,
|
||||
},
|
||||
production: {
|
||||
enabled: true,
|
||||
safetyStock: true,
|
||||
productionComplete: true,
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user