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>

View File

@@ -475,6 +475,186 @@ export async function finalizePricing(id: string): Promise<{ success: boolean; d
}
}
// ============================================
// 품목 목록 + 단가 목록 병합 조회
// ============================================
// 품목 API 응답 타입 (GET /api/v1/items)
interface ItemApiData {
id: number;
item_type: string; // FG, PT, SM, RM, CS (품목 유형)
code: string;
name: string;
unit: string;
category_id: number | null;
created_at: string;
deleted_at: string | null;
}
// 단가 목록 조회용 타입
interface PriceApiListItem {
id: number;
tenant_id: number;
item_type_code: string;
item_id: number;
client_group_id: number | null;
purchase_price: string | null;
processing_cost: string | null;
loss_rate: string | null;
margin_rate: string | null;
sales_price: string | null;
rounding_rule: 'round' | 'ceil' | 'floor';
rounding_unit: number;
supplier: string | null;
effective_from: string;
effective_to: string | null;
status: 'draft' | 'active' | 'finalized';
is_final: boolean;
finalized_at: string | null;
finalized_by: number | null;
note: string | null;
created_at: string;
updated_at: string;
deleted_at: string | null;
}
// 목록 표시용 타입
export interface PricingListItem {
id: string;
itemId: string;
itemCode: string;
itemName: string;
itemType: string;
specification?: string;
unit: string;
purchasePrice?: number;
processingCost?: number;
salesPrice?: number;
marginRate?: number;
effectiveDate?: string;
status: 'draft' | 'active' | 'finalized' | 'not_registered';
currentRevision: number;
isFinal: boolean;
itemTypeCode: string;
}
// 품목 유형 매핑 (type_code → 프론트엔드 ItemType)
function mapItemTypeForList(typeCode?: string): string {
switch (typeCode) {
case 'FG': return 'FG';
case 'PT': return 'PT';
case 'SM': return 'SM';
case 'RM': return 'RM';
case 'CS': return 'CS';
default: return 'PT';
}
}
// API 상태 → 프론트엔드 상태 매핑
function mapStatusForList(apiStatus: string, isFinal: boolean): 'draft' | 'active' | 'finalized' | 'not_registered' {
if (isFinal) return 'finalized';
switch (apiStatus) {
case 'draft': return 'draft';
case 'active': return 'active';
case 'finalized': return 'finalized';
default: return 'draft';
}
}
/**
* 단가 목록 데이터 조회 (품목 + 단가 병합)
*/
export async function getPricingListData(): Promise<PricingListItem[]> {
try {
// 품목 목록 조회
const { response: itemsResponse, error: itemsError } = await serverFetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/items?group_id=1&size=100`,
{ method: 'GET' }
);
if (itemsError || !itemsResponse) {
console.error('[PricingActions] Items fetch error:', itemsError?.message);
return [];
}
const itemsResult = await itemsResponse.json();
const items: ItemApiData[] = itemsResult.success ? (itemsResult.data?.data || []) : [];
// 단가 목록 조회
const { response: pricingResponse, error: pricingError } = await serverFetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/pricing?size=100`,
{ method: 'GET' }
);
if (pricingError || !pricingResponse) {
console.error('[PricingActions] Pricing fetch error:', pricingError?.message);
return [];
}
const pricingResult = await pricingResponse.json();
const pricings: PriceApiListItem[] = pricingResult.success ? (pricingResult.data?.data || []) : [];
// 단가 정보를 빠르게 찾기 위한 Map 생성
const pricingMap = new Map<string, PriceApiListItem>();
for (const pricing of pricings) {
const key = `${pricing.item_type_code}_${pricing.item_id}`;
if (!pricingMap.has(key)) {
pricingMap.set(key, pricing);
}
}
// 품목 목록을 기준으로 병합
return items.map((item) => {
const key = `${item.item_type}_${item.id}`;
const pricing = pricingMap.get(key);
if (pricing) {
return {
id: String(pricing.id),
itemId: String(item.id),
itemCode: item.code,
itemName: item.name,
itemType: mapItemTypeForList(item.item_type),
specification: undefined,
unit: item.unit || 'EA',
purchasePrice: pricing.purchase_price ? parseFloat(pricing.purchase_price) : undefined,
processingCost: pricing.processing_cost ? parseFloat(pricing.processing_cost) : undefined,
salesPrice: pricing.sales_price ? parseFloat(pricing.sales_price) : undefined,
marginRate: pricing.margin_rate ? parseFloat(pricing.margin_rate) : undefined,
effectiveDate: pricing.effective_from,
status: mapStatusForList(pricing.status, pricing.is_final),
currentRevision: 0,
isFinal: pricing.is_final,
itemTypeCode: item.item_type,
};
} else {
return {
id: `item_${item.id}`,
itemId: String(item.id),
itemCode: item.code,
itemName: item.name,
itemType: mapItemTypeForList(item.item_type),
specification: undefined,
unit: item.unit || 'EA',
purchasePrice: undefined,
processingCost: undefined,
salesPrice: undefined,
marginRate: undefined,
effectiveDate: undefined,
status: 'not_registered' as const,
currentRevision: 0,
isFinal: false,
itemTypeCode: item.item_type,
};
}
});
} catch (error) {
if (isNextRedirectError(error)) throw error;
console.error('[PricingActions] getPricingListData error:', error);
return [];
}
}
/**
* 단가 이력 조회
*/

View File

@@ -35,10 +35,10 @@ interface CategorySectionProps {
function CategorySection({ title, enabled, onEnabledChange, children }: CategorySectionProps) {
return (
<div className="bg-gray-800 rounded-lg overflow-hidden">
<div className="bg-gray-100 rounded-lg overflow-hidden">
{/* 카테고리 헤더 */}
<div className="flex items-center justify-between px-4 py-3">
<span className="text-white font-medium">{title}</span>
<div className="flex items-center justify-between px-4 py-3 bg-gray-200">
<span className="text-gray-800 font-medium">{title}</span>
<Switch
checked={enabled}
onCheckedChange={onEnabledChange}
@@ -46,7 +46,7 @@ function CategorySection({ title, enabled, onEnabledChange, children }: Category
/>
</div>
{/* 하위 항목 */}
<div className="bg-gray-700 px-4 py-2 space-y-2">
<div className="bg-gray-50 px-4 py-2 space-y-2">
{children}
</div>
</div>
@@ -64,7 +64,7 @@ interface ItemRowProps {
function ItemRow({ label, checked, onChange, disabled }: ItemRowProps) {
return (
<div className="flex items-center justify-between py-1">
<span className="text-gray-300 text-sm">{label}</span>
<span className="text-gray-600 text-sm">{label}</span>
<Switch
checked={checked}
onCheckedChange={onChange}
@@ -133,17 +133,17 @@ export function ItemSettingsDialog({ isOpen, onClose, settings, onSave }: ItemSe
return (
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
<DialogContent className="!w-[400px] !max-w-[400px] max-h-[80vh] overflow-y-auto p-0 bg-gray-900 border-gray-700">
<DialogContent className="!w-[400px] !max-w-[400px] max-h-[80vh] overflow-y-auto p-0 bg-white border-gray-200">
{/* 헤더 */}
<DialogHeader className="sticky top-0 bg-gray-900 z-10 px-4 py-3 border-b border-gray-700">
<DialogHeader className="sticky top-0 bg-white z-10 px-4 py-3 border-b border-gray-200">
<div className="flex items-center justify-between">
<DialogTitle className="text-white font-medium"> </DialogTitle>
<DialogTitle className="text-gray-900 font-medium"> </DialogTitle>
<button
type="button"
onClick={onClose}
className="p-1 hover:bg-gray-800 rounded transition-colors"
className="p-1 hover:bg-gray-100 rounded transition-colors"
>
<X className="h-5 w-5 text-gray-400" />
<X className="h-5 w-5 text-gray-500" />
</button>
</div>
</DialogHeader>
@@ -315,17 +315,17 @@ export function ItemSettingsDialog({ isOpen, onClose, settings, onSave }: ItemSe
</div>
{/* 하단 버튼 */}
<div className="sticky bottom-0 bg-gray-900 px-4 py-3 border-t border-gray-700 flex justify-center gap-3">
<div className="sticky bottom-0 bg-white px-4 py-3 border-t border-gray-200 flex justify-center gap-3">
<Button
variant="outline"
onClick={onClose}
className="bg-gray-700 border-gray-600 text-white hover:bg-gray-600 min-w-[80px]"
className="bg-gray-100 border-gray-300 text-gray-700 hover:bg-gray-200 min-w-[80px] rounded-full"
>
</Button>
<Button
onClick={handleSave}
className="bg-gray-700 text-white hover:bg-gray-600 min-w-[80px]"
className="bg-gray-500 text-white hover:bg-gray-600 min-w-[80px] rounded-full"
>
</Button>