1
0
forked from baron/baron-sso

Merge branch 'dev' into feature/tenant-user-list-ui-improvement

This commit is contained in:
2026-05-14 11:23:57 +09:00
37 changed files with 1080 additions and 765 deletions

View File

@@ -1,16 +1,23 @@
import * as React from "react";
import {
commonTableBodyClass,
commonTableCaptionClass,
commonTableCellClass,
commonTableClass,
commonTableFooterClass,
commonTableHeadClass,
commonTableHeaderClass,
commonTableRowClass,
commonTableWrapperClass,
} from "../../../../common/ui/table";
import { cn } from "../../lib/utils";
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
<div className={commonTableWrapperClass}>
<table ref={ref} className={cn(commonTableClass, className)} {...props} />
</div>
));
Table.displayName = "Table";
@@ -19,7 +26,11 @@ const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
<thead
ref={ref}
className={cn(commonTableHeaderClass, className)}
{...props}
/>
));
TableHeader.displayName = "TableHeader";
@@ -27,11 +38,7 @@ const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
<tbody ref={ref} className={cn(commonTableBodyClass, className)} {...props} />
));
TableBody.displayName = "TableBody";
@@ -41,7 +48,7 @@ const TableFooter = React.forwardRef<
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn("bg-muted/50 font-medium text-foreground", className)}
className={cn(commonTableFooterClass, className)}
{...props}
/>
));
@@ -51,14 +58,7 @@ const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-muted/30 data-[state=selected]:bg-muted",
className,
)}
{...props}
/>
<tr ref={ref} className={cn(commonTableRowClass, className)} {...props} />
));
TableRow.displayName = "TableRow";
@@ -66,14 +66,7 @@ const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-12 px-6 text-left text-xs font-bold uppercase tracking-[0.08em] text-foreground align-middle sticky top-0 bg-inherit",
className,
)}
{...props}
/>
<th ref={ref} className={cn(commonTableHeadClass, className)} {...props} />
));
TableHead.displayName = "TableHead";
@@ -81,11 +74,7 @@ const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn("p-6 align-middle text-sm", className)}
{...props}
/>
<td ref={ref} className={cn(commonTableCellClass, className)} {...props} />
));
TableCell.displayName = "TableCell";
@@ -95,7 +84,7 @@ const TableCaption = React.forwardRef<
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
className={cn(commonTableCaptionClass, className)}
{...props}
/>
));

View File

@@ -27,6 +27,10 @@ import {
TableHeader,
TableRow,
} from "../../components/ui/table";
import {
commonTableShellClass,
commonTableViewportClass,
} from "../../../../common/ui/table";
import type { AuditLog } from "../../lib/adminApi";
import { fetchAuditLogs } from "../../lib/adminApi";
import { t } from "../../lib/i18n";
@@ -254,8 +258,8 @@ function AuditLogsPage() {
))
)}
</div>
<div className="flex-1 rounded-md border overflow-hidden flex flex-col">
<div className="flex-1 overflow-auto relative custom-scrollbar">
<div className={commonTableShellClass}>
<div className={commonTableViewportClass}>
<Table className="table-fixed">
<TableHeader className="sticky top-0 z-10 bg-secondary shadow-sm">
<TableRow>

View File

@@ -29,10 +29,19 @@ import {
import * as React from "react";
import { Link, useNavigate } from "react-router-dom";
import {
type SortConfig,
type SortResolverMap,
SortableTableHead,
sortableTableHeadBaseClassName,
sortableTableHeaderClassName,
} from "../../../../../common/core/components/sort";
import {
commonTableShellClass,
commonTableViewportClass,
} from "../../../../../common/ui/table";
import {
sortItems,
toggleSort,
type SortConfig,
type SortResolverMap,
} from "../../../../../common/core/utils";
import { RoleGuard } from "../../../components/auth/RoleGuard";
import { Badge } from "../../../components/ui/badge";
@@ -257,7 +266,10 @@ function TenantListPage() {
const [selectedIds, setSelectedIds] = React.useState<string[]>([]);
const [search, setSearch] = React.useState("");
const [sortConfig, setSortConfig] =
React.useState<SortConfig<TenantSortKey> | null>(null);
React.useState<SortConfig<TenantSortKey> | null>({
key: "createdAt",
direction: "desc",
});
const fileInputRef = React.useRef<HTMLInputElement | null>(null);
const [importMessage, setImportMessage] = React.useState("");
const [previewRows, setPreviewRows] = React.useState<
@@ -444,9 +456,8 @@ function TenantListPage() {
}
if (
profile &&
profileRole === "tenant_admin" &&
(profile.manageableTenants?.length ?? 0) <= 1
(profile?.manageableTenants?.length ?? 0) <= 1
) {
return null;
}
@@ -487,17 +498,6 @@ function TenantListPage() {
setSortConfig((current) => toggleSort(current, key));
};
const getSortIcon = (key: TenantSortKey) => {
if (!sortConfig || sortConfig.key !== key) {
return <ArrowUpDown size={14} className="ml-1 opacity-50" />;
}
return sortConfig.direction === "asc" ? (
<ArrowUp size={14} className="ml-1" />
) : (
<ArrowDown size={14} className="ml-1" />
);
};
const deletableTenants = React.useMemo(
() => allTenants.filter((tenant) => !isSeedTenant(tenant)),
[allTenants],

View File

@@ -24,6 +24,15 @@ import {
sortItems,
toggleSort,
} from "../../../../common/core/utils";
import {
SortableTableHead,
sortableTableHeadBaseClassName,
sortableTableHeaderClassName,
} from "../../../../common/core/components/sort";
import {
commonTableShellClass,
commonTableViewportClass,
} from "../../../../common/ui/table";
import { Button } from "../../components/ui/button";
import {
Card,
@@ -294,17 +303,6 @@ function UserListPage() {
setSortConfig((current) => toggleSort(current, key));
};
const getSortIcon = (key: UserSortKey) => {
if (!sortConfig || sortConfig.key !== key) {
return <ArrowUpDown size={14} className="ml-1 opacity-50" />;
}
return sortConfig.direction === "asc" ? (
<ArrowUp size={14} className="ml-1" />
) : (
<ArrowDown size={14} className="ml-1" />
);
};
const total = query.data?.total ?? 0;
const totalPages = Math.ceil(total / limit);
const canPromoteSuperAdmin = isSuperAdminRole(profile?.role);
@@ -603,12 +601,14 @@ function UserListPage() {
</div>
)}
<div className="flex-1 rounded-md border overflow-hidden flex flex-col">
<div className="flex-1 overflow-auto relative custom-scrollbar">
<div className={commonTableShellClass}>
<div className={commonTableViewportClass}>
<Table>
<TableHeader className="sticky top-0 z-10 bg-secondary shadow-sm">
<TableHeader className={sortableTableHeaderClassName}>
<TableRow>
<TableHead className="w-12">
<TableHead
className={`${sortableTableHeadBaseClassName} w-12`}
>
<input
type="checkbox"
className="w-4 h-4 rounded border-gray-300 text-primary focus:ring-primary cursor-pointer"
@@ -689,34 +689,30 @@ function UserListPage() {
{userSchema.map(
(field) =>
visibleColumns[field.key] !== false && (
<TableHead
<SortableTableHead
key={field.key}
className="whitespace-nowrap uppercase cursor-pointer hover:bg-muted/50 transition-colors"
onClick={() => requestSort(field.key)}
>
<div className="flex items-center">
{field.label}
{getSortIcon(field.key)}
</div>
</TableHead>
className="whitespace-nowrap"
label={field.label}
onSort={requestSort}
sortConfig={sortConfig}
sortKey={field.key}
/>
),
)}
<TableHead
className="whitespace-nowrap cursor-pointer hover:bg-muted/50 transition-colors"
onClick={() => requestSort("createdAt")}
>
<div className="flex items-center">
{t("ui.admin.users.list.table.created", "CREATED")}
{getSortIcon("createdAt")}
</div>
</TableHead>
<SortableTableHead
className="whitespace-nowrap"
label={t("ui.admin.users.list.table.created", "CREATED")}
onSort={requestSort}
sortConfig={sortConfig}
sortKey="createdAt"
/>
</TableRow>
</TableHeader>
<TableBody>
{query.isLoading && (
<TableRow>
<TableCell
colSpan={6 + userSchema.length}
colSpan={7 + userSchema.length}
className="h-24 text-center"
>
{t("msg.common.loading", "로딩 중...")}
@@ -726,7 +722,7 @@ function UserListPage() {
{!query.isLoading && items.length === 0 && (
<TableRow>
<TableCell
colSpan={6 + userSchema.length}
colSpan={7 + userSchema.length}
className="h-24 text-center"
>
{t(