refactor(admin): compact UI styling for data-dense admin interface
- Reduce table cell padding (px-5 py-4 → px-3 py-2) across OrderTableBody, ProductTableBody, StopTableBody, AdminTable - Narrow sidebar (w-60 → w-56) with smaller nav items - Reduce page/card padding in admin-design-system.css - Smaller default buttons (md → sm) in AdminButton - Smaller page headers (text-2xl → text-xl, w-12 → w-10 icon) - Tighter cards and filter tabs Makes admin UI feel like professional B2B tool at 100% zoom
This commit is contained in:
@@ -316,7 +316,7 @@ export default function AdminSidebar({ userRole }: SidebarProps) {
|
|||||||
ref={sidebarRef}
|
ref={sidebarRef}
|
||||||
id="admin-sidebar"
|
id="admin-sidebar"
|
||||||
className={[
|
className={[
|
||||||
"fixed top-0 left-0 h-full w-60 z-50",
|
"fixed top-0 left-0 h-full w-56 z-50",
|
||||||
"border-r flex flex-col",
|
"border-r flex flex-col",
|
||||||
"transition-transform duration-300 ease-out lg:translate-x-0",
|
"transition-transform duration-300 ease-out lg:translate-x-0",
|
||||||
mobileOpen ? "translate-x-0" : "-translate-x-full",
|
mobileOpen ? "translate-x-0" : "-translate-x-full",
|
||||||
@@ -408,7 +408,7 @@ export default function AdminSidebar({ userRole }: SidebarProps) {
|
|||||||
onClick={() => closeMobileMenu()}
|
onClick={() => closeMobileMenu()}
|
||||||
onKeyDown={(e) => handleNavKeyDown(e, item.href!, navIndex)}
|
onKeyDown={(e) => handleNavKeyDown(e, item.href!, navIndex)}
|
||||||
className={[
|
className={[
|
||||||
"group flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm font-medium transition-all duration-200",
|
"group flex items-center gap-2.5 px-3 py-2 rounded-lg text-xs font-medium transition-all duration-200",
|
||||||
"focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--admin-sidebar-bg)]",
|
"focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--admin-sidebar-bg)]",
|
||||||
active
|
active
|
||||||
? "border-l-[3px]"
|
? "border-l-[3px]"
|
||||||
|
|||||||
@@ -47,13 +47,13 @@ export default function OrderTableBody({ orders }: { orders: Order[] }) {
|
|||||||
<tbody className="divide-y divide-slate-200">
|
<tbody className="divide-y divide-slate-200">
|
||||||
{orders.map((order) => (
|
{orders.map((order) => (
|
||||||
<tr key={order.id} className="hover:bg-zinc-800">
|
<tr key={order.id} className="hover:bg-zinc-800">
|
||||||
<td className="px-5 py-4">
|
<td className="px-3 py-2">
|
||||||
<span className="font-mono text-sm text-zinc-500">
|
<span className="font-mono text-sm text-zinc-500">
|
||||||
{order.id.slice(0, 8)}
|
{order.id.slice(0, 8)}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td className="px-5 py-4">
|
<td className="px-3 py-2">
|
||||||
<div className="font-medium text-zinc-100">
|
<div className="font-medium text-zinc-100">
|
||||||
{order.customer_name}
|
{order.customer_name}
|
||||||
</div>
|
</div>
|
||||||
@@ -62,9 +62,9 @@ export default function OrderTableBody({ orders }: { orders: Order[] }) {
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td className="px-5 py-4">
|
<td className="px-3 py-2">
|
||||||
<span
|
<span
|
||||||
className={`rounded-full px-3 py-1 text-xs font-medium ${
|
className={`rounded-full px-2 py-0.5 text-xs font-medium ${
|
||||||
statusColors[order.status] ?? "bg-zinc-950 text-zinc-400"
|
statusColors[order.status] ?? "bg-zinc-950 text-zinc-400"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -72,14 +72,14 @@ export default function OrderTableBody({ orders }: { orders: Order[] }) {
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td className="px-5 py-4 font-semibold text-zinc-100">
|
<td className="px-3 py-2 font-semibold text-zinc-100">
|
||||||
${Number(order.subtotal).toFixed(2)}
|
${Number(order.subtotal).toFixed(2)}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td className="px-5 py-4">
|
<td className="px-3 py-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => togglePickup(order.id, pickupToggles[order.id])}
|
onClick={() => togglePickup(order.id, pickupToggles[order.id])}
|
||||||
className={`rounded-full px-3 py-1 text-xs font-medium ${
|
className={`rounded-full px-2 py-0.5 text-xs font-medium ${
|
||||||
pickupToggles[order.id]
|
pickupToggles[order.id]
|
||||||
? "bg-green-900/40 text-green-400"
|
? "bg-green-900/40 text-green-400"
|
||||||
: "bg-zinc-950 text-zinc-500"
|
: "bg-zinc-950 text-zinc-500"
|
||||||
@@ -89,7 +89,7 @@ export default function OrderTableBody({ orders }: { orders: Order[] }) {
|
|||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td className="px-5 py-4 text-sm text-zinc-500">
|
<td className="px-3 py-2 text-sm text-zinc-500">
|
||||||
{formatDate(new Date(order.created_at))}
|
{formatDate(new Date(order.created_at))}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export default function ProductTableBody({
|
|||||||
key={product.id}
|
key={product.id}
|
||||||
className="hover:bg-zinc-800 transition-colors relative"
|
className="hover:bg-zinc-800 transition-colors relative"
|
||||||
>
|
>
|
||||||
<td className="px-5 py-4">
|
<td className="px-3 py-2">
|
||||||
<Link
|
<Link
|
||||||
href={`/admin/products/${product.id}`}
|
href={`/admin/products/${product.id}`}
|
||||||
className="block font-medium text-zinc-100 hover:text-zinc-400"
|
className="block font-medium text-zinc-100 hover:text-zinc-400"
|
||||||
@@ -134,25 +134,25 @@ export default function ProductTableBody({
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td className="px-5 py-4 text-zinc-300">
|
<td className="px-3 py-2 text-zinc-300">
|
||||||
{Array.isArray(product.brands)
|
{Array.isArray(product.brands)
|
||||||
? product.brands[0]?.name
|
? product.brands[0]?.name
|
||||||
: product.brands?.name}
|
: product.brands?.name}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td className="px-5 py-4 text-zinc-300">{product.type}</td>
|
<td className="px-3 py-2 text-zinc-300">{product.type}</td>
|
||||||
|
|
||||||
<td className="px-5 py-4 font-semibold text-zinc-100">
|
<td className="px-3 py-2 font-semibold text-zinc-100">
|
||||||
${Number(product.price).toFixed(2)}
|
${Number(product.price).toFixed(2)}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td className="px-5 py-4">
|
<td className="px-3 py-2">
|
||||||
<AdminBadge variant={product.active ? "success" : "default"} dot>
|
<AdminBadge variant={product.active ? "success" : "default"} dot>
|
||||||
{product.active ? "Active" : "Inactive"}
|
{product.active ? "Active" : "Inactive"}
|
||||||
</AdminBadge>
|
</AdminBadge>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td className="px-5 py-4">
|
<td className="px-3 py-2">
|
||||||
{product.is_taxable === false ? (
|
{product.is_taxable === false ? (
|
||||||
<AdminBadge variant="warning">Non-taxable</AdminBadge>
|
<AdminBadge variant="warning">Non-taxable</AdminBadge>
|
||||||
) : (
|
) : (
|
||||||
@@ -161,7 +161,7 @@ export default function ProductTableBody({
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<td className="px-5 py-4 text-right">
|
<td className="px-3 py-2 text-right">
|
||||||
<div className="relative inline-flex items-center justify-end gap-2">
|
<div className="relative inline-flex items-center justify-end gap-2">
|
||||||
<Link
|
<Link
|
||||||
href={`/admin/products/${product.id}`}
|
href={`/admin/products/${product.id}`}
|
||||||
|
|||||||
@@ -22,31 +22,31 @@ export default function StopTableBody({ stops }: { stops: Stop[] }) {
|
|||||||
}
|
}
|
||||||
className="cursor-pointer hover:bg-zinc-800"
|
className="cursor-pointer hover:bg-zinc-800"
|
||||||
>
|
>
|
||||||
<td className="px-5 py-4">
|
<td className="px-3 py-2">
|
||||||
<span className="font-medium text-zinc-100">
|
<span className="font-medium text-zinc-100">
|
||||||
{stop.city}, {stop.state}
|
{stop.city}, {stop.state}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td className="px-5 py-4 text-zinc-300">
|
<td className="px-3 py-2 text-zinc-300">
|
||||||
{stop.location}
|
{stop.location}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td className="px-5 py-4 text-zinc-300">
|
<td className="px-3 py-2 text-zinc-300">
|
||||||
{stop.date}
|
{stop.date}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td className="px-5 py-4 text-zinc-300">
|
<td className="px-3 py-2 text-zinc-300">
|
||||||
{stop.time}
|
{stop.time}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td className="px-5 py-4 text-zinc-300">
|
<td className="px-3 py-2 text-zinc-300">
|
||||||
{Array.isArray(stop.brands)
|
{Array.isArray(stop.brands)
|
||||||
? stop.brands[0]?.name
|
? stop.brands[0]?.name
|
||||||
: stop.brands?.name}
|
: stop.brands?.name}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td className="px-5 py-4">
|
<td className="px-3 py-2">
|
||||||
<span className="rounded-full bg-zinc-950 px-3 py-1 text-xs font-medium text-zinc-300">
|
<span className="rounded-full bg-zinc-950 px-3 py-1 text-xs font-medium text-zinc-300">
|
||||||
{stop.active ? "Active" : "Inactive"}
|
{stop.active ? "Active" : "Inactive"}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const variantClasses: Record<ButtonVariant, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const sizeClasses: Record<ButtonSize, string> = {
|
const sizeClasses: Record<ButtonSize, string> = {
|
||||||
sm: "px-3 py-1.5 text-xs rounded-lg gap-1.5",
|
sm: "px-2.5 py-1 text-xs rounded-lg gap-1",
|
||||||
md: "px-4 py-2 text-sm rounded-xl gap-2",
|
md: "px-4 py-2 text-sm rounded-xl gap-2",
|
||||||
lg: "px-5 py-3 text-base rounded-xl gap-2",
|
lg: "px-5 py-3 text-base rounded-xl gap-2",
|
||||||
};
|
};
|
||||||
@@ -46,7 +46,7 @@ const sizeClasses: Record<ButtonSize, string> = {
|
|||||||
export default function AdminButton({
|
export default function AdminButton({
|
||||||
children,
|
children,
|
||||||
variant = "primary",
|
variant = "primary",
|
||||||
size = "md",
|
size = "sm",
|
||||||
icon,
|
icon,
|
||||||
iconPosition = "left",
|
iconPosition = "left",
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ type AdminCardProps = {
|
|||||||
export default function AdminCard({ children, className = "", noPadding = false, style }: AdminCardProps) {
|
export default function AdminCard({ children, className = "", noPadding = false, style }: AdminCardProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`rounded-2xl border bg-white shadow-[var(--admin-shadow-sm)] transition-all duration-200 hover:shadow-[var(--admin-shadow-md)] ${noPadding ? "" : "p-5"} ${className}`}
|
className={`rounded-xl border bg-white shadow-[var(--admin-shadow-sm)] transition-all duration-200 hover:shadow-[var(--admin-shadow-md)] ${noPadding ? "" : "p-4"} ${className}`}
|
||||||
style={{
|
style={{
|
||||||
borderColor: 'var(--admin-border)',
|
borderColor: 'var(--admin-border)',
|
||||||
...style
|
...style
|
||||||
|
|||||||
@@ -31,11 +31,11 @@ type AdminFilterTabsProps = {
|
|||||||
const sizeClasses = {
|
const sizeClasses = {
|
||||||
sm: {
|
sm: {
|
||||||
container: "p-0.5 gap-0.5",
|
container: "p-0.5 gap-0.5",
|
||||||
tab: "px-2.5 sm:px-3 py-2 sm:py-1.5 text-xs sm:text-[10px]",
|
tab: "px-2 py-1.5 text-[10px]",
|
||||||
},
|
},
|
||||||
md: {
|
md: {
|
||||||
container: "p-1 gap-0.5",
|
container: "p-0.5 gap-0.5",
|
||||||
tab: "px-3 sm:px-4 py-2.5 sm:py-1.5 text-sm sm:text-xs",
|
tab: "px-2.5 py-1.5 text-xs",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export default function AdminTable<T extends Record<string, unknown>>({
|
|||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-[var(--admin-border)] bg-[var(--admin-bg-subtle)]">
|
<tr className="border-b border-[var(--admin-border)] bg-[var(--admin-bg-subtle)]">
|
||||||
{columns.map((col) => (
|
{columns.map((col) => (
|
||||||
<th key={col.key} className={`px-3 sm:px-5 py-3 text-xs sm:text-[10px] font-bold uppercase tracking-widest text-[var(--admin-text-muted)] whitespace-nowrap ${col.className ?? ""}`}>
|
<th key={col.key} className={`px-2 sm:px-3 py-2 text-xs sm:text-[10px] font-bold uppercase tracking-widest text-[var(--admin-text-muted)] whitespace-nowrap ${col.className ?? ""}`}>
|
||||||
{col.header}
|
{col.header}
|
||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
@@ -59,7 +59,7 @@ export default function AdminTable<T extends Record<string, unknown>>({
|
|||||||
style={{ animationDelay: `${index * 30}ms` }}
|
style={{ animationDelay: `${index * 30}ms` }}
|
||||||
>
|
>
|
||||||
{columns.map((col) => (
|
{columns.map((col) => (
|
||||||
<td key={col.key} className={`px-5 py-3.5 ${col.className ?? ""}`}>
|
<td key={col.key} className={`px-3 py-2 ${col.className ?? ""}`}>
|
||||||
{col.render ? col.render(item) : String(item[col.key] ?? "")}
|
{col.render ? col.render(item) : String(item[col.key] ?? "")}
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -56,18 +56,18 @@ export default function PageHeader({
|
|||||||
{/* Title Row with Icon and Actions */}
|
{/* Title Row with Icon and Actions */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
{/* Left: Icon + Title + Subtitle */}
|
{/* Left: Icon + Title + Subtitle */}
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-3">
|
||||||
{icon && (
|
{icon && (
|
||||||
<div className="flex items-center justify-center w-12 h-12 rounded-2xl bg-[var(--admin-accent-light)] text-[var(--admin-accent)] shadow-[var(--admin-shadow-sm)] transition-transform duration-200 hover:scale-105">
|
<div className="flex items-center justify-center w-10 h-10 rounded-xl bg-[var(--admin-accent-light)] text-[var(--admin-accent)] shadow-[var(--admin-shadow-sm)] transition-transform duration-200 hover:scale-105">
|
||||||
{icon}
|
{icon}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold text-[var(--admin-text-primary)] tracking-tight">
|
<h1 className="text-xl font-bold text-[var(--admin-text-primary)] tracking-tight">
|
||||||
{title}
|
{title}
|
||||||
</h1>
|
</h1>
|
||||||
{subtitle && (
|
{subtitle && (
|
||||||
<p className="mt-1 text-sm text-[var(--admin-text-muted)]">
|
<p className="mt-0.5 text-xs text-[var(--admin-text-muted)]">
|
||||||
{subtitle}
|
{subtitle}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -41,9 +41,9 @@
|
|||||||
--admin-sidebar-active: #5c584f; /* between */
|
--admin-sidebar-active: #5c584f; /* between */
|
||||||
|
|
||||||
/* Spacing */
|
/* Spacing */
|
||||||
--admin-page-padding: 1.5rem; /* px-6 */
|
--admin-page-padding: 1rem; /* px-4 - reduced for density */
|
||||||
--admin-section-gap: 1rem; /* gap-4 */
|
--admin-section-gap: 0.75rem; /* gap-3 - reduced */
|
||||||
--admin-card-padding: 1.25rem; /* p-5 */
|
--admin-card-padding: 1rem; /* p-4 - reduced */
|
||||||
|
|
||||||
/* Border Radius */
|
/* Border Radius */
|
||||||
--admin-radius-sm: 0.5rem; /* rounded-lg */
|
--admin-radius-sm: 0.5rem; /* rounded-lg */
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
.admin-table {
|
.admin-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-size: 0.875rem; /* text-sm */
|
font-size: 0.8125rem; /* 13px - reduced for density */
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-table thead {
|
.admin-table thead {
|
||||||
@@ -111,8 +111,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.admin-table th {
|
.admin-table th {
|
||||||
padding: 0.875rem 1.25rem; /* py-3.5 px-5 */
|
padding: 0.75rem 1rem; /* py-3 px-4 - reduced for density */
|
||||||
font-size: 0.625rem; /* text-[10px] */
|
font-size: 0.5625rem; /* text-9px - reduced */
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.1em;
|
letter-spacing: 0.1em;
|
||||||
@@ -137,7 +137,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.admin-table td {
|
.admin-table td {
|
||||||
padding: 0.875rem 1.25rem; /* py-3.5 px-5 */
|
padding: 0.625rem 1rem; /* py-2.5 px-4 - reduced for density */
|
||||||
|
font-size: 0.8125rem; /* 13px - reduced */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* === Admin Stats Bar === */
|
/* === Admin Stats Bar === */
|
||||||
@@ -184,10 +185,10 @@
|
|||||||
.admin-btn {
|
.admin-btn {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.375rem;
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.375rem 0.75rem; /* reduced padding */
|
||||||
border-radius: var(--admin-radius-sm);
|
border-radius: var(--admin-radius-sm);
|
||||||
font-size: 0.75rem;
|
font-size: 0.6875rem; /* reduced from 0.75rem */
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
transition: all 150ms;
|
transition: all 150ms;
|
||||||
}
|
}
|
||||||
@@ -216,11 +217,11 @@
|
|||||||
.admin-input {
|
.admin-input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 12rem;
|
min-width: 12rem;
|
||||||
padding: 0.625rem 0.75rem;
|
padding: 0.5rem 0.625rem; /* reduced padding */
|
||||||
background-color: var(--admin-card-bg);
|
background-color: var(--admin-card-bg);
|
||||||
border: 1px solid var(--admin-border);
|
border: 1px solid var(--admin-border);
|
||||||
border-radius: var(--admin-radius-sm);
|
border-radius: var(--admin-radius-sm);
|
||||||
font-size: 0.875rem;
|
font-size: 0.8125rem; /* 13px - reduced */
|
||||||
color: var(--admin-text-primary);
|
color: var(--admin-text-primary);
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: border-color 150ms;
|
transition: border-color 150ms;
|
||||||
@@ -245,9 +246,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.admin-filter-tab {
|
.admin-filter-tab {
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.375rem 0.625rem; /* reduced padding */
|
||||||
border-radius: var(--admin-radius-sm);
|
border-radius: var(--admin-radius-sm);
|
||||||
font-size: 0.75rem;
|
font-size: 0.6875rem; /* reduced from 0.75rem */
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--admin-text-secondary);
|
color: var(--admin-text-secondary);
|
||||||
transition: all 150ms;
|
transition: all 150ms;
|
||||||
|
|||||||
Reference in New Issue
Block a user