feat: 다중 도메인 UI 개선 및 컴포넌트 리팩토링

- 게시판, HR, 설정, 차량관리, 건설, 견적 등 전반적 UI 개선
- FormField, TabChip, Select 등 공통 컴포넌트 개선
- 가격배분 edit 페이지 제거 및 상세 페이지 통합
- 체크리스트, 근태, 급여, 권한 관리 등 폼 개선

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-02-25 22:30:06 +09:00
parent 1675bcbedf
commit 8f9507a665
86 changed files with 856 additions and 685 deletions

View File

@@ -21,6 +21,7 @@ import { useDeleteDialog } from '@/hooks/useDeleteDialog';
import { PageLayout } from '@/components/organisms/PageLayout';
import { PageHeader } from '@/components/organisms/PageHeader';
import { toast } from 'sonner';
import { useMenuStore } from '@/stores/menuStore';
import type { Account, AccountFormData, AccountStatus } from './types';
import {
BANK_OPTIONS,
@@ -39,6 +40,7 @@ interface AccountDetailProps {
export function AccountDetail({ account, mode: initialMode }: AccountDetailProps) {
const router = useRouter();
const searchParams = useSearchParams();
const sidebarCollapsed = useMenuStore((state) => state.sidebarCollapsed);
const [mode, setMode] = useState(initialMode);
const deleteDialog = useDeleteDialog({
onDelete: async (id) => deleteBankAccount(Number(id)),
@@ -185,22 +187,28 @@ export function AccountDetail({ account, mode: initialMode }: AccountDetailProps
</CardContent>
</Card>
{/* 버튼 영역 */}
<div className="flex items-center justify-between">
<Button variant="outline" onClick={handleBack}>
<ArrowLeft className="w-4 h-4 mr-2" />
</div>
{/* 하단 액션 버튼 (sticky) */}
<div className={`fixed bottom-4 left-4 right-4 px-4 py-3 bg-background/95 backdrop-blur rounded-xl border shadow-lg z-50 transition-all duration-300 md:bottom-6 md:px-6 md:right-[48px] ${sidebarCollapsed ? 'md:left-[156px]' : 'md:left-[316px]'} flex items-center justify-between`}>
<Button variant="outline" onClick={handleBack} size="sm" className="md:size-default">
<ArrowLeft className="h-4 w-4 md:mr-2" />
<span className="hidden md:inline"></span>
</Button>
<div className="flex items-center gap-1 md:gap-2">
<Button
variant="outline"
onClick={() => account?.id && deleteDialog.single.open(String(account.id))}
size="sm"
className="text-destructive hover:bg-destructive hover:text-destructive-foreground md:size-default"
>
<Trash2 className="h-4 w-4 md:mr-2" />
<span className="hidden md:inline"></span>
</Button>
<Button onClick={handleEdit} size="sm" className="md:size-default">
<Edit className="h-4 w-4 md:mr-2" />
<span className="hidden md:inline"></span>
</Button>
<div className="flex items-center gap-2">
<Button variant="outline" onClick={() => account?.id && deleteDialog.single.open(String(account.id))} className="text-destructive hover:bg-destructive hover:text-destructive-foreground">
<Trash2 className="w-4 h-4 mr-2" />
</Button>
<Button onClick={handleEdit}>
<Edit className="w-4 h-4 mr-2" />
</Button>
</div>
</div>
</div>
@@ -330,17 +338,18 @@ export function AccountDetail({ account, mode: initialMode }: AccountDetailProps
</CardContent>
</Card>
{/* 버튼 영역 */}
<div className="flex items-center justify-between">
<Button variant="outline" onClick={handleCancel}>
<X className="w-4 h-4 mr-2" />
</Button>
<Button onClick={handleSubmit}>
<Save className="w-4 h-4 mr-2" />
{isCreateMode ? '등록' : '저장'}
</Button>
</div>
</div>
{/* 하단 액션 버튼 (sticky) */}
<div className={`fixed bottom-4 left-4 right-4 px-4 py-3 bg-background/95 backdrop-blur rounded-xl border shadow-lg z-50 transition-all duration-300 md:bottom-6 md:px-6 md:right-[48px] ${sidebarCollapsed ? 'md:left-[156px]' : 'md:left-[316px]'} flex items-center justify-between`}>
<Button variant="outline" onClick={handleCancel} size="sm" className="md:size-default">
<X className="h-4 w-4 md:mr-2" />
<span className="hidden md:inline"></span>
</Button>
<Button onClick={handleSubmit} size="sm" className="md:size-default">
<Save className="h-4 w-4 md:mr-2" />
<span className="hidden md:inline">{isCreateMode ? '등록' : '저장'}</span>
</Button>
</div>
</PageLayout>
);

View File

@@ -12,7 +12,7 @@
import { useState, useCallback, useMemo } from 'react';
import { useRouter } from 'next/navigation';
import { Landmark, Save, Trash2, ArrowLeft } from 'lucide-react';
import { Landmark, Save, Trash2, ArrowLeft, Edit } from 'lucide-react';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
@@ -20,6 +20,7 @@ import { DeleteConfirmDialog } from '@/components/ui/confirm-dialog';
import { PageLayout } from '@/components/organisms/PageLayout';
import { PageHeader } from '@/components/organisms/PageHeader';
import { FormField } from '@/components/molecules/FormField';
import { useMenuStore } from '@/stores/menuStore';
import type { Account, AccountCategory, AccountFormData } from './types';
import {
ACCOUNT_CATEGORY_OPTIONS,
@@ -98,6 +99,7 @@ export function AccountDetailForm({
isLoading,
}: AccountDetailFormProps) {
const router = useRouter();
const sidebarCollapsed = useMenuStore((state) => state.sidebarCollapsed);
const [mode, setMode] = useState(initialMode);
const [formData, setFormData] = useState<AccountFormData>(() => getInitialFormData(initialData));
const [isSaving, setIsSaving] = useState(false);
@@ -216,7 +218,7 @@ export function AccountDetailForm({
icon={Landmark}
/>
<div className="space-y-6">
<div className="space-y-6 pb-20">
{/* ===== 기본 정보 ===== */}
<Card>
<CardHeader>
@@ -321,26 +323,30 @@ export function AccountDetailForm({
<InsuranceAccountSection formData={formData} onChange={handleChange} disabled={disabled} />
)}
{/* ===== 하단 버튼 ===== */}
<div className="flex items-center justify-between">
<Button variant="outline" onClick={handleBack}>
<ArrowLeft className="w-4 h-4 mr-2" />
{/* ===== 하단 버튼 (sticky) ===== */}
<div className={`fixed bottom-4 left-4 right-4 px-4 py-3 bg-background/95 backdrop-blur rounded-xl border shadow-lg z-50 transition-all duration-300 md:bottom-6 md:px-6 md:right-[48px] ${sidebarCollapsed ? 'md:left-[156px]' : 'md:left-[316px]'} flex items-center justify-between`}>
<Button variant="outline" onClick={handleBack} size="sm" className="md:size-default">
<ArrowLeft className="h-4 w-4 md:mr-2" />
<span className="hidden md:inline"></span>
</Button>
<div className="flex items-center gap-2">
<div className="flex items-center gap-1 md:gap-2">
{isViewMode ? (
<>
{onDelete && (
<Button
variant="outline"
onClick={() => setShowDeleteDialog(true)}
className="text-destructive hover:bg-destructive hover:text-destructive-foreground"
size="sm"
className="text-destructive hover:bg-destructive hover:text-destructive-foreground md:size-default"
>
<Trash2 className="w-4 h-4 mr-2" />
<Trash2 className="h-4 w-4 md:mr-2" />
<span className="hidden md:inline"></span>
</Button>
)}
<Button onClick={handleEdit}></Button>
<Button onClick={handleEdit} size="sm" className="md:size-default">
<Edit className="h-4 w-4 md:mr-2" />
<span className="hidden md:inline"></span>
</Button>
</>
) : (
<>
@@ -348,15 +354,16 @@ export function AccountDetailForm({
<Button
variant="outline"
onClick={() => setShowDeleteDialog(true)}
className="text-destructive hover:bg-destructive hover:text-destructive-foreground"
size="sm"
className="text-destructive hover:bg-destructive hover:text-destructive-foreground md:size-default"
>
<Trash2 className="w-4 h-4 mr-2" />
<Trash2 className="h-4 w-4 md:mr-2" />
<span className="hidden md:inline"></span>
</Button>
)}
<Button onClick={handleSubmit} disabled={isSaving}>
<Save className="w-4 h-4 mr-2" />
{isCreateMode ? '등록' : '저장'}
<Button onClick={handleSubmit} disabled={isSaving} size="sm" className="md:size-default">
<Save className="h-4 w-4 md:mr-2" />
<span className="hidden md:inline">{isCreateMode ? '등록' : '저장'}</span>
</Button>
</>
)}

View File

@@ -344,6 +344,7 @@ export function AccountManagement() {
{ACCOUNT_STATUS_LABELS[item.status]}
</Badge>
}
showCheckbox={false}
isSelected={false}
onToggleSelection={() => {}}
onClick={() => handleRowClick(item)}