1
0
forked from baron/baron-sso

개발자 권한 신청 승인/취소 및 RP 생성 흐름 개선

This commit is contained in:
2026-04-22 14:39:06 +09:00
parent 2216d9c4e4
commit 685923a03e
12 changed files with 382 additions and 44 deletions

View File

@@ -30,6 +30,7 @@ import {
import { Textarea } from "../../components/ui/textarea";
import {
approveDeveloperRequest,
cancelDeveloperRequestApproval,
fetchDeveloperRequests,
rejectDeveloperRequest,
requestDeveloperAccess,
@@ -72,6 +73,16 @@ export default function DeveloperRequestPage() {
},
});
const cancelApprovalMutation = useMutation({
mutationFn: ({ id, adminNotes }: { id: number; adminNotes: string }) =>
cancelDeveloperRequestApproval(id, adminNotes),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["developer-requests"] });
queryClient.invalidateQueries({ queryKey: ["developer-request"] });
alert(t("msg.dev.request.cancelled", "승인이 취소되었습니다."));
},
});
const handleApprove = (id: number) => {
approveMutation.mutate({ id, adminNotes: adminNotes[id] || "" });
};
@@ -84,6 +95,14 @@ export default function DeveloperRequestPage() {
rejectMutation.mutate({ id, adminNotes: adminNotes[id] });
};
const handleCancelApproval = (id: number) => {
if (!adminNotes[id]) {
alert(t("msg.dev.request.need_cancel_notes", "승인 취소 사유를 입력해주세요."));
return;
}
cancelApprovalMutation.mutate({ id, adminNotes: adminNotes[id] });
};
if (isLoading) {
return (
<div className="p-8 text-center">
@@ -95,6 +114,10 @@ export default function DeveloperRequestPage() {
const hasActiveRequest = requests?.some(
(r) => r.status === "pending" || r.status === "approved",
);
const isActionPending =
approveMutation.isPending ||
rejectMutation.isPending ||
cancelApprovalMutation.isPending;
return (
<div className="space-y-6">
@@ -211,7 +234,7 @@ export default function DeveloperRequestPage() {
variant="outline"
className="text-destructive hover:bg-destructive/10"
onClick={() => handleReject(req.id)}
disabled={rejectMutation.isPending}
disabled={isActionPending}
>
<XCircle className="mr-1 h-3 w-3" />
{t("ui.common.reject", "반려")}
@@ -220,17 +243,44 @@ export default function DeveloperRequestPage() {
size="sm"
className="bg-emerald-600 hover:bg-emerald-700"
onClick={() => handleApprove(req.id)}
disabled={approveMutation.isPending}
disabled={isActionPending}
>
<CheckCircle2 className="mr-1 h-3 w-3" />
{t("ui.common.approve", "승인")}
</Button>
</div>
</div>
) : req.status === "approved" ? (
<div className="flex flex-col gap-2 min-w-[200px] items-end ml-auto">
<Input
placeholder={t(
"ui.dev.request.cancel_notes_placeholder",
"승인 취소 사유 입력...",
)}
className="h-8 text-xs"
value={adminNotes[req.id] || ""}
onChange={(e) =>
setAdminNotes({
...adminNotes,
[req.id]: e.target.value,
})
}
/>
<Button
size="sm"
variant="outline"
className="text-destructive hover:bg-destructive/10"
onClick={() => handleCancelApproval(req.id)}
disabled={isActionPending}
>
<XCircle className="mr-1 h-3 w-3" />
{t("ui.dev.request.cancel_approval", "승인 취소")}
</Button>
</div>
) : (
<span className="text-muted-foreground text-xs italic">
{req.status === "approved"
? t("ui.common.completed", "처리 완료")
{req.status === "cancelled"
? t("ui.dev.request.status.cancelled", "승인 취소됨")
: t("ui.common.rejected", "반려됨")}
</span>
)}
@@ -282,6 +332,13 @@ function StatusBadge({ status }: { status: string }) {
{t("ui.dev.request.status.rejected", "반려됨")}
</Badge>
);
case "cancelled":
return (
<Badge variant="muted" className="gap-1">
<XCircle className="h-3 w-3" />
{t("ui.dev.request.status.cancelled", "승인 취소됨")}
</Badge>
);
default:
return <Badge variant="muted">{status}</Badge>;
}