[agent-pipeline] pipe-20260305181927-h4x5 round-4

This commit is contained in:
DDD1542 2026-03-06 03:46:39 +09:00
parent 182634f852
commit 2439951fab
1 changed files with 87 additions and 7 deletions

View File

@ -254,22 +254,36 @@ function ReceivedTab() {
const [processOpen, setProcessOpen] = useState(false);
const [selectedLine, setSelectedLine] = useState<ApprovalLine | null>(null);
const [selectedRequest, setSelectedRequest] = useState<ApprovalRequest | null>(null);
const [detailLoading, setDetailLoading] = useState(false);
const [comment, setComment] = useState("");
const [isProcessing, setIsProcessing] = useState(false);
const fetchPending = useCallback(async () => {
setLoading(true);
const res = await getMyPendingApprovals();
if (res.success && res.data) setPendingLines(res.data);
try {
const res = await getMyPendingApprovals();
if (res.success && res.data) setPendingLines(res.data);
} catch {
setPendingLines([]);
}
setLoading(false);
}, []);
useEffect(() => { fetchPending(); }, [fetchPending]);
const openProcess = (line: ApprovalLine) => {
const openProcess = async (line: ApprovalLine) => {
setSelectedLine(line);
setSelectedRequest(null);
setComment("");
setProcessOpen(true);
// 결재 상세 내용(결재선 포함) 비동기 로드
if (line.request_id) {
setDetailLoading(true);
const res = await getApprovalRequest(line.request_id);
if (res.success && res.data) setSelectedRequest(res.data);
setDetailLoading(false);
}
};
const handleProcess = async (action: "approved" | "rejected") => {
@ -342,14 +356,14 @@ function ReceivedTab() {
{/* 결재 처리 모달 */}
<Dialog open={processOpen} onOpenChange={setProcessOpen}>
<DialogContent className="max-w-[95vw] sm:max-w-[450px]">
<DialogContent className="max-w-[95vw] sm:max-w-[520px]">
<DialogHeader>
<DialogTitle className="text-base sm:text-lg"> </DialogTitle>
<DialogDescription className="text-xs sm:text-sm">
{selectedLine?.title}
</DialogDescription>
</DialogHeader>
<div className="space-y-3">
<div className="max-h-[55vh] space-y-4 overflow-y-auto">
<div className="grid grid-cols-2 gap-3 text-sm">
<div>
<span className="text-muted-foreground text-xs"></span>
@ -359,18 +373,84 @@ function ReceivedTab() {
<span className="text-muted-foreground text-xs"> </span>
<p className="mt-1 font-medium">{selectedLine?.step_order} </p>
</div>
{selectedLine?.target_table && (
<div>
<span className="text-muted-foreground text-xs"> </span>
<p className="mt-1 font-medium">{selectedLine.target_table}</p>
</div>
)}
<div>
<span className="text-muted-foreground text-xs"></span>
<p className="mt-1">{formatDate(selectedLine?.request_created_at || selectedLine?.created_at)}</p>
</div>
</div>
{/* 결재선 상세 */}
{detailLoading ? (
<div className="flex items-center justify-center py-4">
<Loader2 className="text-muted-foreground h-4 w-4 animate-spin" />
<span className="text-muted-foreground ml-2 text-xs"> ...</span>
</div>
) : selectedRequest?.lines && selectedRequest.lines.length > 0 && (
<div>
<span className="text-muted-foreground text-xs"> </span>
<div className="mt-2 space-y-2">
{selectedRequest.lines
.sort((a, b) => a.step_order - b.step_order)
.map((line) => (
<div
key={line.line_id}
className={`flex items-center justify-between rounded-md border p-2 ${
line.line_id === selectedLine?.line_id ? "border-primary bg-primary/5" : "bg-muted/30"
}`}
>
<div className="flex items-center gap-2">
<Badge
variant={line.line_id === selectedLine?.line_id ? "default" : "outline"}
className="text-[10px]"
>
{line.step_order}
</Badge>
<span className="text-sm font-medium">{line.approver_name || line.approver_id}</span>
{line.approver_position && (
<span className="text-muted-foreground text-xs">({line.approver_position})</span>
)}
{line.line_id === selectedLine?.line_id && (
<Badge variant="secondary" className="text-[10px]"> </Badge>
)}
</div>
<div className="flex items-center gap-2">
<StatusBadge status={line.status} />
{line.processed_at && (
<span className="text-muted-foreground text-[10px]">{formatDate(line.processed_at)}</span>
)}
</div>
</div>
))}
</div>
</div>
)}
{/* 의견 입력 */}
<div>
<Label className="text-xs sm:text-sm"></Label>
<Label className="text-xs sm:text-sm"> </Label>
<Textarea
value={comment}
onChange={(e) => setComment(e.target.value)}
placeholder="결재 의견을 입력하세요 (선택사항)"
className="min-h-[80px] text-xs sm:text-sm"
className="mt-1 min-h-[80px] text-xs sm:text-sm"
/>
</div>
</div>
<DialogFooter className="gap-2 sm:gap-0">
<Button
variant="outline"
onClick={() => setProcessOpen(false)}
disabled={isProcessing}
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
>
</Button>
<Button
variant="destructive"
onClick={() => handleProcess("rejected")}