forked from baron/baron-sso
refactor(adminfront): rename legacy companyCode to tenantSlug across frontend (#426)
This commit is contained in:
@@ -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 ?? [];
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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="">
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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 || "-"}
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 && (
|
||||||
|
|||||||
@@ -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");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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" },
|
||||||
|
|||||||
Reference in New Issue
Block a user