fix(WEB): 회계/결재/레이아웃 버그 수정 및 UI 개선

- BadDebtCollection/ReceivablesStatus 리스트 로직 수정
- DraftBox 결재 기안함 개선
- Sidebar/AuthenticatedLayout 레이아웃 보완
- IntegratedListTemplateV2 수정
- table UI 컴포넌트 수정

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-02-20 15:37:28 +09:00
parent 012a661a19
commit 5f956540e8
7 changed files with 51 additions and 29 deletions

View File

@@ -49,7 +49,7 @@ import { deleteBadDebt, toggleBadDebt } from './actions';
// ===== 테이블 컬럼 정의 =====
const tableColumns = [
{ key: 'no', label: 'No.', className: 'text-center w-[60px]' },
{ key: 'vendorName', label: '거래처' },
{ key: 'vendorName', label: '거래처', className: 'w-[100px]' },
{ key: 'debtAmount', label: '채권금액', className: 'text-right w-[140px]' },
{ key: 'occurrenceDate', label: '발생일', className: 'text-center w-[110px]' },
{ key: 'overdueDays', label: '연체일수', className: 'text-center w-[100px]' },

View File

@@ -448,15 +448,15 @@ export function ReceivablesStatus({ highlightVendorId, initialData, initialSumma
<Card>
<CardContent className="pt-6">
<div className="rounded-md border overflow-x-auto">
<Table>
<Table className="border-separate border-spacing-0" style={{ minWidth: `${200 + 70 + (monthCount * 100) + 120}px` }}>
<TableHeader>
<TableRow>
{/* 거래처/연체 - 왼쪽 고정 */}
<TableHead className="w-[120px] min-w-[120px] sticky left-0 z-20 bg-white">
<TableHead className="w-[200px] min-w-[200px] max-w-[200px] sticky left-0 z-20 bg-white border-r border-gray-200">
/
</TableHead>
{/* 구분 - 왼쪽 고정 (거래처 옆) */}
<TableHead className="w-[70px] min-w-[70px] text-center sticky left-[120px] z-20 bg-white border-r border-gray-200">
<TableHead className="w-[70px] min-w-[70px] text-center sticky left-[200px] z-20 bg-white border-r border-gray-200">
</TableHead>
{/* 동적 월 레이블 - 스크롤 영역 */}
@@ -501,7 +501,7 @@ export function ReceivablesStatus({ highlightVendorId, initialData, initialSumma
// 구분별 행 (매출, 입금, 어음, 미수금) + 메모 행 = 총 5개 행
const rows = [];
// 구분별 행 렌더링
// 구분별 행 렌더링 (rowSpan 대신 각 행마다 개별 거래처 셀로 sticky 안정화)
categoryOrder.forEach((category, catIndex) => {
const categoryData = vendor.categories.find(c => c.category === category);
if (!categoryData) return;
@@ -512,14 +512,13 @@ export function ReceivablesStatus({ highlightVendorId, initialData, initialSumma
ref={catIndex === 0 && isHighlighted ? highlightRowRef : undefined}
className={`${catIndex === 0 ? 'border-t-2 border-gray-300' : ''} ${isHighlighted ? 'ring-2 ring-yellow-400 ring-inset' : ''}`}
>
{/* 거래처명 + 연체 토글 - 왼쪽 고정 (첫 번째 카테고리에만) */}
{catIndex === 0 && (
<TableCell
rowSpan={5}
className={`font-medium border-r border-gray-200 align-top pt-3 sticky left-0 z-10 ${rowBgClass}`}
>
<div className="flex flex-col gap-2">
<span className="text-sm">{vendor.vendorName}</span>
{/* 거래처명 - 왼쪽 고정 (매 행마다 개별 셀, 첫 행만 내용 표시) */}
<TableCell
className={`font-medium border-r border-gray-200 sticky left-0 z-10 w-[200px] min-w-[200px] max-w-[200px] overflow-hidden whitespace-normal ${rowBgClass} ${catIndex === 0 ? 'align-top pt-3' : 'p-0'}`}
>
{catIndex === 0 && (
<div className="flex flex-col gap-2 overflow-hidden">
<span className="text-sm line-clamp-2 break-words" title={vendor.vendorName}>{vendor.vendorName}</span>
<div className="flex items-center gap-2">
<Switch
checked={vendor.isOverdue}
@@ -531,11 +530,11 @@ export function ReceivablesStatus({ highlightVendorId, initialData, initialSumma
)}
</div>
</div>
</TableCell>
)}
)}
</TableCell>
{/* 구분 - 왼쪽 고정 */}
<TableCell className={`text-center border-r border-gray-200 text-sm sticky left-[120px] z-10 ${rowBgClass}`}>
<TableCell className={`text-center border-r border-gray-200 text-sm sticky left-[200px] z-10 ${rowBgClass}`}>
{CATEGORY_LABELS[category]}
</TableCell>
@@ -561,8 +560,11 @@ export function ReceivablesStatus({ highlightVendorId, initialData, initialSumma
const isMemoExpanded = expandedMemos.has(vendor.id);
rows.push(
<TableRow key={`${vendor.id}-memo`}>
{/* 거래처명 셀 (빈 셀 - 시각적 병합 유지) */}
<TableCell className={`border-r border-gray-200 sticky left-0 z-10 w-[200px] min-w-[200px] max-w-[200px] overflow-hidden p-0 ${rowBgClass}`} />
{/* 구분: 메모 + 접기/펼치기 버튼 */}
<TableCell className={`text-center border-r border-gray-200 text-sm sticky left-[120px] z-10 ${rowBgClass}`}>
<TableCell className={`text-center border-r border-gray-200 text-sm sticky left-[200px] z-10 ${rowBgClass}`}>
<div className="flex items-center justify-center gap-1">
<span></span>
<button
@@ -587,7 +589,7 @@ export function ReceivablesStatus({ highlightVendorId, initialData, initialSumma
placeholder="거래처 메모를 입력하세요..."
className="w-full text-sm resize-none transition-all !min-h-0 !py-1"
style={{
height: isMemoExpanded ? '72px' : '24px',
height: isMemoExpanded ? '72px' : '32px',
lineHeight: '24px',
overflowY: isMemoExpanded ? 'auto' : 'hidden',
}}
@@ -606,7 +608,7 @@ export function ReceivablesStatus({ highlightVendorId, initialData, initialSumma
<TableCell className="border-r border-gray-200 sticky left-0 z-10 bg-gray-100">
</TableCell>
<TableCell className="text-center border-r border-gray-200 sticky left-[120px] z-10 bg-gray-100">
<TableCell className="text-center border-r border-gray-200 sticky left-[200px] z-10 bg-gray-100">
</TableCell>
{/* 월별 합계 - 스크롤 영역 */}

View File

@@ -546,14 +546,14 @@ export function DraftBox() {
];
},
headerActions: () => (
<div className="ml-auto flex gap-2">
<Button variant="outline" onClick={handleSendNotification}>
<Bell className="h-4 w-4 mr-2" />
</Button>
</div>
),
// headerActions: () => (
// <div className="ml-auto flex gap-2">
// <Button variant="outline" onClick={handleSendNotification}>
// <Bell className="h-4 w-4 mr-2" />
// 문서완료
// </Button>
// </div>
// ),
selectionActions: ({ selectedItems, onClearSelection }) => (
<>

View File

@@ -16,6 +16,7 @@ interface SidebarProps {
onToggleSubmenu: (menuId: string) => void;
onToggleAll?: () => void;
onCloseMobileSidebar?: () => void;
onExpandSidebar?: () => void;
}
// 재귀적 메뉴 아이템 컴포넌트 Props
@@ -30,6 +31,7 @@ interface MenuItemComponentProps {
onMenuClick: (menuId: string, path: string) => void;
onToggleSubmenu: (menuId: string) => void;
onCloseMobileSidebar?: () => void;
onExpandSidebar?: () => void;
}
// 재귀적 메뉴 아이템 컴포넌트 (3depth 이상 지원)
@@ -44,6 +46,7 @@ function MenuItemComponent({
onMenuClick,
onToggleSubmenu,
onCloseMobileSidebar,
onExpandSidebar,
}: MenuItemComponentProps) {
const IconComponent = item.icon;
const hasChildren = item.children && item.children.length > 0;
@@ -73,6 +76,10 @@ function MenuItemComponent({
const handleClick = () => {
if (hasChildren) {
// 접힌 상태에서 카테고리 클릭 시 사이드바 자동 펼침
if (sidebarCollapsed && onExpandSidebar) {
onExpandSidebar();
}
onToggleSubmenu(item.id);
} else {
onMenuClick(item.id, item.path);
@@ -171,6 +178,7 @@ function MenuItemComponent({
onMenuClick={onMenuClick}
onToggleSubmenu={onToggleSubmenu}
onCloseMobileSidebar={onCloseMobileSidebar}
onExpandSidebar={onExpandSidebar}
/>
))}
</div>
@@ -233,6 +241,7 @@ function MenuItemComponent({
onMenuClick={onMenuClick}
onToggleSubmenu={onToggleSubmenu}
onCloseMobileSidebar={onCloseMobileSidebar}
onExpandSidebar={onExpandSidebar}
/>
))}
</div>
@@ -297,6 +306,7 @@ function MenuItemComponent({
onMenuClick={onMenuClick}
onToggleSubmenu={onToggleSubmenu}
onCloseMobileSidebar={onCloseMobileSidebar}
onExpandSidebar={onExpandSidebar}
/>
))}
</div>
@@ -318,6 +328,7 @@ export default function Sidebar({
onToggleSubmenu,
onToggleAll,
onCloseMobileSidebar,
onExpandSidebar,
}: SidebarProps) {
// 활성 메뉴 자동 스크롤을 위한 ref
const activeMenuRef = useRef<HTMLDivElement | null>(null);
@@ -381,6 +392,7 @@ export default function Sidebar({
onMenuClick={onMenuClick}
onToggleSubmenu={onToggleSubmenu}
onCloseMobileSidebar={onCloseMobileSidebar}
onExpandSidebar={onExpandSidebar}
/>
))}
</div>

View File

@@ -928,7 +928,7 @@ export function IntegratedListTemplateV2<T = any>({
showActions={tableColumns.some(col => col.key === 'actions')}
/>
) : (
<Table>
<Table className="table-fixed">
<TableHeader>
<TableRow>
{renderCustomTableHeader ? (

View File

@@ -87,7 +87,7 @@ const TableCell = React.forwardRef<
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn("p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", className)}
className={cn("p-2 align-middle whitespace-nowrap overflow-hidden text-ellipsis [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", className)}
{...props}
/>
))

View File

@@ -518,6 +518,13 @@ export default function AuthenticatedLayout({ children }: AuthenticatedLayoutPro
router.push(path);
};
// 접힌 사이드바 자동 펼침 (아이콘 클릭 시)
const expandSidebar = useCallback(() => {
if (sidebarCollapsed) {
toggleSidebar();
}
}, [sidebarCollapsed, toggleSidebar]);
// 서브메뉴 토글 함수
const toggleSubmenu = (menuId: string) => {
setExpandedMenus(prev =>
@@ -1264,6 +1271,7 @@ export default function AuthenticatedLayout({ children }: AuthenticatedLayoutPro
onMenuClick={handleMenuClick}
onToggleSubmenu={toggleSubmenu}
onToggleAll={toggleAllMenus}
onExpandSidebar={expandSidebar}
/>
</div>