From 0d4393fc34df14f5e5ce09ccada3eba79c96b2d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EB=B3=91=EC=B2=A0?= Date: Tue, 24 Feb 2026 21:54:21 +0900 Subject: [PATCH] =?UTF-8?q?feat(WEB):=20CEO=20=EB=8C=80=EC=8B=9C=EB=B3=B4?= =?UTF-8?q?=EB=93=9C=20=EC=A0=84=20=EC=84=B9=EC=85=98=20=EA=B3=B5=ED=86=B5?= =?UTF-8?q?=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B8=B0=EB=B0=98=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EnhancedSections 공통 컴포넌트 추출 (SectionCard, StatItem, StatusBadge 등) - 전 섹션(매출/매입/생산/출근/미출하/건설/캘린더/일보 등) 공통 패턴 적용 - components.tsx 공통 UI 컴포넌트 강화 - CLAUDE.md Git Workflow 섹션 추가 (develop/stage/main 플로우) Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 62 ++++ .../business/CEODashboard/components.tsx | 135 ++++++--- .../CEODashboard/sections/CalendarSection.tsx | 192 ++++++------- .../sections/CardManagementSection.tsx | 72 +++-- .../sections/ConstructionSection.tsx | 69 ++--- .../sections/DailyAttendanceSection.tsx | 110 ++++---- .../sections/DailyProductionSection.tsx | 229 +++++++-------- .../sections/DailyReportSection.tsx | 41 ++- .../sections/DebtCollectionSection.tsx | 62 ++-- .../sections/EnhancedSections.tsx | 267 ++++++++---------- .../sections/EntertainmentSection.tsx | 60 ++-- .../sections/MonthlyExpenseSection.tsx | 46 +-- .../sections/PurchaseStatusSection.tsx | 195 ++++++------- .../sections/ReceivableSection.tsx | 84 +++--- .../sections/SalesStatusSection.tsx | 188 +++++------- .../sections/StatusBoardSection.tsx | 18 +- .../sections/TodayIssueSection.tsx | 53 ++-- .../sections/UnshippedSection.tsx | 111 ++++---- .../CEODashboard/sections/VatSection.tsx | 46 +-- .../CEODashboard/sections/WelfareSection.tsx | 60 ++-- 20 files changed, 1011 insertions(+), 1089 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index ca070ed4..e7fed7c5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,6 +17,68 @@ sam_project: --- +## Git Workflow +**Priority**: 🔴 + +### 브랜치 구조 +| 브랜치 | 역할 | 커밋 상태 | +|--------|------|-----------| +| `develop` | 평소 작업 브랜치 (자유롭게) | 지저분해도 OK | +| `stage` | QA/테스트 환경 | 기능별 squash 정리 | +| `main` | 배포용 (기본 브랜치) | 검증된 것만 | +| `feature/*` | 큰 기능/실험적 작업 시 | 선택적 사용 | + +### "git 올려줘" 단축 명령어 +`git 올려줘` 입력 시 **develop에 push**: +1. `git status` → 2. `git diff --stat` → 3. `git add -A` → 4. `git commit` (자동 메시지) → 5. `git push origin develop` + +- `snapshot.txt`, `.DS_Store` 파일은 항상 제외 +- develop에서 자유롭게 커밋 (커밋 메시지 정리 불필요) + +### main에 올리기 (기능별 squash merge) +사용자가 "main에 올려줘" 또는 특정 기능을 main에 올리라고 지시할 때만 실행. +**절대 자동으로 main에 push하지 않음.** + +```bash +# 기능별로 squash merge +git checkout main +git merge --squash develop # 또는 cherry-pick으로 특정 커밋만 선별 +git commit -m "feat: [기능명]" +git push origin main +git checkout develop +``` + +기능별로 나눠서 올리는 경우: +```bash +# 예: "대시보드랑 거래처 main에 올려줘" +git checkout main +git cherry-pick --no-commit <대시보드커밋1> <대시보드커밋2> +git commit -m "feat: CEO 대시보드 캘린더 기능 구현" + +git cherry-pick --no-commit <거래처커밋1> <거래처커밋2> +git commit -m "feat: 거래처 관리 개선" + +git push origin main +git checkout develop +``` + +**핵심: main에는 기능 단위 커밋만 → 문제 시 `git revert`로 해당 기능만 롤백 가능** + +### feature 브랜치 사용 기준 +| 상황 | 방법 | +|------|------| +| 일반 작업 | develop에서 바로 | +| 1주일+ 걸리는 큰 기능 | feature/* 따서 작업 | +| 실험적 시도 | feature/* 따서 작업 | +| 백엔드와 동시 수정 건 | 각자 feature/* 권장 | + +### 금지 사항 +- ❌ main에 직접 커밋/push +- ❌ `git push --force` (main/develop) +- ❌ 사용자 지시 없이 main에 merge + +--- + ## Client Component 사용 원칙 **Priority**: 🔴 diff --git a/src/components/business/CEODashboard/components.tsx b/src/components/business/CEODashboard/components.tsx index 924e9523..52c4d1c2 100644 --- a/src/components/business/CEODashboard/components.tsx +++ b/src/components/business/CEODashboard/components.tsx @@ -1,5 +1,6 @@ 'use client'; +import { useState } from 'react'; import { Check, AlertTriangle, @@ -7,6 +8,7 @@ import { AlertCircle, TrendingUp, TrendingDown, + ChevronDown, type LucideIcon, } from 'lucide-react'; import { Card, CardContent } from '@/components/ui/card'; @@ -18,18 +20,18 @@ import type { CheckPoint, CheckPointType, AmountCard, HighlightColor } from './t // 섹션별 컬러 테마 타입 export type SectionColorTheme = 'blue' | 'purple' | 'orange' | 'green' | 'red' | 'amber' | 'cyan' | 'pink' | 'emerald' | 'indigo'; -// 컬러 테마별 스타일 -export const SECTION_THEME_STYLES: Record = { - blue: { bg: '#eff6ff', border: '#bfdbfe', iconBg: '#3b82f6', labelColor: '#1d4ed8', accentColor: '#3b82f6' }, - purple: { bg: '#faf5ff', border: '#e9d5ff', iconBg: '#a855f7', labelColor: '#7c3aed', accentColor: '#a855f7' }, - orange: { bg: '#fff7ed', border: '#fed7aa', iconBg: '#f97316', labelColor: '#ea580c', accentColor: '#f97316' }, - green: { bg: '#f0fdf4', border: '#bbf7d0', iconBg: '#22c55e', labelColor: '#16a34a', accentColor: '#22c55e' }, - red: { bg: '#fef2f2', border: '#fecaca', iconBg: '#ef4444', labelColor: '#dc2626', accentColor: '#ef4444' }, - amber: { bg: '#fffbeb', border: '#fde68a', iconBg: '#f59e0b', labelColor: '#d97706', accentColor: '#f59e0b' }, - cyan: { bg: '#ecfeff', border: '#a5f3fc', iconBg: '#06b6d4', labelColor: '#0891b2', accentColor: '#06b6d4' }, - pink: { bg: '#fdf2f8', border: '#fbcfe8', iconBg: '#ec4899', labelColor: '#db2777', accentColor: '#ec4899' }, - emerald: { bg: '#ecfdf5', border: '#a7f3d0', iconBg: '#10b981', labelColor: '#059669', accentColor: '#10b981' }, - indigo: { bg: '#eef2ff', border: '#c7d2fe', iconBg: '#6366f1', labelColor: '#4f46e5', accentColor: '#6366f1' }, +// 컬러 테마별 스타일 (다크모드 지원 Tailwind 클래스) +export const SECTION_THEME_STYLES: Record = { + blue: { bgClass: 'bg-blue-50 dark:bg-blue-900/30', borderClass: 'border-blue-200 dark:border-blue-800', iconBg: '#3b82f6', labelClass: 'text-blue-700 dark:text-blue-300', accentColor: '#3b82f6' }, + purple: { bgClass: 'bg-purple-50 dark:bg-purple-900/30', borderClass: 'border-purple-200 dark:border-purple-800', iconBg: '#a855f7', labelClass: 'text-purple-700 dark:text-purple-300', accentColor: '#a855f7' }, + orange: { bgClass: 'bg-orange-50 dark:bg-orange-900/30', borderClass: 'border-orange-200 dark:border-orange-800', iconBg: '#f97316', labelClass: 'text-orange-700 dark:text-orange-300', accentColor: '#f97316' }, + green: { bgClass: 'bg-green-50 dark:bg-green-900/30', borderClass: 'border-green-200 dark:border-green-800', iconBg: '#22c55e', labelClass: 'text-green-700 dark:text-green-300', accentColor: '#22c55e' }, + red: { bgClass: 'bg-red-50 dark:bg-red-900/30', borderClass: 'border-red-200 dark:border-red-800', iconBg: '#ef4444', labelClass: 'text-red-700 dark:text-red-300', accentColor: '#ef4444' }, + amber: { bgClass: 'bg-amber-50 dark:bg-amber-900/30', borderClass: 'border-amber-200 dark:border-amber-800', iconBg: '#f59e0b', labelClass: 'text-amber-700 dark:text-amber-300', accentColor: '#f59e0b' }, + cyan: { bgClass: 'bg-cyan-50 dark:bg-cyan-900/30', borderClass: 'border-cyan-200 dark:border-cyan-800', iconBg: '#06b6d4', labelClass: 'text-cyan-700 dark:text-cyan-300', accentColor: '#06b6d4' }, + pink: { bgClass: 'bg-pink-50 dark:bg-pink-900/30', borderClass: 'border-pink-200 dark:border-pink-800', iconBg: '#ec4899', labelClass: 'text-pink-700 dark:text-pink-300', accentColor: '#ec4899' }, + emerald: { bgClass: 'bg-emerald-50 dark:bg-emerald-900/30', borderClass: 'border-emerald-200 dark:border-emerald-800', iconBg: '#10b981', labelClass: 'text-emerald-700 dark:text-emerald-300', accentColor: '#10b981' }, + indigo: { bgClass: 'bg-indigo-50 dark:bg-indigo-900/30', borderClass: 'border-indigo-200 dark:border-indigo-800', iconBg: '#6366f1', labelClass: 'text-indigo-700 dark:text-indigo-300', accentColor: '#6366f1' }, }; /** @@ -249,31 +251,21 @@ export const AmountCardItem = ({ return formatKoreanAmount(amount); }; - // 테마 적용 시 스타일 - const cardStyle = themeStyle && !card.isHighlighted ? { - backgroundColor: themeStyle.bg, - borderColor: themeStyle.border, - } : undefined; - return ( {/* 건수 뱃지 (오른쪽 상단) */} {showCountBadge && card.subLabel && (
{card.subLabel}
@@ -299,9 +291,8 @@ export const AmountCardItem = ({

{card.label}

@@ -309,7 +300,7 @@ export const AmountCardItem = ({ {/* 금액 */}

{formatCardAmount(card.amount)} @@ -318,11 +309,12 @@ export const AmountCardItem = ({ {/* 트렌드 표시 (pill 형태, 금액 아래에 배치) */} {showTrend && trendValue && (

{trendDirection === 'up' ? ( @@ -360,10 +352,12 @@ export const AmountCardItem = ({ {card.subLabel && card.subAmount === undefined && !card.previousLabel && ( subLabelAsBadge && themeStyle ? ( @@ -431,4 +425,69 @@ export const IssueCardItem = ({ ); -}; \ No newline at end of file +}; + +/** + * 접기/펼치기 가능한 대시보드 카드 + * - 다크 헤더 + 흰색 바디 패턴의 공통 컴포넌트 + * - 헤더 클릭 시 바디 토글 + */ +interface CollapsibleDashboardCardProps { + icon: React.ReactNode; + title: string; + subtitle?: string; + rightElement?: React.ReactNode; + children: React.ReactNode; + defaultOpen?: boolean; + bodyClassName?: string; +} + +export function CollapsibleDashboardCard({ + icon, + title, + subtitle, + rightElement, + children, + defaultOpen = true, + bodyClassName, +}: CollapsibleDashboardCardProps) { + const [isOpen, setIsOpen] = useState(defaultOpen); + + return ( +
+
setIsOpen(!isOpen)} + > +
+
+
+ {icon} +
+
+

{title}

+ {subtitle &&

{subtitle}

} +
+
+
+ {rightElement} + +
+
+
+ {isOpen && ( +
+ {children} +
+ )} +
+ ); +} \ No newline at end of file diff --git a/src/components/business/CEODashboard/sections/CalendarSection.tsx b/src/components/business/CEODashboard/sections/CalendarSection.tsx index 32e327f7..e7a823b9 100644 --- a/src/components/business/CEODashboard/sections/CalendarSection.tsx +++ b/src/components/business/CEODashboard/sections/CalendarSection.tsx @@ -2,7 +2,6 @@ import { useState, useMemo, useEffect } 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 { @@ -12,11 +11,12 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; -import { Plus, ExternalLink, ChevronLeft, ChevronRight } from 'lucide-react'; +import { Plus, ExternalLink, ChevronLeft, ChevronRight, CalendarDays } from 'lucide-react'; import { ScheduleCalendar } from '@/components/common/ScheduleCalendar'; import type { ScheduleEvent } from '@/components/common/ScheduleCalendar/types'; import { getCalendarEventsForYear, type CalendarEvent } from '@/constants/calendarEvents'; import { useCalendarScheduleStore } from '@/stores/useCalendarScheduleStore'; +import { CollapsibleDashboardCard } from '../components'; import type { CalendarScheduleItem, CalendarViewType, @@ -46,16 +46,16 @@ const SCHEDULE_TYPE_COLORS: Record = { // 이슈 뱃지별 색상 const ISSUE_BADGE_COLORS: Record = { - '수주등록': 'bg-blue-100 text-blue-700', - '추심이슈': 'bg-purple-100 text-purple-700', - '안전재고': 'bg-orange-100 text-orange-700', - '지출 승인대기': 'bg-green-100 text-green-700', - '세금 신고': 'bg-red-100 text-red-700', - '결재 요청': 'bg-yellow-100 text-yellow-700', - '신규거래처': 'bg-emerald-100 text-emerald-700', - '입금': 'bg-teal-100 text-teal-700', - '출금': 'bg-pink-100 text-pink-700', - '기타': 'bg-gray-100 text-gray-700', + '수주등록': 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300', + '추심이슈': 'bg-purple-100 text-purple-700 dark:bg-purple-900/40 dark:text-purple-300', + '안전재고': 'bg-orange-100 text-orange-700 dark:bg-orange-900/40 dark:text-orange-300', + '지출 승인대기': 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300', + '세금 신고': 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300', + '결재 요청': 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/40 dark:text-yellow-300', + '신규거래처': 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300', + '입금': 'bg-teal-100 text-teal-700 dark:bg-teal-900/40 dark:text-teal-300', + '출금': 'bg-pink-100 text-pink-700 dark:bg-pink-900/40 dark:text-pink-300', + '기타': 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300', }; // 부서 필터 옵션 @@ -295,67 +295,15 @@ export function CalendarSection({ }; return ( - - - {/* 모바일: 스티키 헤더 (타이틀+필터+월네비) */} -
-
-

캘린더

-
- {/* 부서 필터 */} - - - {/* 업무 필터 */} - -
-
- - {/* 월 네비게이션 */} -
- - - {currentDate.getFullYear()}년 {currentDate.getMonth() + 1}월 - - -
-
- - {/* 데스크탑: 일반 헤더 */} -
-

캘린더

-
+ } + title="캘린더" + subtitle="일정 관리" + > + {/* 모바일: 필터+월네비 */} +
+
+ {/* 부서 필터 */} + {/* 업무 필터 */}
+ + {/* 월 네비게이션 */} +
+ + + {currentDate.getFullYear()}년 {currentDate.getMonth() + 1}월 + + +
+
+ + {/* 데스크탑: 필터 */} +
+ + +
{/* 모바일: 리스트뷰 */}
{/* 일별 리스트 */} -
+
{monthDaysWithEvents.map((day) => { const hasEvents = day.events.length > 0; const isSelected = selectedDate && day.date.getTime() === selectedDate.getTime(); return (
handleDateClick(day.date)} > {/* 날짜 + 일정등록 버튼 */}
{day.label} - {day.isToday && (오늘)} + {day.isToday && (오늘)}
{isSelected && (
{/* 선택된 날짜 일정 + 이슈 목록 */} -
+
-

+

{selectedDate ? formatSelectedDate(selectedDate) : '날짜를 선택하세요'}

-
-
- -
+ } + title="당일 근태 현황" + subtitle="오늘의 출근 현황" + rightElement={ + + } + > {/* 요약 카드 4개 */}
-
+
- 출근 + 출근
- {data.present}명 + {data.present}명
-
+
- 휴가 + 휴가
- {data.onLeave}명 + {data.onLeave}명
-
+
- 지각 + 지각
- {data.late}명 + {data.late}명
-
+
- 결근 + 결근
- {data.absent}명 + {data.absent}명
{/* 테이블 */} -
-
-

직원 근태 목록

+
+
+

직원 근태 목록

- - - - - 최신순 - 오래된순 - 금액 높은순 - 금액 낮은순 - - -
+ } + title="당월 매입 내역" + subtitle="당월 매입 거래 상세" + bodyClassName="p-0" + > +
+
총 {filteredItems.length}건
+ ({ value: s, label: s }))} + value={supplierFilter} + onChange={setSupplierFilter} + placeholder="전체 공급처" + className="w-full h-8 text-xs" + /> +
- - - - - - + + + + + + {filteredItems.map((item, idx) => ( - - - - - + + + + - - - + +
날짜공급처품목금액상태
날짜공급처품목금액상태
{item.date}{item.supplier}{item.item} +
{item.date}{item.supplier}{item.item} {item.amount.toLocaleString()}원 @@ -253,10 +226,10 @@ export function PurchaseStatusSection({ data }: PurchaseStatusSectionProps) { variant="outline" className={ item.status === '결제완료' - ? 'text-green-600 border-green-200 bg-green-50' + ? 'text-green-600 border-green-200 bg-green-50 dark:text-green-400 dark:border-green-800 dark:bg-green-900/30' : item.status === '미결제' - ? 'text-red-600 border-red-200 bg-red-50' - : 'text-orange-600 border-orange-200 bg-orange-50' + ? 'text-red-600 border-red-200 bg-red-50 dark:text-red-400 dark:border-red-800 dark:bg-red-900/30' + : 'text-orange-600 border-orange-200 bg-orange-50 dark:text-orange-400 dark:border-orange-800 dark:bg-orange-900/30' } > {item.status} @@ -266,9 +239,9 @@ export function PurchaseStatusSection({ data }: PurchaseStatusSectionProps) { ))}
합계 +
합계 {filteredItems.reduce((sum, item) => sum + item.amount, 0).toLocaleString()}원 @@ -276,7 +249,7 @@ export function PurchaseStatusSection({ data }: PurchaseStatusSectionProps) {
-
+
); } diff --git a/src/components/business/CEODashboard/sections/ReceivableSection.tsx b/src/components/business/CEODashboard/sections/ReceivableSection.tsx index d7eb27a9..0bc73907 100644 --- a/src/components/business/CEODashboard/sections/ReceivableSection.tsx +++ b/src/components/business/CEODashboard/sections/ReceivableSection.tsx @@ -1,9 +1,9 @@ 'use client'; import { useRouter } from 'next/navigation'; -import { Banknote, Clock, AlertTriangle, CircleDollarSign } from 'lucide-react'; -import { Card, CardContent } from '@/components/ui/card'; -import { SectionTitle, AmountCardItem, CheckPointItem, type SectionColorTheme } from '../components'; +import { Banknote, Clock, AlertTriangle, CircleDollarSign, ChevronRight } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { AmountCardItem, CheckPointItem, CollapsibleDashboardCard, type SectionColorTheme } from '../components'; import type { ReceivableData } from '../types'; // 카드별 아이콘 매핑 (미수금 합계, 30일 이내, 30~90일, 90일 초과) @@ -24,46 +24,46 @@ export function ReceivableSection({ data }: ReceivableSectionProps) { }; return ( - - - + } + title="미수금 현황" + subtitle="미수금 관리 현황" + rightElement={ + data.detailButtonLabel ? ( + + ) : undefined + } + > +
+ {data.cards.map((card, idx) => ( + + ))} +
-
- {data.cards.map((card, idx) => ( - + {data.checkPoints.length > 0 && ( +
+ {data.checkPoints.map((cp) => ( + ))}
- - {data.checkPoints.length > 0 && ( -
- {data.checkPoints.map((cp) => ( - - ))} -
- )} - - + )} + ); -} \ No newline at end of file +} diff --git a/src/components/business/CEODashboard/sections/SalesStatusSection.tsx b/src/components/business/CEODashboard/sections/SalesStatusSection.tsx index b0af22a1..f55ab179 100644 --- a/src/components/business/CEODashboard/sections/SalesStatusSection.tsx +++ b/src/components/business/CEODashboard/sections/SalesStatusSection.tsx @@ -30,6 +30,7 @@ import { ResponsiveContainer, } from 'recharts'; import { formatKoreanAmount } from '@/lib/utils/amount'; +import { CollapsibleDashboardCard } from '../components'; import type { SalesStatusData } from '../types'; interface SalesStatusSectionProps { @@ -59,81 +60,58 @@ export function SalesStatusSection({ data }: SalesStatusSectionProps) { return (
-
- {/* 다크 헤더 */} -
-
-
-
- -
-
-

매출 현황

-

당월 매출 실적

-
-
- - 당월 - -
-
- -
+ } + title="매출 현황" + subtitle="당월 매출 실적" + rightElement={ + + 당월 + + } + > {/* 통계카드 4개 */}
{/* 누적 매출 */} -
+
- +
- 누적 매출 + 누적 매출
- + {formatKoreanAmount(data.cumulativeSales)}
{/* 달성률 */} -
+
- +
- 달성률 + 달성률
- + {data.achievementRate}%
{/* 전년 동기 대비 */}
= 0 ? '#fef2f2' : '#eff6ff', - borderColor: data.yoyChange >= 0 ? '#fecaca' : '#bfdbfe', - }} - className="rounded-xl p-4 border" + className={`rounded-xl p-4 border ${data.yoyChange >= 0 ? 'bg-red-50 border-red-200 dark:bg-red-900/30 dark:border-red-800' : 'bg-blue-50 border-blue-200 dark:bg-blue-900/30 dark:border-blue-800'}`} >
= 0 ? '#ef4444' : '#3b82f6' }} className="p-1.5 rounded-lg"> {data.yoyChange >= 0 - ? - : } + ? + : }
- = 0 ? '#dc2626' : '#1d4ed8' }} className="text-sm font-medium">전년 동기 대비 + = 0 ? 'text-red-700 dark:text-red-300' : 'text-blue-700 dark:text-blue-300'}`}>전년 동기 대비
- + {data.yoyChange >= 0 ? '+' : ''}{data.yoyChange}% {data.yoyChange >= 0 @@ -143,17 +121,14 @@ export function SalesStatusSection({ data }: SalesStatusSectionProps) {
{/* 당월 매출 */} -
+
- +
- 당월 매출 + 당월 매출
- + {formatKoreanAmount(data.monthlySales)}
@@ -162,8 +137,8 @@ export function SalesStatusSection({ data }: SalesStatusSectionProps) { {/* 차트 2열 */}
{/* 월별 매출 추이 */} -
-

월별 매출 추이

+
+

월별 매출 추이

@@ -178,8 +153,8 @@ export function SalesStatusSection({ data }: SalesStatusSectionProps) {
{/* 거래처별 매출 (수평 Bar) */} -
-

거래처별 매출

+
+

거래처별 매출

@@ -194,63 +169,54 @@ export function SalesStatusSection({ data }: SalesStatusSectionProps) {
-
-
+ {/* 당월 매출 내역 (별도 카드) */} -
-
-
-
- -
-
-

당월 매출 내역

-

당월 매출 거래 상세

-
-
-
-
-
총 {filteredItems.length}건
-
- ({ value: c, label: c }))} - value={clientFilter} - onChange={setClientFilter} - placeholder="전체 거래처" - className="w-[160px] h-8 text-xs" - /> - -
+ } + title="당월 매출 내역" + subtitle="당월 매출 거래 상세" + bodyClassName="p-0" + > +
+
총 {filteredItems.length}건
+ ({ value: c, label: c }))} + value={clientFilter} + onChange={setClientFilter} + placeholder="전체 거래처" + className="w-full h-8 text-xs" + /> +
- - - - - - + + + + + + {filteredItems.map((item, idx) => ( - - - - - + + + + - - - + +
날짜거래처품목금액상태
날짜거래처품목금액상태
{item.date}{item.client}{item.item} +
{item.date}{item.client}{item.item} {item.amount.toLocaleString()}원 @@ -258,10 +224,10 @@ export function SalesStatusSection({ data }: SalesStatusSectionProps) { variant="outline" className={ item.status === '입금완료' - ? 'text-green-600 border-green-200 bg-green-50' + ? 'text-green-600 border-green-200 bg-green-50 dark:text-green-400 dark:border-green-800 dark:bg-green-900/30' : item.status === '미입금' - ? 'text-red-600 border-red-200 bg-red-50' - : 'text-orange-600 border-orange-200 bg-orange-50' + ? 'text-red-600 border-red-200 bg-red-50 dark:text-red-400 dark:border-red-800 dark:bg-red-900/30' + : 'text-orange-600 border-orange-200 bg-orange-50 dark:text-orange-400 dark:border-orange-800 dark:bg-orange-900/30' } > {item.status} @@ -271,9 +237,9 @@ export function SalesStatusSection({ data }: SalesStatusSectionProps) { ))}
합계 +
합계 {filteredItems.reduce((sum, item) => sum + item.amount, 0).toLocaleString()}원 @@ -281,7 +247,7 @@ export function SalesStatusSection({ data }: SalesStatusSectionProps) {
-
+
); } diff --git a/src/components/business/CEODashboard/sections/StatusBoardSection.tsx b/src/components/business/CEODashboard/sections/StatusBoardSection.tsx index 3f90e560..44acfb0e 100644 --- a/src/components/business/CEODashboard/sections/StatusBoardSection.tsx +++ b/src/components/business/CEODashboard/sections/StatusBoardSection.tsx @@ -1,8 +1,8 @@ 'use client'; import { useRouter } from 'next/navigation'; -import { Card, CardContent } from '@/components/ui/card'; -import { SectionTitle, IssueCardItem } from '../components'; +import { LayoutGrid } from 'lucide-react'; +import { IssueCardItem, CollapsibleDashboardCard } from '../components'; import type { TodayIssueItem, TodayIssueSettings } from '../types'; // 라벨 → 설정키 매핑 @@ -40,10 +40,11 @@ export function StatusBoardSection({ items, itemSettings }: StatusBoardSectionPr : items; return ( - - - - + } + title="현황판" + subtitle="주요 현황 요약" + >
{filteredItems.map((item) => ( ))}
-
-
+ ); -} \ No newline at end of file +} diff --git a/src/components/business/CEODashboard/sections/TodayIssueSection.tsx b/src/components/business/CEODashboard/sections/TodayIssueSection.tsx index 72e1591f..89490dd3 100644 --- a/src/components/business/CEODashboard/sections/TodayIssueSection.tsx +++ b/src/components/business/CEODashboard/sections/TodayIssueSection.tsx @@ -2,7 +2,6 @@ import { useState, useMemo, useCallback, useRef, useLayoutEffect } 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 { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; @@ -31,6 +30,7 @@ import { Loader2, type LucideIcon, } from 'lucide-react'; +import { CollapsibleDashboardCard } from '../components'; import { usePastIssue } from '@/hooks/useCEODashboard'; import type { TodayIssueListItem, TodayIssueNotificationType } from '../types'; @@ -44,16 +44,16 @@ interface BadgeStyle { // notification_type 코드 기반 스타일 매핑 (API 고정값 사용) const NOTIFICATION_STYLES: Record = { - sales_order: { bg: 'bg-blue-50', text: 'text-blue-700', iconBg: 'bg-blue-500', Icon: ShoppingCart }, - bad_debt: { bg: 'bg-purple-50', text: 'text-purple-700', iconBg: 'bg-purple-500', Icon: AlertCircle }, - safety_stock: { bg: 'bg-orange-50', text: 'text-orange-700', iconBg: 'bg-orange-500', Icon: Package }, - expected_expense: { bg: 'bg-green-50', text: 'text-green-700', iconBg: 'bg-green-500', Icon: Receipt }, - vat_report: { bg: 'bg-red-50', text: 'text-red-700', iconBg: 'bg-red-500', Icon: FileText }, - approval_request: { bg: 'bg-amber-50', text: 'text-amber-700', iconBg: 'bg-amber-500', Icon: CheckCircle2 }, - new_vendor: { bg: 'bg-emerald-50', text: 'text-emerald-700', iconBg: 'bg-emerald-500', Icon: Building2 }, - deposit: { bg: 'bg-cyan-50', text: 'text-cyan-700', iconBg: 'bg-cyan-500', Icon: TrendingUp }, - withdrawal: { bg: 'bg-pink-50', text: 'text-pink-700', iconBg: 'bg-pink-500', Icon: TrendingDown }, - other: { bg: 'bg-gray-50', text: 'text-gray-700', iconBg: 'bg-gray-500', Icon: Info }, + sales_order: { bg: 'bg-blue-50 dark:bg-blue-900/30', text: 'text-blue-700 dark:text-blue-300', iconBg: 'bg-blue-500', Icon: ShoppingCart }, + bad_debt: { bg: 'bg-purple-50 dark:bg-purple-900/30', text: 'text-purple-700 dark:text-purple-300', iconBg: 'bg-purple-500', Icon: AlertCircle }, + safety_stock: { bg: 'bg-orange-50 dark:bg-orange-900/30', text: 'text-orange-700 dark:text-orange-300', iconBg: 'bg-orange-500', Icon: Package }, + expected_expense: { bg: 'bg-green-50 dark:bg-green-900/30', text: 'text-green-700 dark:text-green-300', iconBg: 'bg-green-500', Icon: Receipt }, + vat_report: { bg: 'bg-red-50 dark:bg-red-900/30', text: 'text-red-700 dark:text-red-300', iconBg: 'bg-red-500', Icon: FileText }, + approval_request: { bg: 'bg-amber-50 dark:bg-amber-900/30', text: 'text-amber-700 dark:text-amber-300', iconBg: 'bg-amber-500', Icon: CheckCircle2 }, + new_vendor: { bg: 'bg-emerald-50 dark:bg-emerald-900/30', text: 'text-emerald-700 dark:text-emerald-300', iconBg: 'bg-emerald-500', Icon: Building2 }, + deposit: { bg: 'bg-cyan-50 dark:bg-cyan-900/30', text: 'text-cyan-700 dark:text-cyan-300', iconBg: 'bg-cyan-500', Icon: TrendingUp }, + withdrawal: { bg: 'bg-pink-50 dark:bg-pink-900/30', text: 'text-pink-700 dark:text-pink-300', iconBg: 'bg-pink-500', Icon: TrendingDown }, + other: { bg: 'bg-muted/50', text: 'text-muted-foreground', iconBg: 'bg-gray-500', Icon: Info }, }; // 신용등급 색상 매핑 (A=녹색, B=노랑, C=주황, D=빨강) @@ -277,11 +277,13 @@ export function TodayIssueSection({ items }: TodayIssueSectionProps) { }; return ( - - - {/* 헤더 */} + } + title="오늘의 이슈" + subtitle="주요 알림 및 이슈 현황" + > + {/* 필터/탭 영역 */}
-

오늘의 이슈

{/* 탭 */} @@ -334,7 +336,7 @@ export function TodayIssueSection({ items }: TodayIssueSectionProps) {
{option.label} - + {option.count}
@@ -348,11 +350,11 @@ export function TodayIssueSection({ items }: TodayIssueSectionProps) {
{activeTab === 'past' && pastLoading ? (
- - 데이터를 불러오는 중... + + 데이터를 불러오는 중...
) : filteredItems.length === 0 ? ( -
+
{activeTab === 'past' ? `${formatDateDisplay(pastDate)}에 이슈가 없습니다.` : '표시할 이슈가 없습니다.'} @@ -368,7 +370,7 @@ export function TodayIssueSection({ items }: TodayIssueSectionProps) { return (
handleItemClick(item)} > {/* 아이콘 + 뱃지 */} @@ -382,7 +384,7 @@ export function TodayIssueSection({ items }: TodayIssueSectionProps) {
{/* 내용 */} - + {item.content} @@ -404,7 +406,7 @@ export function TodayIssueSection({ items }: TodayIssueSectionProps) { )} {/* 시간 */} - + {item.time} @@ -434,7 +436,7 @@ export function TodayIssueSection({ items }: TodayIssueSectionProps) {
- - + ); -} \ No newline at end of file +} diff --git a/src/components/business/CEODashboard/sections/UnshippedSection.tsx b/src/components/business/CEODashboard/sections/UnshippedSection.tsx index b135cda7..01591190 100644 --- a/src/components/business/CEODashboard/sections/UnshippedSection.tsx +++ b/src/components/business/CEODashboard/sections/UnshippedSection.tsx @@ -11,6 +11,7 @@ import { SelectValue, } from '@/components/ui/select'; import { MultiSelectCombobox } from '@/components/ui/multi-select-combobox'; +import { CollapsibleDashboardCard } from '../components'; import type { UnshippedData } from '../types'; interface UnshippedSectionProps { @@ -31,81 +32,68 @@ export function UnshippedSection({ data }: UnshippedSectionProps) { }); return ( -
- {/* 다크 헤더 */} -
-
-
-
- -
-
-

미출고 내역

-

납기일 기준 미출고 현황

-
-
- - {data.items.length}건 - -
-
- -
+ } + title="미출고 내역" + subtitle="납기일 기준 미출고 현황" + rightElement={ + + {data.items.length}건 + + } + > {/* 미출고 테이블 */} -
-
-

미출고 목록

-
- ({ value: c, label: c }))} - value={clientFilter} - onChange={setClientFilter} - placeholder="전체 거래처" - className="w-[160px] h-8 text-xs" - /> - -
+
+
+

미출고 목록

+ ({ value: c, label: c }))} + value={clientFilter} + onChange={setClientFilter} + placeholder="전체 거래처" + className="w-full h-8 text-xs" + /> +
- - - - - - - + + + + + + + {filteredItems.map((item, idx) => ( - - - - - - + + + + + +
No포트번호현장명수주처납기일남은일
No포트번호현장명수주처납기일남은일
{idx + 1}{item.portNo}{item.siteName}{item.orderClient}{item.dueDate}
{idx + 1}{item.portNo}{item.siteName}{item.orderClient}{item.dueDate} D-{item.daysLeft} @@ -117,7 +105,6 @@ export function UnshippedSection({ data }: UnshippedSectionProps) {
-
-
+
); } diff --git a/src/components/business/CEODashboard/sections/VatSection.tsx b/src/components/business/CEODashboard/sections/VatSection.tsx index 2a230f58..eb5c45ab 100644 --- a/src/components/business/CEODashboard/sections/VatSection.tsx +++ b/src/components/business/CEODashboard/sections/VatSection.tsx @@ -1,7 +1,7 @@ 'use client'; -import { Card, CardContent } from '@/components/ui/card'; -import { SectionTitle, AmountCardItem, CheckPointItem } from '../components'; +import { Calculator } from 'lucide-react'; +import { AmountCardItem, CheckPointItem, CollapsibleDashboardCard } from '../components'; import type { VatData } from '../types'; interface VatSectionProps { @@ -11,28 +11,28 @@ interface VatSectionProps { export function VatSection({ data, onClick }: VatSectionProps) { return ( - - - + } + title="부가세 현황" + subtitle="부가세 납부 정보" + > +
+ {data.cards.map((card) => ( + + ))} +
-
- {data.cards.map((card) => ( - + {data.checkPoints.length > 0 && ( +
+ {data.checkPoints.map((cp) => ( + ))}
- - {data.checkPoints.length > 0 && ( -
- {data.checkPoints.map((cp) => ( - - ))} -
- )} - - + )} + ); -} \ No newline at end of file +} diff --git a/src/components/business/CEODashboard/sections/WelfareSection.tsx b/src/components/business/CEODashboard/sections/WelfareSection.tsx index f94a6af4..d9e3f733 100644 --- a/src/components/business/CEODashboard/sections/WelfareSection.tsx +++ b/src/components/business/CEODashboard/sections/WelfareSection.tsx @@ -1,8 +1,7 @@ 'use client'; import { Heart, Gift, Coffee, Smile } from 'lucide-react'; -import { Card, CardContent } from '@/components/ui/card'; -import { SectionTitle, AmountCardItem, CheckPointItem, type SectionColorTheme } from '../components'; +import { AmountCardItem, CheckPointItem, CollapsibleDashboardCard, type SectionColorTheme } from '../components'; import type { WelfareData } from '../types'; // 카드별 아이콘 매핑 @@ -16,38 +15,33 @@ interface WelfareSectionProps { export function WelfareSection({ data, onCardClick }: WelfareSectionProps) { return ( - - - + } + title="복리후생비 현황" + subtitle="복리후생비 사용 현황" + > +
+ {data.cards.map((card, idx) => ( + onCardClick(card.id) : undefined} + icon={CARD_ICONS[idx] || Heart} + colorTheme={CARD_THEMES[idx] || 'emerald'} + showTrend={!!card.previousLabel} + trendValue={card.previousLabel} + trendDirection="up" + /> + ))} +
-
- {data.cards.map((card, idx) => ( - onCardClick(card.id) : undefined} - icon={CARD_ICONS[idx] || Heart} - colorTheme={CARD_THEMES[idx] || 'emerald'} - showTrend={!!card.previousLabel} - trendValue={card.previousLabel} - trendDirection="up" - /> + {data.checkPoints.length > 0 && ( +
+ {data.checkPoints.map((cp) => ( + ))}
- - {data.checkPoints.length > 0 && ( -
- {data.checkPoints.map((cp) => ( - - ))} -
- )} - - + )} + ); -} \ No newline at end of file +}