- BiddingListClient: MobileFilter 컴포넌트 적용 - ContractListClient: MobileFilter 컴포넌트 적용 - EstimateListClient: MobileFilter 컴포넌트 적용 - HandoverReportListClient: MobileFilter 컴포넌트 적용 - IssueManagementListClient: MobileFilter 컴포넌트 적용 - ItemManagementClient: MobileFilter 컴포넌트 적용 - LaborManagementClient: MobileFilter 컴포넌트 적용 - PricingListClient: MobileFilter 컴포넌트 적용 - SiteBriefingListClient: MobileFilter 컴포넌트 적용 - SiteManagementListClient: MobileFilter 컴포넌트 적용 - StructureReviewListClient: MobileFilter 컴포넌트 적용 - WorkerStatusListClient: MobileFilter 컴포넌트 적용 - TodayIssueSection: CEO 대시보드 이슈 섹션 개선 - EmployeeForm: 사원등록 폼 개선 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
170 lines
6.1 KiB
TypeScript
170 lines
6.1 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import { Card, CardContent } from '@/components/ui/card';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from '@/components/ui/select';
|
|
import { toast } from 'sonner';
|
|
import type { TodayIssueListItem, TodayIssueListBadgeType } from '../types';
|
|
|
|
// 뱃지 색상 매핑
|
|
const BADGE_COLORS: Record<TodayIssueListBadgeType, string> = {
|
|
'수주 성공': 'bg-blue-100 text-blue-700 hover:bg-blue-100',
|
|
'주식 이슈': 'bg-purple-100 text-purple-700 hover:bg-purple-100',
|
|
'직정 제고': 'bg-orange-100 text-orange-700 hover:bg-orange-100',
|
|
'지출예상내역서': 'bg-green-100 text-green-700 hover:bg-green-100',
|
|
'세금 신고': 'bg-red-100 text-red-700 hover:bg-red-100',
|
|
'결재 요청': 'bg-yellow-100 text-yellow-700 hover:bg-yellow-100',
|
|
'기타': 'bg-gray-100 text-gray-700 hover:bg-gray-100',
|
|
};
|
|
|
|
// 필터 옵션
|
|
const FILTER_OPTIONS = [
|
|
{ value: 'all', label: '전체' },
|
|
{ value: '수주 성공', label: '수주 성공' },
|
|
{ value: '주식 이슈', label: '주식 이슈' },
|
|
{ value: '직정 제고', label: '직정 제고' },
|
|
{ value: '지출예상내역서', label: '지출예상내역서' },
|
|
{ value: '세금 신고', label: '세금 신고' },
|
|
{ value: '결재 요청', label: '결재 요청' },
|
|
];
|
|
|
|
interface TodayIssueSectionProps {
|
|
items: TodayIssueListItem[];
|
|
}
|
|
|
|
export function TodayIssueSection({ items }: TodayIssueSectionProps) {
|
|
const router = useRouter();
|
|
const [filter, setFilter] = useState<string>('all');
|
|
const [dismissedIds, setDismissedIds] = useState<Set<string>>(new Set());
|
|
|
|
// 확인되지 않은 아이템만 필터링
|
|
const activeItems = items.filter((item) => !dismissedIds.has(item.id));
|
|
|
|
// 필터링된 아이템
|
|
const filteredItems = filter === 'all'
|
|
? activeItems
|
|
: activeItems.filter((item) => item.badge === filter);
|
|
|
|
// 아이템 클릭
|
|
const handleItemClick = (item: TodayIssueListItem) => {
|
|
if (item.path) {
|
|
router.push(item.path);
|
|
}
|
|
};
|
|
|
|
// 확인 버튼 클릭 (목록에서 제거)
|
|
const handleDismiss = (item: TodayIssueListItem) => {
|
|
setDismissedIds((prev) => new Set(prev).add(item.id));
|
|
toast.success(`"${item.content}" 확인 완료`);
|
|
};
|
|
|
|
// 승인 버튼 클릭
|
|
const handleApprove = (item: TodayIssueListItem) => {
|
|
setDismissedIds((prev) => new Set(prev).add(item.id));
|
|
toast.success(`"${item.content}" 승인 처리되었습니다.`);
|
|
};
|
|
|
|
// 반려 버튼 클릭
|
|
const handleReject = (item: TodayIssueListItem) => {
|
|
setDismissedIds((prev) => new Set(prev).add(item.id));
|
|
toast.error(`"${item.content}" 반려 처리되었습니다.`);
|
|
};
|
|
|
|
return (
|
|
<Card>
|
|
<CardContent className="p-6">
|
|
{/* 헤더 */}
|
|
<div className="flex items-center justify-between mb-4">
|
|
<h2 className="text-lg font-semibold text-gray-900">오늘의 이슈</h2>
|
|
<Select value={filter} onValueChange={setFilter}>
|
|
<SelectTrigger className="w-32 h-9">
|
|
<SelectValue placeholder="전체" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{FILTER_OPTIONS.map((option) => (
|
|
<SelectItem key={option.value} value={option.value}>
|
|
{option.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* 리스트 */}
|
|
<div className="space-y-3 max-h-[400px] overflow-y-auto pr-1">
|
|
{filteredItems.length === 0 ? (
|
|
<div className="text-center py-8 text-gray-500">
|
|
표시할 이슈가 없습니다.
|
|
</div>
|
|
) : (
|
|
filteredItems.map((item) => (
|
|
<div
|
|
key={item.id}
|
|
className="flex items-center justify-between p-4 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors cursor-pointer"
|
|
onClick={() => handleItemClick(item)}
|
|
>
|
|
{/* 좌측: 뱃지 + 내용 */}
|
|
<div className="flex items-center gap-3 flex-1 min-w-0">
|
|
<Badge
|
|
variant="secondary"
|
|
className={`shrink-0 ${BADGE_COLORS[item.badge]}`}
|
|
>
|
|
{item.badge}
|
|
</Badge>
|
|
<span className="text-sm text-gray-800 truncate">
|
|
{item.content}
|
|
</span>
|
|
</div>
|
|
|
|
{/* 우측: 시간 + 버튼 */}
|
|
<div className="flex items-center gap-3 shrink-0 ml-4" onClick={(e) => e.stopPropagation()}>
|
|
<span className="text-xs text-gray-500 whitespace-nowrap">
|
|
{item.time}
|
|
</span>
|
|
{item.needsApproval ? (
|
|
<div className="flex items-center gap-2">
|
|
<Button
|
|
size="sm"
|
|
variant="default"
|
|
className="h-7 px-3 bg-blue-500 hover:bg-blue-600 text-white text-xs"
|
|
onClick={() => handleApprove(item)}
|
|
>
|
|
승인
|
|
</Button>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
className="h-7 px-3 text-xs"
|
|
onClick={() => handleReject(item)}
|
|
>
|
|
반려
|
|
</Button>
|
|
</div>
|
|
) : (
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
className="h-7 px-3 text-xs text-gray-600 hover:text-green-600 hover:border-green-600 hover:bg-green-50"
|
|
onClick={() => handleDismiss(item)}
|
|
>
|
|
확인
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
} |