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:
@@ -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]' },
|
||||
|
||||
@@ -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>
|
||||
{/* 월별 합계 - 스크롤 영역 */}
|
||||
|
||||
@@ -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 }) => (
|
||||
<>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -928,7 +928,7 @@ export function IntegratedListTemplateV2<T = any>({
|
||||
showActions={tableColumns.some(col => col.key === 'actions')}
|
||||
/>
|
||||
) : (
|
||||
<Table>
|
||||
<Table className="table-fixed">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
{renderCustomTableHeader ? (
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
))
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user