Files
sam-react-prod/src/components/accounting/TaxInvoiceManagement/CardHistoryModal.tsx
유병철 f344dc7d00 refactor(WEB): 회계/견적/설정/생산 등 전반적 코드 개선 및 공통화 2차
- 회계 모듈 전면 개선: 청구/입금/출금/매입/매출/세금계산서/일반전표/거래처원장 등
- 견적 모듈 금액 포맷/할인/수식/미리보기 등 코드 정리
- 설정 모듈: 계정관리/직급/직책/권한 상세 간소화
- 생산 모듈: 작업지시서/작업자화면/검수 문서 코드 정리
- UniversalListPage 엑셀 다운로드 및 필터 기능 확장
- 대시보드/게시판/수주 등 날짜 유틸 공통화 적용
- claudedocs 문서 인덱스 업데이트

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 10:45:47 +09:00

179 lines
6.0 KiB
TypeScript

'use client';
/**
* 카드 내역 불러오기 팝업 (팝업 in 팝업)
*
* - ManualEntryModal 위에 z-index로 표시
* - 날짜범위 + 가맹점/승인번호 검색 + 조회
* - 빈 상태: "카드 내역이 없습니다."
* - 테이블: 날짜, 가맹점, 금액, 승인번호, 선택 버튼
* - 선택 시 부모에 데이터 전달 후 닫기
*/
import { useState, useCallback } from 'react';
import { toast } from 'sonner';
import { formatNumber } from '@/lib/utils/amount';
import { Search } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { DatePicker } from '@/components/ui/date-picker';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { getTodayString, getLocalDateString } from '@/lib/utils/date';
import { getCardHistory } from './actions';
import type { CardHistoryRecord } from './types';
interface CardHistoryModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onSelect: (record: CardHistoryRecord) => void;
}
export function CardHistoryModal({
open,
onOpenChange,
onSelect,
}: CardHistoryModalProps) {
const today = getTodayString();
const monthAgo = getLocalDateString(new Date(Date.now() - 30 * 24 * 60 * 60 * 1000));
const [startDate, setStartDate] = useState(monthAgo);
const [endDate, setEndDate] = useState(today);
const [searchText, setSearchText] = useState('');
const [data, setData] = useState<CardHistoryRecord[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [hasSearched, setHasSearched] = useState(false);
const handleSearch = useCallback(async () => {
setIsLoading(true);
setHasSearched(true);
try {
const result = await getCardHistory({
startDate,
endDate,
search: searchText,
page: 1,
perPage: 50,
});
if (result.success) {
setData(result.data ?? []);
} else {
toast.error(result.error || '카드 내역 조회에 실패했습니다.');
}
} catch {
toast.error('서버 오류가 발생했습니다.');
} finally {
setIsLoading(false);
}
}, [startDate, endDate, searchText]);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[600px] max-h-[80vh] flex flex-col">
<DialogHeader>
<DialogTitle> </DialogTitle>
</DialogHeader>
{/* 검색 영역 */}
<div className="space-y-2">
{/* Row1: 날짜 범위 */}
<div className="flex items-center gap-2">
<DatePicker
value={startDate}
onChange={setStartDate}
className="flex-1"
/>
<span className="text-sm text-muted-foreground shrink-0">~</span>
<DatePicker
value={endDate}
onChange={setEndDate}
className="flex-1"
/>
</div>
{/* Row2: 검색어 + 조회 */}
<div className="flex items-center gap-2">
<Input
placeholder="가맹점/승인번호"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
className="flex-1 h-9 text-sm"
/>
<Button size="sm" onClick={handleSearch} disabled={isLoading}>
<Search className="h-4 w-4 mr-1" />
</Button>
</div>
</div>
{/* 테이블 영역 */}
<div className="flex-1 overflow-auto border rounded-md">
{!hasSearched ? (
<div className="flex items-center justify-center h-[200px] text-sm text-muted-foreground">
.
</div>
) : isLoading ? (
<div className="flex items-center justify-center h-[200px] text-sm text-muted-foreground">
...
</div>
) : data.length === 0 ? (
<div className="flex items-center justify-center h-[200px] text-sm text-muted-foreground">
.
</div>
) : (
<Table>
<TableHeader>
<TableRow>
<TableHead className="text-center"></TableHead>
<TableHead></TableHead>
<TableHead className="text-right"></TableHead>
<TableHead className="text-center"></TableHead>
<TableHead className="text-center w-[70px]"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((item) => (
<TableRow key={item.id}>
<TableCell className="text-center text-sm">
{item.transactionDate}
</TableCell>
<TableCell className="text-sm">{item.merchantName}</TableCell>
<TableCell className="text-right text-sm font-medium">
{formatNumber(item.amount)}
</TableCell>
<TableCell className="text-center text-sm text-muted-foreground">
{item.approvalNumber}
</TableCell>
<TableCell className="text-center">
<Button
variant="outline"
size="sm"
className="h-7 px-2 text-xs"
onClick={() => onSelect(item)}
>
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</div>
</DialogContent>
</Dialog>
);
}