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,116 @@
'use client';
import { useRouter } from 'next/navigation';
import { PageLayout } from '@/components/organisms/PageLayout';
import { PageHeader } from '@/components/organisms/PageHeader';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { ClipboardList, ArrowLeft, Edit, Trash2 } from 'lucide-react';
import type { Board } from './types';
import {
BOARD_STATUS_LABELS,
BOARD_STATUS_COLORS,
BOARD_TARGET_LABELS,
} from './types';
interface BoardDetailProps {
board: Board;
onEdit: () => void;
onDelete: () => void;
}
// 날짜/시간 포맷
const formatDateTime = (dateString: string): string => {
const date = new Date(dateString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
};
export function BoardDetail({ board, onEdit, onDelete }: BoardDetailProps) {
const router = useRouter();
const handleBack = () => {
router.push('/ko/board/board-management');
};
// 대상 표시 텍스트
const getTargetDisplay = () => {
if (board.target === 'all') {
return BOARD_TARGET_LABELS.all;
}
return board.targetName || BOARD_TARGET_LABELS.department;
};
return (
<PageLayout>
<PageHeader
title="게시판관리 상세"
description="게시판 목록을 관리합니다"
icon={ClipboardList}
/>
<div className="space-y-6">
{/* 게시판 정보 */}
<Card>
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle className="text-base"> </CardTitle>
<Badge className={BOARD_STATUS_COLORS[board.status]}>
{BOARD_STATUS_LABELS[board.status]}
</Badge>
</CardHeader>
<CardContent>
<dl className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<dt className="text-sm font-medium text-muted-foreground"></dt>
<dd className="text-sm mt-1">{getTargetDisplay()}</dd>
</div>
<div>
<dt className="text-sm font-medium text-muted-foreground"></dt>
<dd className="text-sm mt-1">{board.authorName}</dd>
</div>
<div>
<dt className="text-sm font-medium text-muted-foreground"></dt>
<dd className="text-sm mt-1">{board.boardName}</dd>
</div>
<div>
<dt className="text-sm font-medium text-muted-foreground"></dt>
<dd className="text-sm mt-1">
<Badge className={BOARD_STATUS_COLORS[board.status]}>
{BOARD_STATUS_LABELS[board.status]}
</Badge>
</dd>
</div>
<div>
<dt className="text-sm font-medium text-muted-foreground"></dt>
<dd className="text-sm mt-1">{formatDateTime(board.createdAt)}</dd>
</div>
</dl>
</CardContent>
</Card>
{/* 버튼 영역 */}
<div className="flex items-center justify-between">
<Button variant="outline" onClick={handleBack}>
<ArrowLeft className="w-4 h-4 mr-2" />
</Button>
<div className="flex items-center gap-2">
<Button variant="outline" onClick={onDelete} className="text-destructive hover:bg-destructive hover:text-destructive-foreground">
<Trash2 className="w-4 h-4 mr-2" />
</Button>
<Button onClick={onEdit}>
<Edit className="w-4 h-4 mr-2" />
</Button>
</div>
</div>
</div>
</PageLayout>
);
}

View File

@@ -0,0 +1,221 @@
'use client';
import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { PageLayout } from '@/components/organisms/PageLayout';
import { PageHeader } from '@/components/organisms/PageHeader';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { ClipboardList, ArrowLeft, Save } from 'lucide-react';
import type { Board, BoardFormData, BoardTarget, BoardStatus } from './types';
import { BOARD_TARGETS, MOCK_DEPARTMENTS, BOARD_STATUS_LABELS } from './types';
interface BoardFormProps {
mode: 'create' | 'edit';
board?: Board;
onSubmit: (data: BoardFormData) => void;
}
// 날짜/시간 포맷
const formatDateTime = (dateString: string): string => {
const date = new Date(dateString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
};
// 현재 날짜/시간
const getCurrentDateTime = (): string => {
return formatDateTime(new Date().toISOString());
};
export function BoardForm({ mode, board, onSubmit }: BoardFormProps) {
const router = useRouter();
const [formData, setFormData] = useState<BoardFormData>({
target: 'all',
targetName: '',
boardName: '',
status: 'active',
});
// 수정 모드일 때 기존 데이터 로드
useEffect(() => {
if (mode === 'edit' && board) {
setFormData({
target: board.target,
targetName: board.targetName || '',
boardName: board.boardName,
status: board.status,
});
}
}, [mode, board]);
const handleBack = () => {
if (mode === 'edit' && board) {
router.push(`/ko/board/board-management/${board.id}`);
} else {
router.push('/ko/board/board-management');
}
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit(formData);
};
const handleTargetChange = (value: BoardTarget) => {
setFormData(prev => ({
...prev,
target: value,
targetName: value === 'all' ? '' : prev.targetName,
}));
};
// 작성자 (현재 로그인한 사용자 - mock)
const currentUser = '홍길동';
// 등록일시
const registeredAt = mode === 'edit' && board ? formatDateTime(board.createdAt) : getCurrentDateTime();
return (
<PageLayout>
<PageHeader
title={mode === 'create' ? '게시판관리 상세' : '게시판관리 상세'}
description="게시판 목록을 관리합니다"
icon={ClipboardList}
/>
<form onSubmit={handleSubmit} className="space-y-6">
{/* 게시판 정보 */}
<Card>
<CardHeader>
<CardTitle className="text-base"> *</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* 대상 */}
<div className="space-y-2">
<Label htmlFor="target"></Label>
<div className="flex gap-2">
<Select
value={formData.target}
onValueChange={(value) => handleTargetChange(value as BoardTarget)}
>
<SelectTrigger id="target" className="w-[120px]">
<SelectValue placeholder="대상 선택" />
</SelectTrigger>
<SelectContent>
{BOARD_TARGETS.map((target) => (
<SelectItem key={target.value} value={target.value}>
{target.label}
</SelectItem>
))}
</SelectContent>
</Select>
{formData.target === 'department' && (
<Select
value={formData.targetName}
onValueChange={(value) => setFormData(prev => ({ ...prev, targetName: value }))}
>
<SelectTrigger className="flex-1">
<SelectValue placeholder="부서 선택" />
</SelectTrigger>
<SelectContent>
{MOCK_DEPARTMENTS.map((dept) => (
<SelectItem key={dept.id} value={dept.name}>
{dept.name}
</SelectItem>
))}
</SelectContent>
</Select>
)}
</div>
</div>
{/* 작성자 */}
<div className="space-y-2">
<Label htmlFor="author"></Label>
<Input
id="author"
value={mode === 'edit' && board ? board.authorName : currentUser}
disabled
className="bg-muted"
/>
</div>
{/* 게시판명 */}
<div className="space-y-2 md:col-span-2">
<Label htmlFor="boardName"></Label>
<Input
id="boardName"
value={formData.boardName}
onChange={(e) => setFormData(prev => ({ ...prev, boardName: e.target.value }))}
placeholder="게시판명을 입력해주세요"
/>
</div>
{/* 상태 */}
<div className="space-y-2">
<Label></Label>
<RadioGroup
value={formData.status}
onValueChange={(value) => setFormData(prev => ({ ...prev, status: value as BoardStatus }))}
className="flex items-center gap-4"
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="inactive" id="inactive" />
<Label htmlFor="inactive" className="font-normal cursor-pointer">
{BOARD_STATUS_LABELS.inactive}
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="active" id="active" />
<Label htmlFor="active" className="font-normal cursor-pointer">
{BOARD_STATUS_LABELS.active}
</Label>
</div>
</RadioGroup>
</div>
{/* 등록일시 */}
<div className="space-y-2">
<Label htmlFor="registeredAt"></Label>
<Input
id="registeredAt"
value={registeredAt}
disabled
className="bg-muted"
/>
</div>
</div>
</CardContent>
</Card>
{/* 버튼 영역 */}
<div className="flex items-center justify-between">
<Button type="button" variant="outline" onClick={handleBack}>
<ArrowLeft className="w-4 h-4 mr-2" />
</Button>
<Button type="submit">
<Save className="w-4 h-4 mr-2" />
{mode === 'create' ? '등록' : '저장'}
</Button>
</div>
</form>
</PageLayout>
);
}

View File

@@ -0,0 +1,467 @@
'use client';
import { useState, useMemo, useCallback } from 'react';
import { useRouter } from 'next/navigation';
import { ClipboardList, Edit, Trash2, Plus } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { TableRow, TableCell } from '@/components/ui/table';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import {
IntegratedListTemplateV2,
type TabOption,
type TableColumn,
} from '@/components/templates/IntegratedListTemplateV2';
import { ListMobileCard, InfoField } from '@/components/organisms/ListMobileCard';
import type { Board, BoardStatus } from './types';
import {
BOARD_STATUS_LABELS,
BOARD_STATUS_COLORS,
BOARD_TARGET_LABELS,
} from './types';
// 날짜 포맷
const formatDate = (dateString: string): string => {
const date = new Date(dateString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
// Mock 데이터
const mockBoards: Board[] = [
{
id: '1',
target: 'all',
boardName: '게시판명',
status: 'active',
authorId: 'u1',
authorName: '홍길동',
createdAt: '2025-09-03T00:00:00Z',
updatedAt: '2025-09-03T00:00:00Z',
},
{
id: '2',
target: 'all',
boardName: '게시판명',
status: 'active',
authorId: 'u1',
authorName: '홍길동',
createdAt: '2025-09-03T00:00:00Z',
updatedAt: '2025-09-03T00:00:00Z',
},
{
id: '3',
target: 'department',
targetName: '부서명',
boardName: '게시판명',
status: 'active',
authorId: 'u1',
authorName: '홍길동',
createdAt: '2025-09-03T00:00:00Z',
updatedAt: '2025-09-03T00:00:00Z',
},
{
id: '4',
target: 'all',
boardName: '게시판명',
status: 'active',
authorId: 'u1',
authorName: '홍길동',
createdAt: '2025-09-03T00:00:00Z',
updatedAt: '2025-09-03T00:00:00Z',
},
{
id: '5',
target: 'department',
targetName: '팀명',
boardName: '게시판명',
status: 'inactive',
authorId: 'u1',
authorName: '홍길동',
createdAt: '2025-09-03T00:00:00Z',
updatedAt: '2025-09-03T00:00:00Z',
},
{
id: '6',
target: 'all',
boardName: '게시판명',
status: 'active',
authorId: 'u1',
authorName: '홍길동',
createdAt: '2025-09-03T00:00:00Z',
updatedAt: '2025-09-03T00:00:00Z',
},
{
id: '7',
target: 'all',
boardName: '게시판명',
status: 'active',
authorId: 'u1',
authorName: '홍길동',
createdAt: '2025-09-03T00:00:00Z',
updatedAt: '2025-09-03T00:00:00Z',
},
];
// 추가 Mock 데이터 생성
const generateMockBoards = (): Board[] => {
const boards: Board[] = [...mockBoards];
const targets: Array<'all' | 'department'> = ['all', 'department'];
const departmentNames = ['영업부', '개발부', '인사부', '경영지원부'];
const authorNames = ['홍길동', '김철수', '이영희', '박지훈'];
for (let i = 8; i <= 20; i++) {
const target = targets[i % targets.length];
const status: BoardStatus = i % 5 === 0 ? 'inactive' : 'active';
boards.push({
id: String(i),
target,
targetName: target === 'department' ? departmentNames[i % departmentNames.length] : undefined,
boardName: `게시판${i}`,
status,
authorId: `u${(i % 4) + 1}`,
authorName: authorNames[i % authorNames.length],
createdAt: '2025-09-03T00:00:00Z',
updatedAt: '2025-09-03T00:00:00Z',
});
}
return boards;
};
export function BoardManagement() {
const router = useRouter();
// 게시판 데이터 상태
const [boards, setBoards] = useState<Board[]>(generateMockBoards);
// 검색 및 필터 상태
const [searchValue, setSearchValue] = useState('');
const [activeTab, setActiveTab] = useState<string>('all');
const [currentPage, setCurrentPage] = useState(1);
const itemsPerPage = 20;
// 다이얼로그 상태
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [boardToDelete, setBoardToDelete] = useState<Board | null>(null);
const [selectedItems, setSelectedItems] = useState<Set<string>>(new Set());
// 필터링된 데이터
const filteredBoards = useMemo(() => {
let filtered = boards;
// 탭 필터 (상태)
if (activeTab !== 'all') {
filtered = filtered.filter(b => b.status === activeTab);
}
// 검색 필터
if (searchValue) {
const search = searchValue.toLowerCase();
filtered = filtered.filter(b =>
b.boardName.toLowerCase().includes(search) ||
b.authorName.toLowerCase().includes(search) ||
(b.targetName && b.targetName.toLowerCase().includes(search)) ||
BOARD_TARGET_LABELS[b.target].toLowerCase().includes(search)
);
}
return filtered;
}, [boards, activeTab, searchValue]);
// 페이지네이션된 데이터
const paginatedData = useMemo(() => {
const startIndex = (currentPage - 1) * itemsPerPage;
return filteredBoards.slice(startIndex, startIndex + itemsPerPage);
}, [filteredBoards, currentPage, itemsPerPage]);
// 통계 계산
const stats = useMemo(() => {
const activeCount = boards.filter(b => b.status === 'active').length;
const inactiveCount = boards.filter(b => b.status === 'inactive').length;
return { activeCount, inactiveCount };
}, [boards]);
// 탭 옵션
const tabs: TabOption[] = useMemo(() => [
{ value: 'all', label: '전체', count: boards.length, color: 'gray' },
{ value: 'active', label: '사용', count: stats.activeCount, color: 'green' },
{ value: 'inactive', label: '미사용', count: stats.inactiveCount, color: 'red' },
], [boards.length, stats]);
// 테이블 컬럼 정의
const tableColumns: TableColumn[] = useMemo(() => [
{ key: 'rowNumber', label: 'No.', className: 'w-[60px] text-center' },
{ key: 'target', label: '대상', className: 'min-w-[100px]' },
{ key: 'boardName', label: '게시판명', className: 'min-w-[150px]' },
{ key: 'status', label: '상태', className: 'min-w-[80px]' },
{ key: 'authorName', label: '작성자', className: 'min-w-[100px]' },
{ key: 'createdAt', label: '등록일시', className: 'min-w-[120px]' },
{ key: 'actions', label: '작업', className: 'w-[100px] text-right' },
], []);
// 체크박스 토글
const toggleSelection = useCallback((id: string) => {
setSelectedItems(prev => {
const newSet = new Set(prev);
if (newSet.has(id)) {
newSet.delete(id);
} else {
newSet.add(id);
}
return newSet;
});
}, []);
// 전체 선택/해제
const toggleSelectAll = useCallback(() => {
if (selectedItems.size === paginatedData.length && paginatedData.length > 0) {
setSelectedItems(new Set());
} else {
const allIds = new Set(paginatedData.map((item) => item.id));
setSelectedItems(allIds);
}
}, [selectedItems.size, paginatedData]);
// 일괄 삭제 핸들러
const handleBulkDelete = useCallback(() => {
const ids = Array.from(selectedItems);
setBoards(prev => prev.filter(board => !ids.includes(board.id)));
setSelectedItems(new Set());
}, [selectedItems]);
// 핸들러
const handleAddBoard = useCallback(() => {
router.push('/ko/board/board-management/new');
}, [router]);
const handleDeleteBoard = useCallback(() => {
if (boardToDelete) {
setBoards(prev => prev.filter(board => board.id !== boardToDelete.id));
setDeleteDialogOpen(false);
setBoardToDelete(null);
}
}, [boardToDelete]);
const handleRowClick = useCallback((row: Board) => {
router.push(`/ko/board/board-management/${row.id}`);
}, [router]);
const handleEdit = useCallback((id: string) => {
router.push(`/ko/board/board-management/${id}/edit`);
}, [router]);
const openDeleteDialog = useCallback((board: Board) => {
setBoardToDelete(board);
setDeleteDialogOpen(true);
}, []);
// 대상 표시 텍스트
const getTargetDisplay = (board: Board) => {
if (board.target === 'all') {
return BOARD_TARGET_LABELS.all;
}
return board.targetName || BOARD_TARGET_LABELS.department;
};
// 테이블 행 렌더링
const renderTableRow = useCallback((item: Board, index: number, globalIndex: number) => {
const isSelected = selectedItems.has(item.id);
return (
<TableRow
key={item.id}
className="hover:bg-muted/50 cursor-pointer"
onClick={() => handleRowClick(item)}
>
<TableCell className="text-center" onClick={(e) => e.stopPropagation()}>
<Checkbox
checked={isSelected}
onCheckedChange={() => toggleSelection(item.id)}
/>
</TableCell>
<TableCell className="text-muted-foreground text-center">
{globalIndex}
</TableCell>
<TableCell>{getTargetDisplay(item)}</TableCell>
<TableCell>{item.boardName}</TableCell>
<TableCell>
<Badge className={BOARD_STATUS_COLORS[item.status]}>
{BOARD_STATUS_LABELS[item.status]}
</Badge>
</TableCell>
<TableCell>{item.authorName}</TableCell>
<TableCell>{formatDate(item.createdAt)}</TableCell>
<TableCell className="text-right" onClick={(e) => e.stopPropagation()}>
{isSelected && (
<div className="flex items-center justify-end gap-1">
<Button
variant="ghost"
size="sm"
onClick={() => handleEdit(item.id)}
title="수정"
>
<Edit className="w-4 h-4" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => openDeleteDialog(item)}
title="삭제"
>
<Trash2 className="w-4 h-4 text-red-500" />
</Button>
</div>
)}
</TableCell>
</TableRow>
);
}, [selectedItems, toggleSelection, handleRowClick, handleEdit, openDeleteDialog]);
// 모바일 카드 렌더링
const renderMobileCard = useCallback((
item: Board,
index: number,
globalIndex: number,
isSelected: boolean,
onToggle: () => void
) => {
return (
<ListMobileCard
id={item.id}
title={item.boardName}
headerBadges={
<div className="flex items-center gap-2 flex-wrap">
<Badge variant="outline" className="text-xs">
#{globalIndex}
</Badge>
<span className="text-xs text-muted-foreground">
{getTargetDisplay(item)}
</span>
</div>
}
statusBadge={
<Badge className={BOARD_STATUS_COLORS[item.status]}>
{BOARD_STATUS_LABELS[item.status]}
</Badge>
}
isSelected={isSelected}
onToggleSelection={onToggle}
onCardClick={() => handleRowClick(item)}
infoGrid={
<div className="grid grid-cols-2 gap-x-4 gap-y-3">
<InfoField label="대상" value={getTargetDisplay(item)} />
<InfoField label="작성자" value={item.authorName} />
<InfoField label="등록일시" value={formatDate(item.createdAt)} />
</div>
}
actions={
isSelected ? (
<div className="flex gap-2 flex-wrap">
<Button
variant="default"
size="default"
className="flex-1 min-w-[100px] h-11"
onClick={(e) => { e.stopPropagation(); handleEdit(item.id); }}
>
<Edit className="h-4 w-4 mr-2" />
</Button>
<Button
variant="outline"
size="default"
className="flex-1 min-w-[100px] h-11 border-red-200 text-red-600 hover:border-red-300 bg-transparent"
onClick={(e) => { e.stopPropagation(); openDeleteDialog(item); }}
>
<Trash2 className="h-4 w-4 mr-2" />
</Button>
</div>
) : undefined
}
/>
);
}, [handleRowClick, handleEdit, openDeleteDialog]);
// 헤더 액션
const headerActions = (
<Button onClick={handleAddBoard}>
<Plus className="w-4 h-4 mr-2" />
</Button>
);
// 페이지네이션 설정
const totalPages = Math.ceil(filteredBoards.length / itemsPerPage);
return (
<>
<IntegratedListTemplateV2<Board>
title="게시판관리"
description="게시판 목록을 관리합니다"
icon={ClipboardList}
headerActions={headerActions}
searchValue={searchValue}
onSearchChange={setSearchValue}
searchPlaceholder="게시판명, 작성자, 대상 검색..."
tabs={tabs}
activeTab={activeTab}
onTabChange={setActiveTab}
tableColumns={tableColumns}
data={paginatedData}
totalCount={filteredBoards.length}
allData={filteredBoards}
selectedItems={selectedItems}
onToggleSelection={toggleSelection}
onToggleSelectAll={toggleSelectAll}
onBulkDelete={handleBulkDelete}
getItemId={(item) => item.id}
renderTableRow={renderTableRow}
renderMobileCard={renderMobileCard}
pagination={{
currentPage,
totalPages,
totalItems: filteredBoards.length,
itemsPerPage,
onPageChange: setCurrentPage,
}}
/>
{/* 삭제 확인 다이얼로그 */}
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle> </AlertDialogTitle>
<AlertDialogDescription>
&quot;{boardToDelete?.boardName}&quot; ?
<br />
<span className="text-destructive">
.
</span>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel></AlertDialogCancel>
<AlertDialogAction
onClick={handleDeleteBoard}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
);
}

View File

@@ -0,0 +1,58 @@
// 게시판 상태 타입
export type BoardStatus = 'active' | 'inactive';
// 대상 타입 (전사, 부서 등)
export type BoardTarget = 'all' | 'department';
// 게시판 타입
export interface Board {
id: string;
target: BoardTarget;
targetName?: string; // 부서명 (target이 department일 때)
boardName: string;
status: BoardStatus;
authorId: string;
authorName: string;
createdAt: string;
updatedAt: string;
}
// 게시판 폼 데이터 타입
export interface BoardFormData {
target: BoardTarget;
targetName?: string;
boardName: string;
status: BoardStatus;
}
// 상태 라벨
export const BOARD_STATUS_LABELS: Record<BoardStatus, string> = {
active: '사용함',
inactive: '사용안함',
};
// 상태 색상
export const BOARD_STATUS_COLORS: Record<BoardStatus, string> = {
active: 'bg-green-100 text-green-800 hover:bg-green-100',
inactive: 'bg-gray-100 text-gray-800 hover:bg-gray-100',
};
// 대상 라벨
export const BOARD_TARGET_LABELS: Record<BoardTarget, string> = {
all: '전사',
department: '부서',
};
// 대상 옵션
export const BOARD_TARGETS = [
{ value: 'all' as BoardTarget, label: '전사' },
{ value: 'department' as BoardTarget, label: '부서' },
];
// Mock 부서 데이터
export const MOCK_DEPARTMENTS = [
{ id: 'd1', name: '영업부' },
{ id: 'd2', name: '개발부' },
{ id: 'd3', name: '인사부' },
{ id: 'd4', name: '경영지원부' },
];