[agent-pipeline] pipe-20260305181927-h4x5 round-4
This commit is contained in:
parent
182634f852
commit
2439951fab
|
|
@ -254,22 +254,36 @@ function ReceivedTab() {
|
||||||
|
|
||||||
const [processOpen, setProcessOpen] = useState(false);
|
const [processOpen, setProcessOpen] = useState(false);
|
||||||
const [selectedLine, setSelectedLine] = useState<ApprovalLine | null>(null);
|
const [selectedLine, setSelectedLine] = useState<ApprovalLine | null>(null);
|
||||||
|
const [selectedRequest, setSelectedRequest] = useState<ApprovalRequest | null>(null);
|
||||||
|
const [detailLoading, setDetailLoading] = useState(false);
|
||||||
const [comment, setComment] = useState("");
|
const [comment, setComment] = useState("");
|
||||||
const [isProcessing, setIsProcessing] = useState(false);
|
const [isProcessing, setIsProcessing] = useState(false);
|
||||||
|
|
||||||
const fetchPending = useCallback(async () => {
|
const fetchPending = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const res = await getMyPendingApprovals();
|
try {
|
||||||
if (res.success && res.data) setPendingLines(res.data);
|
const res = await getMyPendingApprovals();
|
||||||
|
if (res.success && res.data) setPendingLines(res.data);
|
||||||
|
} catch {
|
||||||
|
setPendingLines([]);
|
||||||
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => { fetchPending(); }, [fetchPending]);
|
useEffect(() => { fetchPending(); }, [fetchPending]);
|
||||||
|
|
||||||
const openProcess = (line: ApprovalLine) => {
|
const openProcess = async (line: ApprovalLine) => {
|
||||||
setSelectedLine(line);
|
setSelectedLine(line);
|
||||||
|
setSelectedRequest(null);
|
||||||
setComment("");
|
setComment("");
|
||||||
setProcessOpen(true);
|
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") => {
|
const handleProcess = async (action: "approved" | "rejected") => {
|
||||||
|
|
@ -342,14 +356,14 @@ function ReceivedTab() {
|
||||||
|
|
||||||
{/* 결재 처리 모달 */}
|
{/* 결재 처리 모달 */}
|
||||||
<Dialog open={processOpen} onOpenChange={setProcessOpen}>
|
<Dialog open={processOpen} onOpenChange={setProcessOpen}>
|
||||||
<DialogContent className="max-w-[95vw] sm:max-w-[450px]">
|
<DialogContent className="max-w-[95vw] sm:max-w-[520px]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="text-base sm:text-lg">결재 처리</DialogTitle>
|
<DialogTitle className="text-base sm:text-lg">결재 처리</DialogTitle>
|
||||||
<DialogDescription className="text-xs sm:text-sm">
|
<DialogDescription className="text-xs sm:text-sm">
|
||||||
{selectedLine?.title}
|
{selectedLine?.title}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</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 className="grid grid-cols-2 gap-3 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground text-xs">요청자</span>
|
<span className="text-muted-foreground text-xs">요청자</span>
|
||||||
|
|
@ -359,18 +373,84 @@ function ReceivedTab() {
|
||||||
<span className="text-muted-foreground text-xs">결재 단계</span>
|
<span className="text-muted-foreground text-xs">결재 단계</span>
|
||||||
<p className="mt-1 font-medium">{selectedLine?.step_order}차 결재</p>
|
<p className="mt-1 font-medium">{selectedLine?.step_order}차 결재</p>
|
||||||
</div>
|
</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>
|
</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>
|
<div>
|
||||||
<Label className="text-xs sm:text-sm">의견</Label>
|
<Label className="text-xs sm:text-sm">결재 의견</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
value={comment}
|
value={comment}
|
||||||
onChange={(e) => setComment(e.target.value)}
|
onChange={(e) => setComment(e.target.value)}
|
||||||
placeholder="결재 의견을 입력하세요 (선택사항)"
|
placeholder="결재 의견을 입력하세요 (선택사항)"
|
||||||
className="min-h-[80px] text-xs sm:text-sm"
|
className="mt-1 min-h-[80px] text-xs sm:text-sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter className="gap-2 sm:gap-0">
|
<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
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
onClick={() => handleProcess("rejected")}
|
onClick={() => handleProcess("rejected")}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue