Merge pull request 'fix(대무자): COMPANY_ADMIN + SQL + UI select' from johngreen into main
Build & Deploy to K8s / build-and-deploy (push) Failing after 3m24s

This commit is contained in:
2026-05-12 17:02:44 +09:00
4 changed files with 64 additions and 13 deletions
@@ -6,12 +6,20 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
getSubstituteList,
createSubstitute,
deleteSubstitute,
checkSubstituteOverlap,
} from "@/lib/api/substitute";
import { getUserList } from "@/lib/api/user";
/**
* 사용자별 대무자(代務者) 관리 섹션.
@@ -39,6 +47,10 @@ export function SubstituteSection({ originalUserId, originalUserName }: Substitu
const [dialogOpen, setDialogOpen] = useState(false);
const [submitting, setSubmitting] = useState(false);
// 같은 회사 활성 사용자 목록 (대무자 후보) — 본인/SUPER_ADMIN/비활성 제외
const [candidates, setCandidates] = useState<Record<string, any>[]>([]);
const [candidatesLoading, setCandidatesLoading] = useState(false);
const [form, setForm] = useState({
proxy_user_id: "",
start_date: "",
@@ -63,10 +75,29 @@ export function SubstituteSection({ originalUserId, originalUserName }: Substitu
load();
}, [load]);
// 대무자 후보 사용자 목록 로드 (다이얼로그 열릴 때 한번)
const loadCandidates = useCallback(async () => {
setCandidatesLoading(true);
const res = await getUserList({ status: "active", limit: 1000 });
if (res?.success && Array.isArray(res.data?.list)) {
const filtered = (res.data.list as Record<string, any>[]).filter(
(u) =>
u.user_id !== originalUserId &&
u.user_type !== "SUPER_ADMIN" &&
u.status === "active",
);
setCandidates(filtered);
} else {
setCandidates([]);
}
setCandidatesLoading(false);
}, [originalUserId]);
const openDialog = () => {
setForm({ proxy_user_id: "", start_date: "", end_date: "", reason: "" });
setError(null);
setDialogOpen(true);
loadCandidates();
};
const submit = async () => {
@@ -215,16 +246,36 @@ export function SubstituteSection({ originalUserId, originalUserName }: Substitu
<div className="space-y-3 py-2 text-sm">
<div>
<Label htmlFor="proxy_user_id"> ID</Label>
<Input
id="proxy_user_id"
<Label htmlFor="proxy_user_id"></Label>
<Select
value={form.proxy_user_id}
onChange={(e) => setForm({ ...form, proxy_user_id: e.target.value })}
placeholder="예: hjkim"
autoFocus
/>
onValueChange={(v) => setForm({ ...form, proxy_user_id: v })}
>
<SelectTrigger id="proxy_user_id" autoFocus>
<SelectValue
placeholder={
candidatesLoading
? "사용자 목록 불러오는 중..."
: candidates.length === 0
? "지정 가능한 사용자가 없습니다"
: "대무자를 선택하세요"
}
/>
</SelectTrigger>
<SelectContent>
{candidates.map((u) => (
<SelectItem key={u.user_id} value={u.user_id}>
{u.user_name || u.user_id}
<span className="ml-1 text-muted-foreground">
({u.user_id}
{u.dept_name ? ` · ${u.dept_name}` : ""})
</span>
</SelectItem>
))}
</SelectContent>
</Select>
<p className="mt-1 text-[0.7rem] text-muted-foreground">
. SUPER_ADMIN .
. ·SUPER_ADMIN .
</p>
</div>