Files
sam-react-prod/src/components/business/CEODashboard/sections/TodayIssueSection.tsx
byeongcheolryu ea8d701a8d refactor(WEB): 공사관리 리스트 페이지 모바일 필터 마이그레이션
- 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>
2026-01-13 18:33:39 +09:00

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>
);
}