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:
byeongcheolryu
2026-01-09 19:19:37 +09:00
parent e4af3232dd
commit 284c19f036
60 changed files with 2280 additions and 1348 deletions

View File

@@ -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>