1
0
forked from baron/baron-sso

테넌트 목록 조회 cursor기반으로 재구성. 사용자 metadata 미사용 필드 제거

This commit is contained in:
2026-05-13 18:05:51 +09:00
parent a4d707d4d8
commit 5e7b7b878c
85 changed files with 4808 additions and 734 deletions

View File

@@ -56,12 +56,11 @@ import {
type TenantSummary,
type UserAppointment,
type UserUpdateRequest,
createTenant,
deleteUser,
fetchAllTenants,
fetchMe,
fetchPasswordPolicy,
fetchTenant,
fetchTenants,
fetchUser,
fetchUserRpHistory,
updateUser,
@@ -78,11 +77,12 @@ import {
parseOrgChartTenantSelection,
} from "./orgChartPicker";
import type { UserSchemaField } from "./userSchemaFields";
import { resolvePersonalTenant } from "./utils/personalTenant";
type UserFormValues = Omit<UserUpdateRequest, "metadata"> & {
metadata: Record<string, Record<string, string | number | boolean>>;
};
type UserType = "hanmac" | "external" | "personal";
type UserCategory = "hanmac" | "external" | "personal";
type PasswordResetMode = "generated" | "manual";
type PickerTarget = { kind: "appointment"; index: number };
@@ -318,8 +318,8 @@ function UserDetailPage() {
const [passwordResetError, setPasswordResetError] = React.useState<
string | null
>(null);
const [isHanmacFamily, setIsHanmacFamily] = React.useState(false);
const [userType, setUserType] = React.useState<UserType>("external");
const [userCategory, setUserCategory] =
React.useState<UserCategory>("external");
const [additionalAppointments, setAdditionalAppointments] = React.useState<
AppointmentDraft[]
>([]);
@@ -346,8 +346,8 @@ function UserDetailPage() {
});
const { data: tenantsData } = useQuery({
queryKey: ["tenants", { limit: 100 }],
queryFn: () => fetchTenants(100, 0),
queryKey: ["tenants", "all"],
queryFn: () => fetchAllTenants(),
});
const tenants = React.useMemo(
() => tenantsData?.items ?? [],
@@ -465,20 +465,14 @@ function UserDetailPage() {
return tenants.find((tenant) => tenant.slug === "hanmac-family")?.id ?? "";
}, [tenants]);
const personalTenant = React.useMemo(
() =>
tenants.find(
(tenant) =>
tenant.slug === "personal" ||
(tenant.type === "PERSONAL" &&
tenant.name.toLowerCase() === "personal"),
),
() => resolvePersonalTenant(tenants),
[tenants],
);
const pickerUrl = buildAuthenticatedOrgChartTenantPickerUrl(
import.meta.env.ORGFRONT_URL,
{
tenantId: userType === "hanmac" ? hanmacFamilyTenantId : undefined,
tenantId: userCategory === "hanmac" ? hanmacFamilyTenantId : undefined,
},
);
@@ -566,25 +560,16 @@ function UserDetailPage() {
);
};
const handleUserTypeChange = (value: string) => {
const nextType = value as UserType;
setUserType(nextType);
setIsHanmacFamily(nextType === "hanmac");
if (nextType !== "hanmac") {
const handleUserCategoryChange = (value: string) => {
const nextCategory = value as UserCategory;
setUserCategory(nextCategory);
if (nextCategory !== "hanmac") {
setAdditionalAppointments([]);
}
};
const ensurePersonalTenant = async () => {
if (personalTenant) return personalTenant;
const tenant = await createTenant({
name: "Personal",
slug: "personal",
type: "PERSONAL",
status: "active",
});
queryClient.invalidateQueries({ queryKey: ["tenants"] });
return tenant;
return personalTenant;
};
React.useEffect(() => {
@@ -638,14 +623,18 @@ function UserDetailPage() {
tenants,
hanmacFamilyTenantId,
);
const resolvedUserType =
metadata.userType === "personal" || user.companyCode === "personal"
? "personal"
: isUserHanmacFamily
? "hanmac"
: "external";
setUserType(resolvedUserType);
setIsHanmacFamily(resolvedUserType === "hanmac");
const isPersonalUser =
user.companyCode === personalTenant.slug ||
user.tenantSlug === personalTenant.slug ||
user.tenant?.id === personalTenant.id ||
user.tenant?.slug === personalTenant.slug ||
metadata.personalTenantId === personalTenant.id;
const resolvedUserCategory = isPersonalUser
? "personal"
: isUserHanmacFamily
? "hanmac"
: "external";
setUserCategory(resolvedUserCategory);
const familyFallbackTenants = [
...(user.joinedTenants ?? []),
...(user.tenant ? [user.tenant] : []),
@@ -696,7 +685,7 @@ function UserDetailPage() {
: [],
);
}
}, [hanmacFamilyTenantId, tenants, user, reset]);
}, [hanmacFamilyTenantId, personalTenant, tenants, user, reset]);
const mutation = useMutation({
mutationFn: (data: UserUpdateRequest) => updateUser(userId, data),
@@ -737,10 +726,13 @@ function UserDetailPage() {
}),
);
const {
hanmacFamily: _hanmacFamily,
userType: _userType,
...safeMetadata
} = cleanMetadata;
const metadata: Record<string, unknown> = {
...cleanMetadata,
hanmacFamily: userType === "hanmac" && isHanmacFamily,
userType,
...safeMetadata,
};
const profileData = { ...data };
@@ -750,7 +742,7 @@ function UserDetailPage() {
metadata,
};
if (userType === "personal") {
if (userCategory === "personal") {
try {
const tenant = await ensurePersonalTenant();
payload.tenantSlug = tenant.slug;
@@ -768,7 +760,7 @@ function UserDetailPage() {
}
}
if (userType === "hanmac") {
if (userCategory === "hanmac") {
const appointments = additionalAppointments
.filter((appointment) => appointment.tenantId)
.map((appointment) => ({
@@ -1071,8 +1063,8 @@ function UserDetailPage() {
</div>
<Tabs
value={userType}
onValueChange={handleUserTypeChange}
value={userCategory}
onValueChange={handleUserCategoryChange}
className="space-y-4 pt-6 border-t border-dashed"
>
<TabsList className="flex h-auto w-full justify-start rounded-none border-b bg-transparent p-0 text-foreground">
@@ -1097,7 +1089,7 @@ function UserDetailPage() {
</TabsList>
</Tabs>
{userType === "external" && (
{userCategory === "external" && (
<div className="grid gap-8 md:grid-cols-2">
<div className="space-y-2">
<Label
@@ -1141,7 +1133,7 @@ function UserDetailPage() {
</div>
)}
{userType === "hanmac" && (
{userCategory === "hanmac" && (
<div className="space-y-4 rounded-md border p-4">
<div className="space-y-4">
<div className="space-y-3">
@@ -1314,7 +1306,7 @@ function UserDetailPage() {
</div>
)}
{userType === "personal" && (
{userCategory === "personal" && (
<div className="rounded-md border bg-muted/30 p-4 text-sm">
{personalTenant
? `Personal (${personalTenant.slug})`
@@ -1322,7 +1314,7 @@ function UserDetailPage() {
</div>
)}
{userType === "external" && (
{userCategory === "external" && (
<div className="grid gap-6 md:grid-cols-3 pt-8 border-t">
<div className="space-y-2">
<Label