1
0
forked from baron/baron-sso

refactor(adminfront): rename legacy companyCode to tenantSlug across frontend (#426)

This commit is contained in:
2026-03-23 16:59:05 +09:00
parent b2f96b216d
commit b0e18cc724
12 changed files with 48 additions and 48 deletions

View File

@@ -23,20 +23,20 @@ function TenantUsersPage() {
const params = useParams<{ tenantId: string }>(); const params = useParams<{ tenantId: string }>();
const tenantId = params.tenantId ?? ""; const tenantId = params.tenantId ?? "";
// 테넌트의 슬러그(companyCode)를 먼저 가져옴 // 테넌트의 슬러그(tenantSlug)를 먼저 가져옴
const tenantQuery = useQuery({ const tenantQuery = useQuery({
queryKey: ["tenant", tenantId], queryKey: ["tenant", tenantId],
queryFn: () => fetchTenant(tenantId), queryFn: () => fetchTenant(tenantId),
enabled: tenantId.length > 0, enabled: tenantId.length > 0,
}); });
const companyCode = tenantQuery.data?.slug; const tenantSlug = tenantQuery.data?.slug;
// 해당 슬러그로 사용자 검색 // 해당 슬러그로 사용자 검색
const usersQuery = useQuery({ const usersQuery = useQuery({
queryKey: ["users", { companyCode }], queryKey: ["users", { tenantSlug }],
queryFn: () => fetchUsers(100, 0, companyCode), queryFn: () => fetchUsers(100, 0, tenantSlug),
enabled: !!companyCode, enabled: !!tenantSlug,
}); });
const users = usersQuery.data?.items ?? []; const users = usersQuery.data?.items ?? [];

View File

@@ -290,7 +290,7 @@ const MemberTable: React.FC<{
{showTenant && ( {showTenant && (
<TableCell className="py-2"> <TableCell className="py-2">
<Badge variant="outline" className="text-[10px] h-5"> <Badge variant="outline" className="text-[10px] h-5">
{user.companyCode} {user.tenantSlug}
</Badge> </Badge>
</TableCell> </TableCell>
)} )}
@@ -360,7 +360,7 @@ const UserAddDialog: React.FC<{
const res = await createUser({ const res = await createUser({
email, email,
name, name,
companyCode: tenantSlug, tenantSlug: tenantSlug,
role: "user", role: "user",
}); });
toast.success( toast.success(
@@ -394,7 +394,7 @@ const UserAddDialog: React.FC<{
if (!selectedUserId) return; if (!selectedUserId) return;
setIsSubmitting(true); setIsSubmitting(true);
try { try {
await updateUser(selectedUserId, { companyCode: tenantSlug }); await updateUser(selectedUserId, { tenantSlug: tenantSlug });
toast.success( toast.success(
t("msg.info.saved_success", "사용자가 테넌트에 배정되었습니다."), t("msg.info.saved_success", "사용자가 테넌트에 배정되었습니다."),
); );
@@ -504,12 +504,12 @@ const UserAddDialog: React.FC<{
<div className="text-xs text-muted-foreground"> <div className="text-xs text-muted-foreground">
{user.email} {user.email}
</div> </div>
{user.companyCode && ( {user.tenantSlug && (
<Badge <Badge
variant="outline" variant="outline"
className="text-[10px] mt-1" className="text-[10px] mt-1"
> >
{user.companyCode} {user.tenantSlug}
</Badge> </Badge>
)} )}
</div> </div>

View File

@@ -69,7 +69,7 @@ function UserCreatePage() {
name: "", name: "",
phone: "", phone: "",
role: "user", role: "user",
companyCode: "", tenantSlug: "",
department: "", department: "",
position: "", position: "",
jobTitle: "", jobTitle: "",
@@ -80,13 +80,13 @@ function UserCreatePage() {
// Lock company for tenant_admin // Lock company for tenant_admin
React.useEffect(() => { React.useEffect(() => {
const p = profile as any; const p = profile as any;
if (p?.role === "tenant_admin" && p.companyCode) { if (p?.role === "tenant_admin" && p.tenantSlug) {
setValue("companyCode", p.companyCode); setValue("tenantSlug", p.tenantSlug);
} }
}, [profile, setValue]); }, [profile, setValue]);
const selectedCompanyCode = watch("companyCode"); const selectedTenantSlug = watch("tenantSlug");
const selectedTenant = tenants.find((t) => t.slug === selectedCompanyCode); const selectedTenant = tenants.find((t) => t.slug === selectedTenantSlug);
const selectedTenantId = selectedTenant?.id ?? ""; const selectedTenantId = selectedTenant?.id ?? "";
@@ -353,15 +353,15 @@ function UserCreatePage() {
<div className="grid gap-4 md:grid-cols-2"> <div className="grid gap-4 md:grid-cols-2">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="companyCode"> <Label htmlFor="tenantSlug">
{t("ui.admin.users.create.form.tenant", "테넌트 (Tenant)")} {t("ui.admin.users.create.form.tenant", "테넌트 (Tenant)")}
</Label> </Label>
<div className="relative"> <div className="relative">
<select <select
id="companyCode" id="tenantSlug"
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50" className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
{...register("companyCode")} {...register("tenantSlug")}
disabled={(profile as any)?.role === "tenant_admin"} disabled={(profile as any)?.role === "tenant_admin"}
> >
<option value=""> <option value="">

View File

@@ -202,7 +202,7 @@ function UserDetailPage() {
phone: "", phone: "",
role: "user", role: "user",
status: "active", status: "active",
companyCode: "", tenantSlug: "",
department: "", department: "",
position: "", position: "",
jobTitle: "", jobTitle: "",
@@ -243,7 +243,7 @@ function UserDetailPage() {
phone: user.phone || "", phone: user.phone || "",
role: user.role, role: user.role,
status: user.status, status: user.status,
companyCode: user.companyCode || "", tenantSlug: user.tenantSlug || "",
department: user.department || "", department: user.department || "",
position: user.position || "", position: user.position || "",
jobTitle: user.jobTitle || "", jobTitle: user.jobTitle || "",
@@ -377,7 +377,7 @@ function UserDetailPage() {
<div className="relative"> <div className="relative">
<select <select
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:opacity-50" className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:opacity-50"
{...register("companyCode")} {...register("tenantSlug")}
disabled={ disabled={
profile?.role === "tenant_admin" && profile?.role === "tenant_admin" &&
userAffiliatedTenants.length <= 1 userAffiliatedTenants.length <= 1
@@ -428,7 +428,7 @@ function UserDetailPage() {
to={`/tenants/${jt.id}`} to={`/tenants/${jt.id}`}
className={`inline-flex items-center gap-1 px-2 py-0.5 rounded border text-[11px] transition-colors ${ className={`inline-flex items-center gap-1 px-2 py-0.5 rounded border text-[11px] transition-colors ${
jt.id === jt.id ===
tenants.find((t) => t.slug === watch("companyCode")) tenants.find((t) => t.slug === watch("tenantSlug"))
?.id ?.id
? "bg-primary/10 border-primary/30 text-primary font-bold" ? "bg-primary/10 border-primary/30 text-primary font-bold"
: "bg-background border-border text-muted-foreground hover:border-primary/50" : "bg-background border-border text-muted-foreground hover:border-primary/50"

View File

@@ -88,8 +88,8 @@ function UserListPage() {
// Lock company for tenant_admin // Lock company for tenant_admin
React.useEffect(() => { React.useEffect(() => {
if (profile?.role === "tenant_admin" && profile.companyCode) { if (profile?.role === "tenant_admin" && profile.tenantSlug) {
setSelectedCompany(profile.companyCode); setSelectedCompany(profile.tenantSlug);
} }
}, [profile]); }, [profile]);
@@ -134,7 +134,7 @@ function UserListPage() {
const query = useQuery({ const query = useQuery({
queryKey: [ queryKey: [
"users", "users",
{ limit, offset, search, companyCode: selectedCompany }, { limit, offset, search, tenantSlug: selectedCompany },
], ],
queryFn: () => fetchUsers(limit, offset, search, selectedCompany), queryFn: () => fetchUsers(limit, offset, search, selectedCompany),
placeholderData: (previousData) => previousData, placeholderData: (previousData) => previousData,
@@ -531,7 +531,7 @@ function UserListPage() {
<TableCell> <TableCell>
<div className="flex flex-col text-sm"> <div className="flex flex-col text-sm">
<span className="font-medium text-blue-600"> <span className="font-medium text-blue-600">
{user.tenant?.name || user.companyCode || "-"} {user.tenant?.name || user.tenantSlug || "-"}
</span> </span>
<span className="text-xs text-muted-foreground"> <span className="text-xs text-muted-foreground">
{user.department || "-"} {user.department || "-"}

View File

@@ -138,7 +138,7 @@ export function UserBulkMoveGroupModal({
if (!selectedTenantSlug) return; if (!selectedTenantSlug) return;
mutation.mutate({ mutation.mutate({
userIds, userIds,
companyCode: selectedTenantSlug, tenantSlug: selectedTenantSlug,
department: selectedGroupName, // can be empty for "No Department" department: selectedGroupName, // can be empty for "No Department"
} as any); } as any);
}; };

View File

@@ -74,7 +74,7 @@ export function UserBulkUploadModal({ onSuccess }: UserBulkUploadModalProps) {
}; };
const downloadTemplate = () => { const downloadTemplate = () => {
const headers = "email,name,phone,role,companyCode,department,employee_id"; const headers = "email,name,phone,role,tenant,department,employee_id";
const example = const example =
"user1@example.com,홍길동,010-1234-5678,user,tenant-slug,개발팀,EMP001"; "user1@example.com,홍길동,010-1234-5678,user,tenant-slug,개발팀,EMP001";
const blob = new Blob( const blob = new Blob(
@@ -203,7 +203,7 @@ ${example}`,
<tr key={u.email} className="border-t"> <tr key={u.email} className="border-t">
<td className="p-2">{u.email}</td> <td className="p-2">{u.email}</td>
<td className="p-2">{u.name}</td> <td className="p-2">{u.name}</td>
<td className="p-2">{u.companyCode || "-"}</td> <td className="p-2">{u.tenantSlug || "-"}</td>
</tr> </tr>
))} ))}
{previewData.length > 10 && ( {previewData.length > 10 && (

View File

@@ -3,7 +3,7 @@ import { parseUserCSV } from "./csvParser";
describe("parseUserCSV", () => { describe("parseUserCSV", () => {
it("should parse valid CSV correctly", () => { it("should parse valid CSV correctly", () => {
const csv = `email,name,phone,role,companyCode,department,emp_id const csv = `email,name,phone,role,tenant,department,emp_id
user1@test.com,Hong Gil Dong,010-1111-2222,user,baron,HR,E001 user1@test.com,Hong Gil Dong,010-1111-2222,user,baron,HR,E001
user2@test.com,Kim Cheol Su,,admin,baron,IT,E002`; user2@test.com,Kim Cheol Su,,admin,baron,IT,E002`;
@@ -15,7 +15,7 @@ user2@test.com,Kim Cheol Su,,admin,baron,IT,E002`;
name: "Hong Gil Dong", name: "Hong Gil Dong",
phone: "010-1111-2222", phone: "010-1111-2222",
role: "user", role: "user",
companyCode: "baron", tenantSlug: "baron",
department: "HR", department: "HR",
metadata: { metadata: {
emp_id: "E001", emp_id: "E001",
@@ -37,10 +37,10 @@ no-name@test.com,`;
}); });
it("should handle mixed case headers", () => { it("should handle mixed case headers", () => {
const csv = `EMAIL,Name,CompanyCode const csv = `EMAIL,Name,Tenant
test@test.com,Test,baron`; test@test.com,Test,baron`;
const result = parseUserCSV(csv); const result = parseUserCSV(csv);
expect(result[0].email).toBe("test@test.com"); expect(result[0].email).toBe("test@test.com");
expect(result[0].companyCode).toBe("baron"); expect(result[0].tenantSlug).toBe("baron");
}); });
}); });

View File

@@ -30,8 +30,8 @@ export function parseUserCSV(text: string): BulkUserItem[] {
item.phone = value; item.phone = value;
} else if (header === "role") { } else if (header === "role") {
item.role = value; item.role = value;
} else if (header === "companycode") { } else if (header === "tenant") {
item.companyCode = value; item.tenantSlug = value;
} else if (header === "department") { } else if (header === "department") {
item.department = value; item.department = value;
} else { } else {

View File

@@ -352,7 +352,7 @@ export type UserSummary = {
phone?: string; phone?: string;
role: string; role: string;
status: string; status: string;
companyCode?: string; tenantSlug?: string;
tenant?: TenantSummary; tenant?: TenantSummary;
joinedTenants?: TenantSummary[]; // [New] 다중 소속 테넌트 목록 joinedTenants?: TenantSummary[]; // [New] 다중 소속 테넌트 목록
metadata?: Record<string, unknown>; metadata?: Record<string, unknown>;
@@ -376,7 +376,7 @@ export type UserCreateRequest = {
name: string; name: string;
phone?: string; phone?: string;
role?: string; role?: string;
companyCode?: string; tenantSlug?: string;
department?: string; department?: string;
position?: string; position?: string;
jobTitle?: string; jobTitle?: string;
@@ -392,7 +392,7 @@ export type UserUpdateRequest = {
phone?: string; phone?: string;
role?: string; role?: string;
status?: string; status?: string;
companyCode?: string; tenantSlug?: string;
department?: string; department?: string;
position?: string; position?: string;
jobTitle?: string; jobTitle?: string;
@@ -403,7 +403,7 @@ export type BulkUserItem = {
name: string; name: string;
phone?: string; phone?: string;
role?: string; role?: string;
companyCode?: string; tenantSlug?: string;
department?: string; department?: string;
metadata?: Record<string, unknown>; metadata?: Record<string, unknown>;
}; };
@@ -423,10 +423,10 @@ export async function fetchUsers(
limit = 50, limit = 50,
offset = 0, offset = 0,
search?: string, search?: string,
companyCode?: string, tenantSlug?: string,
) { ) {
const { data } = await apiClient.get<UserListResponse>("/v1/admin/users", { const { data } = await apiClient.get<UserListResponse>("/v1/admin/users", {
params: { limit, offset, search, companyCode }, params: { limit, offset, search, tenantSlug },
}); });
return data; return data;
} }
@@ -446,10 +446,10 @@ export async function createUser(payload: UserCreateRequest) {
return data; return data;
} }
export function exportUsersCSVUrl(search?: string, companyCode?: string) { export function exportUsersCSVUrl(search?: string, tenantSlug?: string) {
const params = new URLSearchParams(); const params = new URLSearchParams();
if (search) params.append("search", search); if (search) params.append("search", search);
if (companyCode) params.append("companyCode", companyCode); if (tenantSlug) params.append("tenantSlug", tenantSlug);
// Get mock role from storage if exists for dev environment // Get mock role from storage if exists for dev environment
const mockRole = window.localStorage.getItem("X-Mock-Role"); const mockRole = window.localStorage.getItem("X-Mock-Role");
@@ -503,7 +503,7 @@ export type UserProfileResponse = {
role: string; role: string;
department: string; department: string;
affiliationType: string; affiliationType: string;
companyCode?: string; tenantSlug?: string;
tenantId?: string; tenantId?: string;
metadata?: Record<string, unknown>; metadata?: Record<string, unknown>;
tenant?: TenantSummary; tenant?: TenantSummary;

View File

@@ -8,15 +8,15 @@ describe("i18n utility", () => {
}); });
it("returns fallback if key not found", () => { it("returns fallback if key not found", () => {
expect(t("non.existent.key", "Fallback")).toBe("Fallback"); expect(t("this.key.truly.does.not.exist", "Fallback")).toBe("Fallback");
}); });
it("returns key if fallback not provided and key not found", () => { it("returns key if fallback not provided and key not found", () => {
expect(t("non.existent.key")).toBe("non.existent.key"); expect(t("this.key.truly.does.not.exist")).toBe("this.key.truly.does.not.exist");
}); });
it("replaces variables in template", () => { it("replaces variables in template", () => {
expect(t("test.key", "Hello {{ name }}", { name: "World" })).toBe( expect(t("this.test.key", "Hello {{ name }}", { name: "World" })).toBe(
"Hello World", "Hello World",
); );
}); });

View File

@@ -70,7 +70,7 @@ test.describe("User Schema Dynamic Form", () => {
id: "u-1", id: "u-1",
name: "John Doe", name: "John Doe",
email: "john@test.com", email: "john@test.com",
companyCode: "test-tenant", tenantSlug: "test-tenant",
tenant: { id: "t-1", name: "Test Tenant", slug: "test-tenant" }, tenant: { id: "t-1", name: "Test Tenant", slug: "test-tenant" },
joinedTenants: [ joinedTenants: [
{ id: "t-1", name: "Test Tenant", slug: "test-tenant" }, { id: "t-1", name: "Test Tenant", slug: "test-tenant" },