refactor(WEB): Server Component → Client Component 전면 마이그레이션
- 53개 페이지를 Server Component에서 Client Component로 변환 - Next.js 15에서 Server Component 렌더링 중 쿠키 수정 불가 이슈 해결 - 폐쇄형 ERP 시스템 특성상 SEO 불필요, Client Component 사용이 적합 주요 변경사항: - 모든 페이지에 'use client' 지시어 추가 - use(params) 훅으로 async params 처리 - useState + useEffect로 데이터 페칭 패턴 적용 - skipTokenRefresh 옵션 및 관련 코드 제거 (더 이상 필요 없음) 변환된 페이지: - Settings: 4개 (account-info, notification-settings, permissions, popup-management) - Accounting: 9개 (vendors, sales, deposits, bills, withdrawals, expected-expenses, bad-debt-collection) - Sales: 4개 (quote-management, pricing-management) - Production/Quality/Master-data: 6개 - Material/Outbound: 4개 - Construction: 22개 - Other: 4개 (payment-history, subscription, dev/test-urls) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -198,7 +198,7 @@ export function DashboardSettingsDialog({
|
||||
onClose();
|
||||
}, [settings, onClose]);
|
||||
|
||||
// 커스텀 스위치 (ON/OFF 라벨 포함)
|
||||
// 커스텀 스위치 (라이트 테마용)
|
||||
const ToggleSwitch = ({
|
||||
checked,
|
||||
onCheckedChange,
|
||||
@@ -210,36 +210,20 @@ export function DashboardSettingsDialog({
|
||||
type="button"
|
||||
onClick={() => onCheckedChange(!checked)}
|
||||
className={cn(
|
||||
'relative inline-flex h-7 w-14 items-center rounded-full transition-colors',
|
||||
checked ? 'bg-cyan-500' : 'bg-gray-300'
|
||||
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
|
||||
checked ? 'bg-blue-500' : 'bg-gray-300'
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
'absolute left-1 text-[10px] font-medium text-white transition-opacity',
|
||||
checked ? 'opacity-100' : 'opacity-0'
|
||||
)}
|
||||
>
|
||||
ON
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
'absolute right-1 text-[10px] font-medium text-gray-500 transition-opacity',
|
||||
checked ? 'opacity-0' : 'opacity-100'
|
||||
)}
|
||||
>
|
||||
OFF
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
'inline-block h-5 w-5 transform rounded-full bg-white shadow-md transition-transform',
|
||||
checked ? 'translate-x-8' : 'translate-x-1'
|
||||
'inline-block h-4 w-4 transform rounded-full bg-white shadow-md transition-transform',
|
||||
checked ? 'translate-x-6' : 'translate-x-1'
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
|
||||
// 섹션 행 컴포넌트
|
||||
// 섹션 행 컴포넌트 (라이트 테마)
|
||||
const SectionRow = ({
|
||||
label,
|
||||
checked,
|
||||
@@ -258,11 +242,16 @@ export function DashboardSettingsDialog({
|
||||
children?: React.ReactNode;
|
||||
}) => (
|
||||
<Collapsible open={isExpanded} onOpenChange={onToggleExpand}>
|
||||
<div className="flex items-center justify-between py-2 border-b border-gray-100">
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-between py-3 px-4 bg-gray-200',
|
||||
children && isExpanded ? 'rounded-t-lg' : 'rounded-lg'
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{hasExpand && (
|
||||
<CollapsibleTrigger asChild>
|
||||
<button type="button" className="p-1 hover:bg-gray-100 rounded">
|
||||
<button type="button" className="p-1 hover:bg-gray-300 rounded">
|
||||
{isExpanded ? (
|
||||
<ChevronUp className="h-4 w-4 text-gray-500" />
|
||||
) : (
|
||||
@@ -271,12 +260,12 @@ export function DashboardSettingsDialog({
|
||||
</button>
|
||||
</CollapsibleTrigger>
|
||||
)}
|
||||
<span className="text-sm font-medium">{label}</span>
|
||||
<span className="text-sm font-medium text-gray-800">{label}</span>
|
||||
</div>
|
||||
<ToggleSwitch checked={checked} onCheckedChange={onCheckedChange} />
|
||||
</div>
|
||||
{children && (
|
||||
<CollapsibleContent className="pl-6 py-2 space-y-3 bg-gray-50">
|
||||
<CollapsibleContent className="px-4 py-3 space-y-3 bg-gray-50 rounded-b-lg">
|
||||
{children}
|
||||
</CollapsibleContent>
|
||||
)}
|
||||
@@ -285,30 +274,30 @@ export function DashboardSettingsDialog({
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={(open) => !open && handleCancel()}>
|
||||
<DialogContent className="w-[95vw] max-w-[450px] sm:max-w-[450px] max-h-[85vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-lg font-bold">항목 설정</DialogTitle>
|
||||
<DialogContent className="w-[95vw] max-w-[450px] sm:max-w-[450px] max-h-[85vh] overflow-y-auto bg-white border-gray-200 p-0">
|
||||
<DialogHeader className="p-4 border-b border-gray-200">
|
||||
<DialogTitle className="text-lg font-bold text-gray-900">항목 설정</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 py-2">
|
||||
<div className="space-y-3 p-4">
|
||||
{/* 오늘의 이슈 섹션 */}
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center justify-between py-2 border-b-2 border-gray-200">
|
||||
<span className="text-sm font-semibold">오늘의 이슈</span>
|
||||
<div className="space-y-0 rounded-lg overflow-hidden">
|
||||
<div className="flex items-center justify-between py-3 px-4 bg-gray-200">
|
||||
<span className="text-sm font-medium text-gray-800">오늘의 이슈</span>
|
||||
<ToggleSwitch
|
||||
checked={localSettings.todayIssue.enabled}
|
||||
onCheckedChange={handleTodayIssueToggle}
|
||||
/>
|
||||
</div>
|
||||
{localSettings.todayIssue.enabled && (
|
||||
<div className="pl-4 space-y-1">
|
||||
<div className="bg-gray-50">
|
||||
{(Object.keys(TODAY_ISSUE_LABELS) as Array<keyof TodayIssueSettings>).map(
|
||||
(key) => (
|
||||
<div
|
||||
key={key}
|
||||
className="flex items-center justify-between py-1.5"
|
||||
className="flex items-center justify-between py-2.5 px-6 border-t border-gray-200"
|
||||
>
|
||||
<span className="text-sm text-gray-700">
|
||||
<span className="text-sm text-gray-600">
|
||||
{TODAY_ISSUE_LABELS[key]}
|
||||
</span>
|
||||
<ToggleSwitch
|
||||
@@ -408,36 +397,36 @@ export function DashboardSettingsDialog({
|
||||
)}
|
||||
</button>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="mt-2 p-3 bg-white border rounded text-xs space-y-4">
|
||||
<CollapsibleContent className="mt-2 p-3 bg-white border border-gray-200 rounded text-xs space-y-4">
|
||||
{/* ■ 중소기업 판단 기준표 */}
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="font-bold">■</span>
|
||||
<span className="font-bold text-gray-800">■</span>
|
||||
<span className="text-sm font-medium text-gray-800">중소기업 판단 기준표</span>
|
||||
</div>
|
||||
<table className="w-full border-collapse text-xs">
|
||||
<thead>
|
||||
<tr className="bg-gray-100">
|
||||
<th className="border px-2 py-1 text-center">조건</th>
|
||||
<th className="border px-2 py-1 text-center">기준</th>
|
||||
<th className="border px-2 py-1 text-center">충족 요건</th>
|
||||
<th className="border border-gray-300 px-2 py-1 text-center text-gray-700">조건</th>
|
||||
<th className="border border-gray-300 px-2 py-1 text-center text-gray-700">기준</th>
|
||||
<th className="border border-gray-300 px-2 py-1 text-center text-gray-700">충족 요건</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="border px-2 py-1 text-center">① 매출액</td>
|
||||
<td className="border px-2 py-1 text-center">업종별 상이</td>
|
||||
<td className="border px-2 py-1 text-center">업종별 기준 금액 이하</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">① 매출액</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">업종별 상이</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">업종별 기준 금액 이하</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border px-2 py-1 text-center">② 자산총액</td>
|
||||
<td className="border px-2 py-1 text-center">5,000억원</td>
|
||||
<td className="border px-2 py-1 text-center">미만</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">② 자산총액</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">5,000억원</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">미만</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border px-2 py-1 text-center">③ 독립성</td>
|
||||
<td className="border px-2 py-1 text-center">소유·경영</td>
|
||||
<td className="border px-2 py-1 text-center">대기업 계열 아님</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">③ 독립성</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">소유·경영</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">대기업 계열 아님</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -451,20 +440,20 @@ export function DashboardSettingsDialog({
|
||||
<table className="w-full border-collapse text-xs">
|
||||
<thead>
|
||||
<tr className="bg-gray-100">
|
||||
<th className="border px-2 py-1 text-center">업종 분류</th>
|
||||
<th className="border px-2 py-1 text-center">기준 매출액</th>
|
||||
<th className="border border-gray-300 px-2 py-1 text-center text-gray-700">업종 분류</th>
|
||||
<th className="border border-gray-300 px-2 py-1 text-center text-gray-700">기준 매출액</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td className="border px-2 py-1 text-center">제조업</td><td className="border px-2 py-1 text-center">1,500억원 이하</td></tr>
|
||||
<tr><td className="border px-2 py-1 text-center">건설업</td><td className="border px-2 py-1 text-center">1,000억원 이하</td></tr>
|
||||
<tr><td className="border px-2 py-1 text-center">운수업</td><td className="border px-2 py-1 text-center">1,000억원 이하</td></tr>
|
||||
<tr><td className="border px-2 py-1 text-center">도매업</td><td className="border px-2 py-1 text-center">1,000억원 이하</td></tr>
|
||||
<tr><td className="border px-2 py-1 text-center">소매업</td><td className="border px-2 py-1 text-center">600억원 이하</td></tr>
|
||||
<tr><td className="border px-2 py-1 text-center">정보통신업</td><td className="border px-2 py-1 text-center">600억원 이하</td></tr>
|
||||
<tr><td className="border px-2 py-1 text-center">전문서비스업</td><td className="border px-2 py-1 text-center">600억원 이하</td></tr>
|
||||
<tr><td className="border px-2 py-1 text-center">숙박·음식점업</td><td className="border px-2 py-1 text-center">400억원 이하</td></tr>
|
||||
<tr><td className="border px-2 py-1 text-center">기타 서비스업</td><td className="border px-2 py-1 text-center">400억원 이하</td></tr>
|
||||
<tr><td className="border border-gray-200 px-2 py-1 text-center text-gray-600">제조업</td><td className="border border-gray-200 px-2 py-1 text-center text-gray-600">1,500억원 이하</td></tr>
|
||||
<tr><td className="border border-gray-200 px-2 py-1 text-center text-gray-600">건설업</td><td className="border border-gray-200 px-2 py-1 text-center text-gray-600">1,000억원 이하</td></tr>
|
||||
<tr><td className="border border-gray-200 px-2 py-1 text-center text-gray-600">운수업</td><td className="border border-gray-200 px-2 py-1 text-center text-gray-600">1,000억원 이하</td></tr>
|
||||
<tr><td className="border border-gray-200 px-2 py-1 text-center text-gray-600">도매업</td><td className="border border-gray-200 px-2 py-1 text-center text-gray-600">1,000억원 이하</td></tr>
|
||||
<tr><td className="border border-gray-200 px-2 py-1 text-center text-gray-600">소매업</td><td className="border border-gray-200 px-2 py-1 text-center text-gray-600">600억원 이하</td></tr>
|
||||
<tr><td className="border border-gray-200 px-2 py-1 text-center text-gray-600">정보통신업</td><td className="border border-gray-200 px-2 py-1 text-center text-gray-600">600억원 이하</td></tr>
|
||||
<tr><td className="border border-gray-200 px-2 py-1 text-center text-gray-600">전문서비스업</td><td className="border border-gray-200 px-2 py-1 text-center text-gray-600">600억원 이하</td></tr>
|
||||
<tr><td className="border border-gray-200 px-2 py-1 text-center text-gray-600">숙박·음식점업</td><td className="border border-gray-200 px-2 py-1 text-center text-gray-600">400억원 이하</td></tr>
|
||||
<tr><td className="border border-gray-200 px-2 py-1 text-center text-gray-600">기타 서비스업</td><td className="border border-gray-200 px-2 py-1 text-center text-gray-600">400억원 이하</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -477,14 +466,14 @@ export function DashboardSettingsDialog({
|
||||
<table className="w-full border-collapse text-xs">
|
||||
<thead>
|
||||
<tr className="bg-gray-100">
|
||||
<th className="border px-2 py-1 text-center">구분</th>
|
||||
<th className="border px-2 py-1 text-center">기준</th>
|
||||
<th className="border border-gray-300 px-2 py-1 text-center text-gray-700">구분</th>
|
||||
<th className="border border-gray-300 px-2 py-1 text-center text-gray-700">기준</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="border px-2 py-1 text-center">5,000억원 미만</td>
|
||||
<td className="border px-2 py-1 text-center">직전 사업연도 말 자산총액</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">5,000억원 미만</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">직전 사업연도 말 자산총액</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -498,31 +487,31 @@ export function DashboardSettingsDialog({
|
||||
<table className="w-full border-collapse text-xs">
|
||||
<thead>
|
||||
<tr className="bg-gray-100">
|
||||
<th className="border px-2 py-1 text-center">구분</th>
|
||||
<th className="border px-2 py-1 text-center">내용</th>
|
||||
<th className="border px-2 py-1 text-center">판정</th>
|
||||
<th className="border border-gray-300 px-2 py-1 text-center text-gray-700">구분</th>
|
||||
<th className="border border-gray-300 px-2 py-1 text-center text-gray-700">내용</th>
|
||||
<th className="border border-gray-300 px-2 py-1 text-center text-gray-700">판정</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="border px-2 py-1 text-center">독립기업</td>
|
||||
<td className="border px-2 py-1">아래 항목에 모두 해당하지 않음</td>
|
||||
<td className="border px-2 py-1 text-center">충족</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">독립기업</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-gray-600">아래 항목에 모두 해당하지 않음</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">충족</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border px-2 py-1 text-center">기업집단 소속</td>
|
||||
<td className="border px-2 py-1">공정거래법상 상호출자제한 기업집단 소속</td>
|
||||
<td className="border px-2 py-1 text-center">미충족</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">기업집단 소속</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-gray-600">공정거래법상 상호출자제한 기업집단 소속</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">미충족</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border px-2 py-1 text-center">대기업 지분</td>
|
||||
<td className="border px-2 py-1">대기업이 발행주식 30% 이상 보유</td>
|
||||
<td className="border px-2 py-1 text-center">미충족</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">대기업 지분</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-gray-600">대기업이 발행주식 30% 이상 보유</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">미충족</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border px-2 py-1 text-center">관계기업 합산</td>
|
||||
<td className="border px-2 py-1">관계기업 포함 시 매출액·자산 기준 초과</td>
|
||||
<td className="border px-2 py-1 text-center">미충족</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">관계기업 합산</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-gray-600">관계기업 포함 시 매출액·자산 기준 초과</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">미충족</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -531,27 +520,27 @@ export function DashboardSettingsDialog({
|
||||
{/* ■ 판정 결과 */}
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="font-bold">■</span>
|
||||
<span className="font-bold text-gray-800">■</span>
|
||||
<span className="text-sm font-medium text-gray-800">판정 결과</span>
|
||||
</div>
|
||||
<table className="w-full border-collapse text-xs">
|
||||
<thead>
|
||||
<tr className="bg-gray-100">
|
||||
<th className="border px-2 py-1 text-center">판정</th>
|
||||
<th className="border px-2 py-1 text-center">조건</th>
|
||||
<th className="border px-2 py-1 text-center">접대비 기본한도</th>
|
||||
<th className="border border-gray-300 px-2 py-1 text-center text-gray-700">판정</th>
|
||||
<th className="border border-gray-300 px-2 py-1 text-center text-gray-700">조건</th>
|
||||
<th className="border border-gray-300 px-2 py-1 text-center text-gray-700">접대비 기본한도</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="border px-2 py-1 text-center">중소기업</td>
|
||||
<td className="border px-2 py-1 text-center">①②③ 모두 충족</td>
|
||||
<td className="border px-2 py-1 text-center">3,600만원</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">중소기업</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">①②③ 모두 충족</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">3,600만원</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border px-2 py-1 text-center">일반법인</td>
|
||||
<td className="border px-2 py-1 text-center">①②③ 중 하나라도 미충족</td>
|
||||
<td className="border px-2 py-1 text-center">1,200만원</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">일반법인</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">①②③ 중 하나라도 미충족</td>
|
||||
<td className="border border-gray-200 px-2 py-1 text-center text-gray-600">1,200만원</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -699,11 +688,18 @@ export function DashboardSettingsDialog({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DialogFooter className="flex gap-2 sm:justify-center">
|
||||
<Button variant="outline" onClick={handleCancel} className="w-20">
|
||||
<DialogFooter className="flex gap-3 p-4 border-t border-gray-200 sm:justify-center">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleCancel}
|
||||
className="w-20 bg-gray-100 hover:bg-gray-200 text-gray-700 border-gray-300 rounded-full"
|
||||
>
|
||||
취소
|
||||
</Button>
|
||||
<Button onClick={handleSave} className="w-20 bg-blue-600 hover:bg-blue-700">
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
className="w-20 bg-gray-500 hover:bg-gray-600 text-white rounded-full"
|
||||
>
|
||||
저장
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
Reference in New Issue
Block a user