refactor: 리스트 컴포넌트 UI 및 레이아웃 일관성 개선

- 여러 관리 페이지(영업, 회계, 인사, 결재, 게시판, 설정)의 리스트 UI 통일
- IntegratedListTemplateV2 기반 레이아웃 정리
- PricingHistoryDialog 개선
- 공통 컴포넌트 추출 계획 문서 추가

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2025-12-24 11:34:42 +09:00
parent d5f758f1eb
commit c1abf89d80
24 changed files with 657 additions and 221 deletions

View File

@@ -307,18 +307,18 @@ export function BillManagementClient({
// ===== 헤더 액션 =====
const headerActions = (
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
extraActions={
<Button onClick={() => router.push('/ko/accounting/bills/new')}>
<Plus className="h-4 w-4 mr-2" />
</Button>
}
/>
<>
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
/>
<Button className="ml-auto" onClick={() => router.push('/ko/accounting/bills/new')}>
<Plus className="h-4 w-4 mr-2" />
</Button>
</>
);
// ===== 거래처 목록 (필터용) =====

View File

@@ -452,18 +452,18 @@ export function SalesManagement() {
// ===== 헤더 액션 =====
const headerActions = (
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
extraActions={
<Button onClick={handleNewSales}>
<Plus className="h-4 w-4 mr-2" />
</Button>
}
/>
<>
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
/>
<Button className="ml-auto" onClick={handleNewSales}>
<Plus className="h-4 w-4 mr-2" />
</Button>
</>
);
// ===== 계정과목명 저장 핸들러 =====

View File

@@ -276,22 +276,23 @@ export function VendorLedger() {
// ===== 헤더 액션 =====
const headerActions = (
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
extraActions={
<Button
variant="outline"
size="sm"
onClick={handleExcelDownload}
>
<Download className="mr-2 h-4 w-4" />
</Button>
}
/>
<>
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
/>
<Button
className="ml-auto"
variant="outline"
size="sm"
onClick={handleExcelDownload}
>
<Download className="mr-2 h-4 w-4" />
</Button>
</>
);
// ===== 테이블 하단 합계 행 =====

View File

@@ -463,26 +463,26 @@ export function ApprovalBox() {
// ===== 헤더 액션 (DateRangeSelector + 승인/반려 버튼) =====
const headerActions = (
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
extraActions={
selectedItems.size > 0 && (
<>
<Button variant="default" onClick={handleApproveClick}>
<Check className="h-4 w-4 mr-2" />
</Button>
<Button variant="destructive" onClick={handleRejectClick}>
<X className="h-4 w-4 mr-2" />
</Button>
</>
)
}
/>
<>
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
/>
{selectedItems.size > 0 && (
<div className="ml-auto flex gap-2">
<Button variant="default" onClick={handleApproveClick}>
<Check className="h-4 w-4 mr-2" />
</Button>
<Button variant="destructive" onClick={handleRejectClick}>
<X className="h-4 w-4 mr-2" />
</Button>
</div>
)}
</>
);
// ===== 테이블 헤더 액션 (필터 + 정렬 셀렉트) =====

View File

@@ -443,32 +443,32 @@ export function DraftBox() {
// ===== 헤더 액션 (DateRangeSelector + 버튼들) =====
const headerActions = (
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
extraActions={
<>
{selectedItems.size > 0 && (
<>
<Button variant="default" onClick={handleSubmit}>
<Send className="h-4 w-4 mr-2" />
</Button>
<Button variant="destructive" onClick={handleDelete}>
<Trash2 className="h-4 w-4 mr-2" />
</Button>
</>
)}
<Button onClick={handleNewDocument}>
<Plus className="h-4 w-4 mr-2" />
</Button>
</>
}
/>
<>
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
/>
<div className="ml-auto flex gap-2">
{selectedItems.size > 0 && (
<>
<Button variant="default" onClick={handleSubmit}>
<Send className="h-4 w-4 mr-2" />
</Button>
<Button variant="destructive" onClick={handleDelete}>
<Trash2 className="h-4 w-4 mr-2" />
</Button>
</>
)}
<Button onClick={handleNewDocument}>
<Plus className="h-4 w-4 mr-2" />
</Button>
</div>
</>
);
// ===== 테이블 헤더 액션 (필터 + 정렬 셀렉트) =====

View File

@@ -372,26 +372,26 @@ export function ReferenceBox() {
// ===== 헤더 액션 (DateRangeSelector + 열람/미열람 버튼) =====
const headerActions = (
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
extraActions={
selectedItems.size > 0 && (
<>
<Button variant="default" onClick={handleMarkReadClick}>
<Eye className="h-4 w-4 mr-2" />
</Button>
<Button variant="outline" onClick={handleMarkUnreadClick}>
<EyeOff className="h-4 w-4 mr-2" />
</Button>
</>
)
}
/>
<>
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
/>
{selectedItems.size > 0 && (
<div className="ml-auto flex gap-2">
<Button variant="default" onClick={handleMarkReadClick}>
<Eye className="h-4 w-4 mr-2" />
</Button>
<Button variant="outline" onClick={handleMarkUnreadClick}>
<EyeOff className="h-4 w-4 mr-2" />
</Button>
</div>
)}
</>
);
// ===== 테이블 헤더 액션 (필터 + 정렬 셀렉트) =====

View File

@@ -378,18 +378,18 @@ export function BoardList() {
// ===== 헤더 액션 =====
const headerActions = (
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
extraActions={
<Button onClick={handleNewPost}>
<Plus className="h-4 w-4 mr-2" />
</Button>
}
/>
<>
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
/>
<Button className="ml-auto" onClick={handleNewPost}>
<Plus className="h-4 w-4 mr-2" />
</Button>
</>
);
// ===== 테이블 헤더 액션 (필터 1개만: 게시판) =====

View File

@@ -396,7 +396,7 @@ export function BoardManagement() {
// 헤더 액션
const headerActions = (
<Button onClick={handleAddBoard}>
<Button className="ml-auto" onClick={handleAddBoard}>
<Plus className="w-4 h-4 mr-2" />
</Button>

View File

@@ -254,18 +254,18 @@ export function InquiryList() {
description="1:1 문의를 등록하고 답변을 확인합니다."
icon={MessageSquare}
headerActions={
<div className="flex items-center gap-2 flex-wrap">
<>
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
/>
<Button onClick={handleCreate}>
<Button className="ml-auto" onClick={handleCreate}>
<Plus className="h-4 w-4 mr-2" />
</Button>
</div>
</>
}
searchValue={searchValue}
onSearchChange={setSearchValue}

View File

@@ -426,24 +426,24 @@ export function AttendanceManagement() {
// 헤더 액션 (DateRangeSelector + 버튼들)
const headerActions = (
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
extraActions={
<>
<Button variant="outline" onClick={handleExcelDownload}>
<Download className="w-4 h-4 mr-2" />
</Button>
<Button onClick={handleAddAttendance}>
<Plus className="w-4 h-4 mr-2" />
</Button>
</>
}
/>
<>
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
/>
<div className="ml-auto flex gap-2">
<Button variant="outline" onClick={handleExcelDownload}>
<Download className="w-4 h-4 mr-2" />
</Button>
<Button onClick={handleAddAttendance}>
<Plus className="w-4 h-4 mr-2" />
</Button>
</div>
</>
);
// 테이블 헤더 액션 (필터 + 정렬 셀렉트) - 사원관리와 동일한 위치

View File

@@ -390,7 +390,7 @@ export function CardManagement() {
// 헤더 액션
const headerActions = (
<Button onClick={handleAddCard}>
<Button className="ml-auto" onClick={handleAddCard}>
<Plus className="w-4 h-4 mr-2" />
</Button>

View File

@@ -580,31 +580,31 @@ export function EmployeeManagement() {
// 헤더 액션 (DateRangeSelector + 버튼들)
const headerActions = (
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
extraActions={
<>
<Button
variant="outline"
onClick={() => setUserInviteOpen(true)}
>
<Mail className="w-4 h-4 mr-2" />
</Button>
<Button variant="outline" onClick={handleCSVUpload}>
<Upload className="w-4 h-4 mr-2" />
CSV
</Button>
<Button onClick={handleAddEmployee}>
<Plus className="w-4 h-4 mr-2" />
</Button>
</>
}
/>
<>
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
/>
<div className="ml-auto flex gap-2">
<Button
variant="outline"
onClick={() => setUserInviteOpen(true)}
>
<Mail className="w-4 h-4 mr-2" />
</Button>
<Button variant="outline" onClick={handleCSVUpload}>
<Upload className="w-4 h-4 mr-2" />
CSV
</Button>
<Button onClick={handleAddEmployee}>
<Plus className="w-4 h-4 mr-2" />
</Button>
</div>
</>
);
// 테이블 헤더 액션 (필터/정렬 셀렉트박스)

View File

@@ -501,51 +501,51 @@ export function VacationManagement() {
// ===== 헤더 액션 (DateRangeSelector + 버튼들) =====
const headerActions = (
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
extraActions={
<>
{/* 탭별 액션 버튼 */}
{mainTab === 'grant' && (
<Button onClick={() => setGrantDialogOpen(true)}>
<>
<DateRangeSelector
startDate={startDate}
endDate={endDate}
onStartDateChange={setStartDate}
onEndDateChange={setEndDate}
/>
<div className="ml-auto flex gap-2">
{/* 탭별 액션 버튼 */}
{mainTab === 'grant' && (
<Button onClick={() => setGrantDialogOpen(true)}>
<Plus className="h-4 w-4 mr-2" />
</Button>
)}
{mainTab === 'request' && (
<>
{/* 버튼 순서: 승인 → 거절 → 휴가신청 (휴가신청 버튼 위치 고정) */}
{selectedItems.size > 0 && (
<>
<Button variant="default" onClick={handleApproveClick}>
<Check className="h-4 w-4 mr-2" />
</Button>
<Button variant="destructive" onClick={handleRejectClick}>
<X className="h-4 w-4 mr-2" />
</Button>
</>
)}
<Button onClick={() => setRequestDialogOpen(true)}>
<Plus className="h-4 w-4 mr-2" />
</Button>
)}
</>
)}
{mainTab === 'request' && (
<>
{/* 버튼 순서: 승인 → 거절 → 휴가신청 (휴가신청 버튼 위치 고정) */}
{selectedItems.size > 0 && (
<>
<Button variant="default" onClick={handleApproveClick}>
<Check className="h-4 w-4 mr-2" />
</Button>
<Button variant="destructive" onClick={handleRejectClick}>
<X className="h-4 w-4 mr-2" />
</Button>
</>
)}
<Button onClick={() => setRequestDialogOpen(true)}>
<Plus className="h-4 w-4 mr-2" />
</Button>
</>
)}
{/* 엑셀 다운로드 버튼 - 주석처리 */}
{/* <Button variant="outline" onClick={() => console.log('엑셀 다운로드')}>
<Download className="h-4 w-4 mr-2" />
엑셀 다운로드
</Button> */}
</>
}
/>
{/* 엑셀 다운로드 버튼 - 주석처리 */}
{/* <Button variant="outline" onClick={() => console.log('엑셀 다운로드')}>
<Download className="h-4 w-4 mr-2" />
엑셀 다운로드
</Button> */}
</div>
</>
);
// ===== 테이블 헤더 액션 (필터 + 정렬 셀렉트) - 사원관리와 동일한 위치 =====

View File

@@ -117,25 +117,25 @@ export function PricingHistoryDialog({
<div>
<span className="text-muted-foreground">:</span>
<div>
{revision.previousData.purchasePrice?.toLocaleString() || '-'}
{revision.previousData?.purchasePrice?.toLocaleString() || '-'}
</div>
</div>
<div>
<span className="text-muted-foreground">:</span>
<div>
{revision.previousData.processingCost?.toLocaleString() || '-'}
{revision.previousData?.processingCost?.toLocaleString() || '-'}
</div>
</div>
<div>
<span className="text-muted-foreground">:</span>
<div>
{revision.previousData.salesPrice?.toLocaleString() || '-'}
{revision.previousData?.salesPrice?.toLocaleString() || '-'}
</div>
</div>
<div>
<span className="text-muted-foreground">:</span>
<div>
{revision.previousData.marginRate?.toFixed(1) || '-'}%
{revision.previousData?.marginRate?.toFixed(1) || '-'}%
</div>
</div>
</div>

View File

@@ -403,7 +403,7 @@ export function PricingListClient({
// TODO: API 연동 시 품목 마스터 동기화 로직 구현
console.log('품목 마스터 동기화');
}}
className="gap-2"
className="ml-auto gap-2"
>
<RefreshCw className="h-4 w-4" />

View File

@@ -294,7 +294,7 @@ export function AccountManagement() {
// ===== 헤더 액션 (카드관리와 동일한 패턴) =====
const headerActions = (
<Button onClick={handleCreate}>
<Button className="ml-auto" onClick={handleCreate}>
<Plus className="w-4 h-4 mr-2" />
</Button>

View File

@@ -411,7 +411,7 @@ export function PermissionManagement() {
// ===== 헤더 액션 =====
const headerActions = (
<div className="flex items-center gap-2 flex-wrap">
<div className="flex items-center gap-2 flex-wrap ml-auto">
{selectedItems.size > 0 && (
<Button variant="destructive" onClick={handleBulkDelete}>
<Trash2 className="h-4 w-4 mr-2" />

View File

@@ -286,7 +286,7 @@ export function PopupList() {
description="팝업 목록을 관리합니다."
icon={Megaphone}
headerActions={
<Button onClick={handleCreate}>
<Button className="ml-auto" onClick={handleCreate}>
<Plus className="h-4 w-4 mr-2" />
</Button>

View File

@@ -228,7 +228,7 @@ export function IntegratedListTemplateV2<T = any>({
{/* 헤더 액션 (달력, 버튼 등) - 타이틀 아래 배치 */}
{headerActions && (
<div className="flex items-center gap-2 flex-wrap">
<div className="flex items-center gap-2 flex-wrap w-full">
{headerActions}
</div>
)}