forked from baron/baron-sso
custom claim 타입보정 UI. 대표테넌트 노출 보정
This commit is contained in:
@@ -151,50 +151,111 @@ function assignableSystemRoleValue(role?: string | null) {
|
||||
return isSuperAdminRole(role) ? "super_admin" : "user";
|
||||
}
|
||||
|
||||
function collectAdditionalTenantLabels(user: UserSummary) {
|
||||
const primaryKeys = new Set(
|
||||
[user.tenant?.id, user.tenant?.slug, user.tenantSlug]
|
||||
.filter((value): value is string => Boolean(value))
|
||||
.map((value) => value.toLowerCase()),
|
||||
type RepresentativeTenantCandidate = {
|
||||
id?: string;
|
||||
slug?: string;
|
||||
name?: string;
|
||||
config?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
function stringValue(value: unknown) {
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
}
|
||||
|
||||
function tenantVisibility(tenant?: RepresentativeTenantCandidate) {
|
||||
const visibility = tenant?.config?.visibility;
|
||||
return typeof visibility === "string" ? visibility.trim() : "";
|
||||
}
|
||||
|
||||
function findTenantCandidate(
|
||||
candidate: RepresentativeTenantCandidate,
|
||||
tenants: TenantSummary[],
|
||||
) {
|
||||
const id = candidate.id?.toLowerCase() ?? "";
|
||||
const slug = candidate.slug?.toLowerCase() ?? "";
|
||||
if (!id && !slug) return undefined;
|
||||
return tenants.find(
|
||||
(tenant) =>
|
||||
(id && tenant.id.toLowerCase() === id) ||
|
||||
(slug && tenant.slug.toLowerCase() === slug),
|
||||
);
|
||||
const labels: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
const addLabel = (
|
||||
tenantId?: unknown,
|
||||
tenantSlug?: unknown,
|
||||
tenantName?: unknown,
|
||||
) => {
|
||||
const id = typeof tenantId === "string" ? tenantId.trim() : "";
|
||||
const slug = typeof tenantSlug === "string" ? tenantSlug.trim() : "";
|
||||
const name = typeof tenantName === "string" ? tenantName.trim() : "";
|
||||
const key = (id || slug || name).toLowerCase();
|
||||
if (!key || primaryKeys.has(key) || seen.has(key)) {
|
||||
return;
|
||||
}
|
||||
seen.add(key);
|
||||
labels.push(name || slug || id);
|
||||
};
|
||||
}
|
||||
|
||||
function isPrivateTenantCandidate(
|
||||
candidate: RepresentativeTenantCandidate,
|
||||
tenants: TenantSummary[],
|
||||
) {
|
||||
const tenant = findTenantCandidate(candidate, tenants) ?? candidate;
|
||||
return tenantVisibility(tenant) === "private";
|
||||
}
|
||||
|
||||
function candidateLabel(candidate: RepresentativeTenantCandidate) {
|
||||
return candidate.name || candidate.slug || candidate.id || "";
|
||||
}
|
||||
|
||||
function metadataTenantCandidate(
|
||||
metadata: Record<string, unknown> | undefined,
|
||||
): RepresentativeTenantCandidate | null {
|
||||
const id = stringValue(metadata?.primaryTenantId);
|
||||
const slug = stringValue(metadata?.primaryTenantSlug);
|
||||
const name = stringValue(metadata?.primaryTenantName);
|
||||
if (!id && !slug && !name) return null;
|
||||
return { id, slug, name };
|
||||
}
|
||||
|
||||
function appointmentTenantCandidate(
|
||||
appointment: unknown,
|
||||
): RepresentativeTenantCandidate | null {
|
||||
if (!appointment || typeof appointment !== "object") return null;
|
||||
const value = appointment as Record<string, unknown>;
|
||||
const id = stringValue(value.tenantId);
|
||||
const slug = stringValue(value.tenantSlug ?? value.slug);
|
||||
const name = stringValue(value.tenantName ?? value.name);
|
||||
if (!id && !slug && !name) return null;
|
||||
return { id, slug, name };
|
||||
}
|
||||
|
||||
function resolveRepresentativeTenantLabel(
|
||||
user: UserSummary,
|
||||
tenants: TenantSummary[],
|
||||
) {
|
||||
const candidates: RepresentativeTenantCandidate[] = [];
|
||||
const knownTenants = [
|
||||
...(user.tenant ? [user.tenant] : []),
|
||||
...(user.joinedTenants ?? []),
|
||||
...tenants,
|
||||
];
|
||||
const primaryFromMetadata = metadataTenantCandidate(user.metadata);
|
||||
if (primaryFromMetadata) candidates.push(primaryFromMetadata);
|
||||
if (user.tenant) candidates.push(user.tenant);
|
||||
|
||||
for (const tenant of user.joinedTenants ?? []) {
|
||||
addLabel(tenant.id, tenant.slug, tenant.name);
|
||||
candidates.push(tenant);
|
||||
}
|
||||
|
||||
const appointments = user.metadata?.additionalAppointments;
|
||||
if (Array.isArray(appointments)) {
|
||||
for (const appointment of appointments) {
|
||||
if (!appointment || typeof appointment !== "object") {
|
||||
if (
|
||||
appointment &&
|
||||
typeof appointment === "object" &&
|
||||
(appointment as Record<string, unknown>).isPrimary !== true
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const value = appointment as Record<string, unknown>;
|
||||
addLabel(
|
||||
value.tenantId,
|
||||
value.tenantSlug ?? value.slug,
|
||||
value.tenantName ?? value.name,
|
||||
);
|
||||
const candidate = appointmentTenantCandidate(appointment);
|
||||
if (candidate) candidates.push(candidate);
|
||||
}
|
||||
}
|
||||
if (user.tenantSlug) candidates.push({ slug: user.tenantSlug });
|
||||
|
||||
return labels;
|
||||
const representative = candidates.find(
|
||||
(candidate) =>
|
||||
candidateLabel(candidate) &&
|
||||
!isPrivateTenantCandidate(candidate, knownTenants),
|
||||
);
|
||||
|
||||
return candidateLabel(representative ?? {});
|
||||
}
|
||||
|
||||
function normalizeUserTableRect(rect: Rect, fallbackWidth: number): Rect {
|
||||
@@ -467,10 +528,10 @@ function UserListPage() {
|
||||
name_email: (user) =>
|
||||
`${user.name ?? ""} ${user.email ?? ""} ${user.phone ?? ""}`,
|
||||
tenant_dept: (user) =>
|
||||
`${user.tenant?.name ?? user.tenantSlug ?? ""} ${collectAdditionalTenantLabels(user).join(" ")} ${user.department ?? ""}`,
|
||||
`${resolveRepresentativeTenantLabel(user, tenants)} ${user.department ?? ""}`,
|
||||
},
|
||||
),
|
||||
[userSchema],
|
||||
[tenants, userSchema],
|
||||
);
|
||||
const items = React.useMemo(() => {
|
||||
if (!sortConfig) {
|
||||
@@ -1019,8 +1080,9 @@ function UserListPage() {
|
||||
virtualRows.map((virtualRow) => {
|
||||
const user = items[virtualRow.index];
|
||||
if (!user) return null;
|
||||
const additionalTenantLabels =
|
||||
collectAdditionalTenantLabels(user);
|
||||
const representativeTenantLabel =
|
||||
resolveRepresentativeTenantLabel(user, tenants) ||
|
||||
t("ui.common.unassigned", "미배정");
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
@@ -1151,27 +1213,13 @@ function UserListPage() {
|
||||
<TableCell>
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-sm font-medium">
|
||||
{user.tenant?.name ||
|
||||
user.tenantSlug ||
|
||||
t("ui.common.unassigned", "미배정")}
|
||||
{representativeTenantLabel}
|
||||
</span>
|
||||
{user.department && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{user.department}
|
||||
</span>
|
||||
)}
|
||||
{additionalTenantLabels.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{additionalTenantLabels.map((label) => (
|
||||
<span
|
||||
key={label}
|
||||
className="max-w-40 truncate rounded border bg-muted/40 px-1.5 py-0.5 text-xs text-muted-foreground"
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
{/* Dynamic Metadata Cells */}
|
||||
|
||||
Reference in New Issue
Block a user