forked from baron/baron-sso
개발자 권한 신청 테이블 공통 스타일 적용
This commit is contained in:
@@ -27,6 +27,10 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "../../components/ui/table";
|
||||
import {
|
||||
commonTableShellClass,
|
||||
commonTableViewportClass,
|
||||
} from "../../../../common/ui/table";
|
||||
import { Textarea } from "../../components/ui/textarea";
|
||||
import {
|
||||
approveDeveloperRequest,
|
||||
@@ -185,158 +189,162 @@ export default function DeveloperRequestPage() {
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
{isSuperAdmin && (
|
||||
<TableHead>
|
||||
{t("ui.dev.request.table.user", "사용자")}
|
||||
</TableHead>
|
||||
)}
|
||||
<TableHead>{t("ui.dev.request.table.org", "소속")}</TableHead>
|
||||
<TableHead>
|
||||
{t("ui.dev.request.table.reason", "신청 사유")}
|
||||
</TableHead>
|
||||
<TableHead>
|
||||
{t("ui.dev.request.table.status", "상태")}
|
||||
</TableHead>
|
||||
<TableHead>
|
||||
{t("ui.dev.request.table.date", "신청일")}
|
||||
</TableHead>
|
||||
{isSuperAdmin && (
|
||||
<TableHead className="text-right">
|
||||
{t("ui.dev.request.table.actions", "관리")}
|
||||
</TableHead>
|
||||
)}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{!requests || requests.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={isSuperAdmin ? 6 : 4}
|
||||
className="h-32 text-center text-muted-foreground"
|
||||
>
|
||||
{t("msg.dev.request.empty", "신청 내역이 없습니다.")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
requests.map((req) => (
|
||||
<TableRow key={req.id}>
|
||||
<div className={commonTableShellClass}>
|
||||
<div className={commonTableViewportClass}>
|
||||
<Table>
|
||||
<TableHeader className="sticky top-0 z-10 bg-secondary shadow-sm">
|
||||
<TableRow>
|
||||
{isSuperAdmin && (
|
||||
<TableCell className="font-medium">
|
||||
<div>{req.name}</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{req.email || req.userId}
|
||||
</div>
|
||||
{(req.phone || req.role) && (
|
||||
<div className="mt-1 text-xs text-muted-foreground">
|
||||
{[req.phone, req.role].filter(Boolean).join(" / ")}
|
||||
</div>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableHead>
|
||||
{t("ui.dev.request.table.user", "사용자")}
|
||||
</TableHead>
|
||||
)}
|
||||
<TableCell>{req.organization}</TableCell>
|
||||
<TableCell className="max-w-md">
|
||||
<div className="truncate" title={req.reason}>
|
||||
{req.reason}
|
||||
</div>
|
||||
{req.adminNotes && (
|
||||
<div className="mt-1 text-xs text-amber-600 bg-amber-50 dark:bg-amber-900/20 p-1.5 rounded">
|
||||
<strong>Admin:</strong> {req.adminNotes}
|
||||
</div>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<StatusBadge status={req.status} />
|
||||
</TableCell>
|
||||
<TableCell className="text-muted-foreground text-sm">
|
||||
{new Date(req.createdAt).toLocaleDateString()}
|
||||
</TableCell>
|
||||
<TableHead>{t("ui.dev.request.table.org", "소속")}</TableHead>
|
||||
<TableHead>
|
||||
{t("ui.dev.request.table.reason", "신청 사유")}
|
||||
</TableHead>
|
||||
<TableHead>
|
||||
{t("ui.dev.request.table.status", "상태")}
|
||||
</TableHead>
|
||||
<TableHead>
|
||||
{t("ui.dev.request.table.date", "신청일")}
|
||||
</TableHead>
|
||||
{isSuperAdmin && (
|
||||
<TableCell className="text-right">
|
||||
{req.status === "pending" ? (
|
||||
<div className="flex flex-col gap-2 min-w-[200px] items-end ml-auto">
|
||||
<Input
|
||||
placeholder={t(
|
||||
"ui.dev.request.admin_notes_placeholder",
|
||||
"메모 입력 (선택)...",
|
||||
)}
|
||||
className="h-8 text-xs"
|
||||
value={adminNotes[req.id] || ""}
|
||||
onChange={(e) =>
|
||||
setAdminNotes({
|
||||
...adminNotes,
|
||||
[req.id]: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="text-destructive hover:bg-destructive/10"
|
||||
onClick={() => handleReject(req.id)}
|
||||
disabled={isActionPending}
|
||||
>
|
||||
<XCircle className="mr-1 h-3 w-3" />
|
||||
{t("ui.common.reject", "반려")}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-emerald-600 hover:bg-emerald-700"
|
||||
onClick={() => handleApprove(req.id)}
|
||||
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 === "cancelled"
|
||||
? t(
|
||||
"ui.dev.request.status.cancelled",
|
||||
"승인 취소됨",
|
||||
)
|
||||
: t("ui.common.rejected", "반려됨")}
|
||||
</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableHead className="text-right">
|
||||
{t("ui.dev.request.table.actions", "관리")}
|
||||
</TableHead>
|
||||
)}
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{!requests || requests.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={isSuperAdmin ? 6 : 4}
|
||||
className="h-32 text-center text-muted-foreground"
|
||||
>
|
||||
{t("msg.dev.request.empty", "신청 내역이 없습니다.")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
requests.map((req) => (
|
||||
<TableRow key={req.id}>
|
||||
{isSuperAdmin && (
|
||||
<TableCell className="font-medium">
|
||||
<div>{req.name}</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{req.email || req.userId}
|
||||
</div>
|
||||
{(req.phone || req.role) && (
|
||||
<div className="mt-1 text-xs text-muted-foreground">
|
||||
{[req.phone, req.role].filter(Boolean).join(" / ")}
|
||||
</div>
|
||||
)}
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell>{req.organization}</TableCell>
|
||||
<TableCell className="max-w-md">
|
||||
<div className="truncate" title={req.reason}>
|
||||
{req.reason}
|
||||
</div>
|
||||
{req.adminNotes && (
|
||||
<div className="mt-1 rounded bg-amber-50 p-1.5 text-xs text-amber-600 dark:bg-amber-900/20">
|
||||
<strong>Admin:</strong> {req.adminNotes}
|
||||
</div>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<StatusBadge status={req.status} />
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground">
|
||||
{new Date(req.createdAt).toLocaleDateString()}
|
||||
</TableCell>
|
||||
{isSuperAdmin && (
|
||||
<TableCell className="text-right">
|
||||
{req.status === "pending" ? (
|
||||
<div className="ml-auto flex min-w-[200px] flex-col items-end gap-2">
|
||||
<Input
|
||||
placeholder={t(
|
||||
"ui.dev.request.admin_notes_placeholder",
|
||||
"메모 입력 (선택)...",
|
||||
)}
|
||||
className="h-8 text-xs"
|
||||
value={adminNotes[req.id] || ""}
|
||||
onChange={(e) =>
|
||||
setAdminNotes({
|
||||
...adminNotes,
|
||||
[req.id]: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="text-destructive hover:bg-destructive/10"
|
||||
onClick={() => handleReject(req.id)}
|
||||
disabled={isActionPending}
|
||||
>
|
||||
<XCircle className="mr-1 h-3 w-3" />
|
||||
{t("ui.common.reject", "반려")}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-emerald-600 hover:bg-emerald-700"
|
||||
onClick={() => handleApprove(req.id)}
|
||||
disabled={isActionPending}
|
||||
>
|
||||
<CheckCircle2 className="mr-1 h-3 w-3" />
|
||||
{t("ui.common.approve", "승인")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : req.status === "approved" ? (
|
||||
<div className="ml-auto flex min-w-[200px] flex-col items-end gap-2">
|
||||
<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-xs italic text-muted-foreground">
|
||||
{req.status === "cancelled"
|
||||
? t(
|
||||
"ui.dev.request.status.cancelled",
|
||||
"승인 취소됨",
|
||||
)
|
||||
: t("ui.common.rejected", "반려됨")}
|
||||
</span>
|
||||
)}
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user