feat: dashboard overhaul, chargeability reports, dispo import enhancements, UI polish
Dashboard: expanded chargeability widget, resource/project table widgets with sorting and filters, stat cards with formatMoney integration. Chargeability: new report client with filtering, chargeability-bookings use case, updated dashboard overview logic. Dispo import: TBD project handling, parse-dispo-matrix improvements, stage-dispo-projects resource value scores, new tests. Estimates: CommercialTermsEditor component, commercial-terms engine module, expanded estimate schemas and types. UI: AppShell navigation updates, timeline filter/toolbar enhancements, role management improvements, signin page redesign, Tailwind/globals polish, SystemSettings SMTP section, anonymization support. Tests: new router tests (anonymization, chargeability, effort-rule, entitlement, estimate, experience-multiplier, notification, resource, staffing, vacation). Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { signOut } from "next-auth/react";
|
||||
import Link from "next/link";
|
||||
import type { Route } from "next";
|
||||
@@ -11,38 +12,83 @@ import { ThemeProvider } from "./ThemeProvider.js";
|
||||
import { NotificationBell } from "../notifications/NotificationBell.js";
|
||||
import { NavProgressBar } from "~/components/ui/NavProgressBar.js";
|
||||
|
||||
function IconFrame({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<span className="flex h-8 w-8 items-center justify-center rounded-xl border border-white/60 bg-white/80 text-slate-600 shadow-sm dark:border-slate-800 dark:bg-slate-900/70 dark:text-slate-300">
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function DashboardIcon() {
|
||||
return <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.8} d="M4 13h6V5H4v8zm10 6h6V5h-6v14zM4 19h6v-2H4v2zm0-4h6v-2H4v2zm10 4h6v-6h-6v6z" /></svg>;
|
||||
}
|
||||
function ResourcesIcon() {
|
||||
return <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.8} d="M16 21v-2a4 4 0 00-4-4H6a4 4 0 00-4 4v2m18 0v-2a4 4 0 00-3-3.87M14 3.13a4 4 0 010 7.75M12 7a4 4 0 11-8 0 4 4 0 018 0z" /></svg>;
|
||||
}
|
||||
function ProjectsIcon() {
|
||||
return <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.8} d="M3 7h18M7 3v4m10-4v4M5 11h14v8H5z" /></svg>;
|
||||
}
|
||||
function EstimatesIcon() {
|
||||
return <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.8} d="M8 7h8M8 11h4m-4 4h8M5 4h14a2 2 0 012 2v12a2 2 0 01-2 2H5a2 2 0 01-2-2V6a2 2 0 012-2z" /></svg>;
|
||||
}
|
||||
function AllocationsIcon() {
|
||||
return <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.8} d="M8 7V3m8 4V3M4 11h16M5 5h14a1 1 0 011 1v13a1 1 0 01-1 1H5a1 1 0 01-1-1V6a1 1 0 011-1z" /></svg>;
|
||||
}
|
||||
function TimelineIcon() {
|
||||
return <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.8} d="M4 6h16M4 12h10M4 18h7m9-8h-4m4 6h-7" /></svg>;
|
||||
}
|
||||
function StaffingIcon() {
|
||||
return <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.8} d="M12 20l9-5-9-5-9 5 9 5zm0-10l9-5-9-5-9 5 9 5zm0 10v-10" /></svg>;
|
||||
}
|
||||
function VacationIcon() {
|
||||
return <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.8} d="M4 19c3-4 6-6 8-6s5 2 8 6M7 12c.8-2.5 2.5-4 5-4s4.2 1.5 5 4M12 8V4" /></svg>;
|
||||
}
|
||||
function RolesIcon() {
|
||||
return <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.8} d="M7 7h10v10H7zM4 4h4m8 0h4m-4 16h4M4 20h4" /></svg>;
|
||||
}
|
||||
function SkillsIcon() {
|
||||
return <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.8} d="M12 3l2.8 5.7 6.2.9-4.5 4.4 1 6.2L12 17.2 6.5 20.2l1-6.2L3 9.6l6.2-.9L12 3z" /></svg>;
|
||||
}
|
||||
function ChargeabilityIcon() {
|
||||
return <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.8} d="M5 17l4-4 3 3 7-8M19 19H5V5" /></svg>;
|
||||
}
|
||||
function AdminIcon() {
|
||||
return <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.8} d="M12 8a4 4 0 100 8 4 4 0 000-8zm8 4l-2.1.7a7.9 7.9 0 01-.6 1.5l1 2-2.1 2.1-2-1a7.9 7.9 0 01-1.5.6L12 20l-1.7-2.1a7.9 7.9 0 01-1.5-.6l-2 1-2.1-2.1 1-2a7.9 7.9 0 01-.6-1.5L4 12l2.1-1.7a7.9 7.9 0 01.6-1.5l-1-2 2.1-2.1 2 1a7.9 7.9 0 011.5-.6L12 4l1.7 2.1a7.9 7.9 0 011.5.6l2-1 2.1 2.1-1 2a7.9 7.9 0 01.6 1.5L20 12z" /></svg>;
|
||||
}
|
||||
|
||||
const allNavItems = [
|
||||
{ href: "/dashboard", label: "Dashboard", icon: "📊", roles: ["ADMIN", "MANAGER", "CONTROLLER", "USER", "VIEWER"] },
|
||||
{ href: "/resources", label: "Resources", icon: "👥", roles: ["ADMIN", "MANAGER", "CONTROLLER"] },
|
||||
{ href: "/projects", label: "Projects", icon: "📋", roles: ["ADMIN", "MANAGER", "CONTROLLER", "VIEWER"] },
|
||||
{ href: "/estimates", label: "Estimates", icon: "🧮", roles: ["ADMIN", "MANAGER", "CONTROLLER", "USER", "VIEWER"] },
|
||||
{ href: "/allocations", label: "Allocations", icon: "📅", roles: ["ADMIN", "MANAGER", "CONTROLLER"] },
|
||||
{ href: "/timeline", label: "Timeline", icon: "🗓️", roles: ["ADMIN", "MANAGER", "CONTROLLER", "USER", "VIEWER"] },
|
||||
{ href: "/staffing", label: "Staffing", icon: "🎯", roles: ["ADMIN", "MANAGER"] },
|
||||
{ href: "/vacations", label: "Vacations", icon: "🏖️", roles: ["ADMIN", "MANAGER"] },
|
||||
{ href: "/vacations/my", label: "My Vacations", icon: "🌴", roles: ["ADMIN", "MANAGER", "CONTROLLER", "USER", "VIEWER"] },
|
||||
{ href: "/roles", label: "Roles", icon: "🏷️", roles: ["ADMIN", "MANAGER", "CONTROLLER"] },
|
||||
{ href: "/analytics/skills", label: "Skills Analytics", icon: "📈", roles: ["ADMIN", "MANAGER", "CONTROLLER", "VIEWER"] },
|
||||
{ href: "/reports/chargeability", label: "Chargeability", icon: "📊", roles: ["ADMIN", "MANAGER", "CONTROLLER"] },
|
||||
{ href: "/dashboard", label: "Dashboard", icon: <DashboardIcon />, roles: ["ADMIN", "MANAGER", "CONTROLLER", "USER", "VIEWER"] },
|
||||
{ href: "/resources", label: "Resources", icon: <ResourcesIcon />, roles: ["ADMIN", "MANAGER", "CONTROLLER"] },
|
||||
{ href: "/projects", label: "Projects", icon: <ProjectsIcon />, roles: ["ADMIN", "MANAGER", "CONTROLLER", "VIEWER"] },
|
||||
{ href: "/estimates", label: "Estimates", icon: <EstimatesIcon />, roles: ["ADMIN", "MANAGER", "CONTROLLER", "USER", "VIEWER"] },
|
||||
{ href: "/allocations", label: "Allocations", icon: <AllocationsIcon />, roles: ["ADMIN", "MANAGER", "CONTROLLER"] },
|
||||
{ href: "/timeline", label: "Timeline", icon: <TimelineIcon />, roles: ["ADMIN", "MANAGER", "CONTROLLER", "USER", "VIEWER"] },
|
||||
{ href: "/staffing", label: "Staffing", icon: <StaffingIcon />, roles: ["ADMIN", "MANAGER"] },
|
||||
{ href: "/vacations", label: "Vacations", icon: <VacationIcon />, roles: ["ADMIN", "MANAGER"] },
|
||||
{ href: "/vacations/my", label: "My Vacations", icon: <VacationIcon />, roles: ["ADMIN", "MANAGER", "CONTROLLER", "USER", "VIEWER"] },
|
||||
{ href: "/roles", label: "Roles", icon: <RolesIcon />, roles: ["ADMIN", "MANAGER", "CONTROLLER"] },
|
||||
{ href: "/analytics/skills", label: "Skills Analytics", icon: <SkillsIcon />, roles: ["ADMIN", "MANAGER", "CONTROLLER", "VIEWER"] },
|
||||
{ href: "/reports/chargeability", label: "Chargeability", icon: <ChargeabilityIcon />, roles: ["ADMIN", "MANAGER", "CONTROLLER"] },
|
||||
];
|
||||
|
||||
const adminNavItems = [
|
||||
{ href: "/admin/blueprints", label: "Blueprints", icon: "🏗️" },
|
||||
{ href: "/admin/countries", label: "Countries", icon: "🌍" },
|
||||
{ href: "/admin/org-units", label: "Org Units", icon: "🏢" },
|
||||
{ href: "/admin/utilization-categories", label: "Util. Categories", icon: "📊" },
|
||||
{ href: "/admin/clients", label: "Clients", icon: "🏦" },
|
||||
{ href: "/admin/rate-cards", label: "Rate Cards", icon: "💲" },
|
||||
{ href: "/admin/effort-rules", label: "Effort Rules", icon: "📐" },
|
||||
{ href: "/admin/experience-multipliers", label: "Exp. Multipliers", icon: "📈" },
|
||||
{ href: "/admin/management-levels", label: "Mgmt Levels", icon: "📶" },
|
||||
{ href: "/admin/users", label: "Users", icon: "👤" },
|
||||
{ href: "/admin/settings", label: "Settings", icon: "⚙️" },
|
||||
{ href: "/admin/skill-import", label: "Skill Import", icon: "📥" },
|
||||
{ href: "/admin/blueprints", label: "Blueprints", icon: <AdminIcon /> },
|
||||
{ href: "/admin/countries", label: "Countries", icon: <AdminIcon /> },
|
||||
{ href: "/admin/org-units", label: "Org Units", icon: <AdminIcon /> },
|
||||
{ href: "/admin/utilization-categories", label: "Util. Categories", icon: <AdminIcon /> },
|
||||
{ href: "/admin/clients", label: "Clients", icon: <AdminIcon /> },
|
||||
{ href: "/admin/rate-cards", label: "Rate Cards", icon: <AdminIcon /> },
|
||||
{ href: "/admin/effort-rules", label: "Effort Rules", icon: <AdminIcon /> },
|
||||
{ href: "/admin/experience-multipliers", label: "Exp. Multipliers", icon: <AdminIcon /> },
|
||||
{ href: "/admin/management-levels", label: "Mgmt Levels", icon: <AdminIcon /> },
|
||||
{ href: "/admin/users", label: "Users", icon: <AdminIcon /> },
|
||||
{ href: "/admin/settings", label: "Settings", icon: <AdminIcon /> },
|
||||
{ href: "/admin/skill-import", label: "Skill Import", icon: <AdminIcon /> },
|
||||
];
|
||||
|
||||
const managerNavItems = [
|
||||
{ href: "/admin/vacations", label: "Vacation Mgmt", icon: "🏖️" },
|
||||
{ href: "/admin/vacations", label: "Vacation Mgmt", icon: <VacationIcon /> },
|
||||
];
|
||||
|
||||
function Sidebar({ userRole }: { userRole: string }) {
|
||||
@@ -55,38 +101,45 @@ function Sidebar({ userRole }: { userRole: string }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<nav className="w-64 bg-white dark:bg-gray-900 border-r border-gray-200 dark:border-gray-800 flex flex-col shrink-0">
|
||||
<nav className="w-72 shrink-0 border-r border-white/60 bg-white/80 backdrop-blur-xl dark:border-slate-800 dark:bg-slate-950/75 flex flex-col">
|
||||
{/* Logo */}
|
||||
<div className="p-6 border-b border-gray-200 dark:border-gray-800">
|
||||
<h1 className="text-xl font-bold text-gray-900 dark:text-gray-50">
|
||||
Pl<span className="text-brand-600">anarchy</span>
|
||||
</h1>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-500 mt-1">Resource Planning</p>
|
||||
<div className="border-b border-gray-200/80 px-6 py-6 dark:border-slate-800">
|
||||
<div className="inline-flex items-center gap-3 rounded-2xl border border-brand-200/70 bg-gradient-to-br from-white to-brand-50 px-4 py-3 shadow-sm dark:border-brand-900/50 dark:from-slate-950 dark:to-slate-900">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-2xl bg-brand-600 text-white shadow-lg shadow-brand-600/25">
|
||||
<DashboardIcon />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="font-display text-xl font-semibold text-gray-900 dark:text-gray-50">
|
||||
Pl<span className="text-brand-600">anarchy</span>
|
||||
</h1>
|
||||
<p className="text-xs uppercase tracking-[0.18em] text-gray-500 dark:text-gray-400">Resource Planning</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Nav links */}
|
||||
<div className="flex-1 p-4 space-y-1 overflow-y-auto">
|
||||
<div className="flex-1 space-y-1 overflow-y-auto px-4 py-5">
|
||||
{visibleNavItems.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href as Route}
|
||||
className={clsx(
|
||||
"flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-colors",
|
||||
"group flex items-center gap-3 rounded-2xl px-3 py-2.5 text-sm font-medium transition-all",
|
||||
pathname.startsWith(item.href)
|
||||
? "bg-brand-50 dark:bg-brand-900/30 text-brand-700 dark:text-brand-400"
|
||||
: "text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800",
|
||||
? "bg-gradient-to-r from-brand-50 to-brand-100/70 text-brand-800 shadow-sm ring-1 ring-brand-200/70 dark:from-brand-900/30 dark:to-brand-800/20 dark:text-brand-200 dark:ring-brand-900/40"
|
||||
: "text-gray-700 hover:bg-gray-100/90 hover:text-gray-900 dark:text-gray-300 dark:hover:bg-slate-900 dark:hover:text-white",
|
||||
)}
|
||||
>
|
||||
<span>{item.icon}</span>
|
||||
{item.label}
|
||||
<IconFrame>{item.icon}</IconFrame>
|
||||
<span className="flex-1">{item.label}</span>
|
||||
</Link>
|
||||
))}
|
||||
|
||||
{showManagerSection && (
|
||||
<>
|
||||
<div className="pt-3 pb-1">
|
||||
<div className="border-t border-gray-200 dark:border-gray-700 pt-3">
|
||||
<span className="px-3 text-xs font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">
|
||||
<div className="pb-1 pt-4">
|
||||
<div className="border-t border-gray-200 pt-4 dark:border-slate-800">
|
||||
<span className="px-3 text-[11px] font-semibold uppercase tracking-[0.18em] text-gray-400 dark:text-gray-500">
|
||||
{showAdmin ? "Admin" : "Management"}
|
||||
</span>
|
||||
</div>
|
||||
@@ -96,14 +149,14 @@ function Sidebar({ userRole }: { userRole: string }) {
|
||||
key={item.href}
|
||||
href={item.href as Route}
|
||||
className={clsx(
|
||||
"flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-colors",
|
||||
"group flex items-center gap-3 rounded-2xl px-3 py-2.5 text-sm font-medium transition-all",
|
||||
pathname.startsWith(item.href)
|
||||
? "bg-brand-50 dark:bg-brand-900/30 text-brand-700 dark:text-brand-400"
|
||||
: "text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800",
|
||||
? "bg-gradient-to-r from-brand-50 to-brand-100/70 text-brand-800 shadow-sm ring-1 ring-brand-200/70 dark:from-brand-900/30 dark:to-brand-800/20 dark:text-brand-200 dark:ring-brand-900/40"
|
||||
: "text-gray-700 hover:bg-gray-100/90 hover:text-gray-900 dark:text-gray-300 dark:hover:bg-slate-900 dark:hover:text-white",
|
||||
)}
|
||||
>
|
||||
<span>{item.icon}</span>
|
||||
{item.label}
|
||||
<IconFrame>{item.icon}</IconFrame>
|
||||
<span className="flex-1">{item.label}</span>
|
||||
</Link>
|
||||
))}
|
||||
{managerNavItems.map((item) => (
|
||||
@@ -111,14 +164,14 @@ function Sidebar({ userRole }: { userRole: string }) {
|
||||
key={item.href}
|
||||
href={item.href as Route}
|
||||
className={clsx(
|
||||
"flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-colors",
|
||||
"group flex items-center gap-3 rounded-2xl px-3 py-2.5 text-sm font-medium transition-all",
|
||||
pathname.startsWith(item.href)
|
||||
? "bg-brand-50 dark:bg-brand-900/30 text-brand-700 dark:text-brand-400"
|
||||
: "text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800",
|
||||
? "bg-gradient-to-r from-brand-50 to-brand-100/70 text-brand-800 shadow-sm ring-1 ring-brand-200/70 dark:from-brand-900/30 dark:to-brand-800/20 dark:text-brand-200 dark:ring-brand-900/40"
|
||||
: "text-gray-700 hover:bg-gray-100/90 hover:text-gray-900 dark:text-gray-300 dark:hover:bg-slate-900 dark:hover:text-white",
|
||||
)}
|
||||
>
|
||||
<span>{item.icon}</span>
|
||||
{item.label}
|
||||
<IconFrame>{item.icon}</IconFrame>
|
||||
<span className="flex-1">{item.label}</span>
|
||||
</Link>
|
||||
))}
|
||||
</>
|
||||
@@ -126,31 +179,35 @@ function Sidebar({ userRole }: { userRole: string }) {
|
||||
</div>
|
||||
|
||||
{/* Bottom actions */}
|
||||
<div className="p-4 border-t border-gray-200 dark:border-gray-800 space-y-1">
|
||||
<div className="flex items-center gap-2 px-3 py-1">
|
||||
<div className="space-y-1 border-t border-gray-200/80 p-4 dark:border-slate-800">
|
||||
<div className="flex items-center gap-2 px-3 py-2">
|
||||
<NotificationBell />
|
||||
<span className="text-xs text-gray-400 dark:text-gray-500">Notifications</span>
|
||||
<span className="text-xs uppercase tracking-[0.16em] text-gray-400 dark:text-gray-500">Notifications</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPrefsOpen(true)}
|
||||
className="w-full flex items-center gap-3 px-3 py-2 text-sm text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
|
||||
className="flex w-full items-center gap-3 rounded-2xl px-3 py-2.5 text-sm text-gray-700 transition-colors hover:bg-gray-100/90 dark:text-gray-300 dark:hover:bg-slate-900"
|
||||
>
|
||||
<svg className="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
Preferences
|
||||
<IconFrame>
|
||||
<svg className="h-4 w-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
</IconFrame>
|
||||
<span>Preferences</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void signOut({ callbackUrl: "/auth/signin" })}
|
||||
className="w-full flex items-center gap-3 px-3 py-2 text-sm text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
|
||||
className="flex w-full items-center gap-3 rounded-2xl px-3 py-2.5 text-sm text-gray-700 transition-colors hover:bg-gray-100/90 dark:text-gray-300 dark:hover:bg-slate-900"
|
||||
>
|
||||
<svg className="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
|
||||
</svg>
|
||||
Sign out
|
||||
<IconFrame>
|
||||
<svg className="h-4 w-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
|
||||
</svg>
|
||||
</IconFrame>
|
||||
<span>Sign out</span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -166,9 +223,9 @@ export function AppShell({ children, userRole = "USER" }: { children: React.Reac
|
||||
<Suspense>
|
||||
<NavProgressBar />
|
||||
</Suspense>
|
||||
<div className="flex h-screen bg-gray-50 dark:bg-gray-950">
|
||||
<div className="flex h-screen bg-transparent">
|
||||
<Sidebar userRole={userRole} />
|
||||
<main className="flex-1 overflow-auto">{children}</main>
|
||||
<main className="flex-1 overflow-auto bg-transparent">{children}</main>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user