refactor(layout): remove site-header and tighten spacing

replace site-header with inline sidebar trigger, reduce
dashboard padding/gaps, and adjust schedule page layout
to flex properly within the content area.
This commit is contained in:
Nicholai Vogel 2026-01-23 21:08:18 -07:00
parent 598047635d
commit e6c1b7c4a0
12 changed files with 91 additions and 42 deletions

View File

@ -1,8 +1,8 @@
import { AppSidebar } from "@/components/app-sidebar"
import { SiteHeader } from "@/components/site-header"
import {
SidebarInset,
SidebarProvider,
SidebarTrigger,
} from "@/components/ui/sidebar"
export default function DashboardLayout({
@ -15,15 +15,16 @@ export default function DashboardLayout({
style={
{
"--sidebar-width": "calc(var(--spacing) * 72)",
"--header-height": "calc(var(--spacing) * 12)",
} as React.CSSProperties
}
>
<AppSidebar variant="inset" />
<SidebarInset>
<SiteHeader />
<div className="flex flex-1 flex-col">
<div className="@container/main flex flex-1 flex-col gap-2">
<div className="@container/main flex flex-1 flex-col">
<div className="px-4 pt-2">
<SidebarTrigger className="-ml-1" />
</div>
{children}
</div>
</div>

View File

@ -6,9 +6,9 @@ import data from "./data.json"
export default function Page() {
return (
<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
<div className="flex flex-col gap-3 py-2 md:gap-4 md:py-3">
<SectionCards />
<div className="px-4 lg:px-6">
<div className="px-3 lg:px-4">
<ChartAreaInteractive />
</div>
<DataTable data={data} />

View File

@ -49,7 +49,7 @@ export default async function SchedulePage({
}
return (
<div className="p-6">
<div className="px-4 py-2 flex flex-col flex-1 min-h-0">
<ScheduleView
projectId={id}
projectName={projectName}

View File

@ -2,6 +2,7 @@
import { useRef, useEffect, useState } from "react"
import type { FrappeTask } from "@/lib/schedule/gantt-transform"
import "./gantt.css"
type ViewMode = "Day" | "Week" | "Month"
@ -66,6 +67,15 @@ export function GanttChart({
},
})
// remove overflow from inner gantt-container so popup isn't clipped
// the parent wrapper handles horizontal scrolling instead
const ganttContainer = containerRef.current.querySelector(
".gantt-container"
) as HTMLElement | null
if (ganttContainer) {
ganttContainer.style.overflow = "visible"
}
setLoaded(true)
}
@ -88,7 +98,7 @@ export function GanttChart({
}
return (
<div className="gantt-container overflow-x-auto">
<div className="gantt-wrapper relative overflow-x-auto">
<div ref={containerRef} />
</div>
)

View File

@ -1,6 +1,6 @@
/* frappe-gantt base styles (vendored from frappe-gantt/dist/frappe-gantt.css) */
:root{--g-arrow-color: #1f2937;--g-bar-color: #fff;--g-bar-border: #fff;--g-tick-color-thick: #ededed;--g-tick-color: #f3f3f3;--g-actions-background: #f3f3f3;--g-border-color: #ebeff2;--g-text-muted: #7c7c7c;--g-text-light: #fff;--g-text-dark: #171717;--g-progress-color: #dbdbdb;--g-handle-color: #37352f;--g-weekend-label-color: #dcdce4;--g-expected-progress: #c4c4e9;--g-header-background: #fff;--g-row-color: #fdfdfd;--g-row-border-color: #c7c7c7;--g-today-highlight: #37352f;--g-popup-actions: #ebeff2;--g-weekend-highlight-color: #f7f7f7}
.gantt-container{line-height:14.5px;position:relative;overflow:auto;font-size:12px;height:var(--gv-grid-height);width:100%;border-radius:8px}
.gantt-container{line-height:14.5px;position:relative;isolation:isolate;overflow:auto;font-size:12px;height:var(--gv-grid-height);width:100%;border-radius:8px}
.gantt-container .popup-wrapper{position:absolute;top:0;left:0;background:#fff;box-shadow:0 10px 24px -3px #0003;padding:10px;border-radius:5px;width:max-content;z-index:1000}
.gantt-container .popup-wrapper .title{margin-bottom:2px;color:var(--g-text-dark);font-size:.85rem;font-weight:650;line-height:15px}
.gantt-container .popup-wrapper .subtitle{color:var(--g-text-dark);font-size:.8rem;margin-bottom:5px}

View File

@ -103,8 +103,8 @@ export function ScheduleCalendarView({
}
return (
<div>
<div className="flex items-center justify-between mb-4">
<div className="flex flex-col flex-1 min-h-0">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<Button
variant="outline"
@ -144,7 +144,7 @@ export function ScheduleCalendarView({
</Select>
</div>
<div className="border rounded-md overflow-hidden">
<div className="border rounded-md overflow-hidden flex flex-col flex-1 min-h-0">
<div className="grid grid-cols-7 border-b">
{["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map(
(day) => (
@ -158,7 +158,7 @@ export function ScheduleCalendarView({
)}
</div>
<div className="grid grid-cols-7">
<div className="grid grid-cols-7 flex-1">
{days.map((day) => {
const dateKey = format(day, "yyyy-MM-dd")
const dayTasks = tasksByDate.get(dateKey) || []
@ -174,7 +174,7 @@ export function ScheduleCalendarView({
return (
<div
key={dateKey}
className={`min-h-[90px] border-r border-b last:border-r-0 p-1 ${
className={`min-h-0 border-r border-b last:border-r-0 p-1 ${
!inMonth ? "bg-muted/30" : ""
} ${isNonWork ? "bg-muted/50" : ""}`}
>

View File

@ -87,8 +87,8 @@ export function ScheduleGanttView({
}
return (
<div>
<div className="flex items-center justify-between mb-4">
<div className="flex flex-col flex-1 min-h-0">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-3">
{(["Day", "Week", "Month"] as ViewMode[]).map((mode) => (
<Button
@ -134,7 +134,7 @@ export function ScheduleGanttView({
<ResizablePanelGroup
orientation="horizontal"
className="border rounded-md min-h-[400px]"
className="border rounded-md flex-1 min-h-[300px]"
>
<ResizablePanel defaultSize={30} minSize={20}>
<div className="h-full overflow-auto">

View File

@ -296,8 +296,8 @@ export function ScheduleListView({
})
return (
<div>
<div className="flex gap-2 mb-4">
<div className="flex flex-col flex-1 min-h-0">
<div className="flex gap-2 mb-2">
<Button
size="sm"
variant="outline"
@ -309,7 +309,7 @@ export function ScheduleListView({
</Button>
</div>
<div className="rounded-md border">
<div className="rounded-md border flex-1 overflow-auto">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (

View File

@ -25,7 +25,7 @@ export function ScheduleToolbar({ onNewItem }: ScheduleToolbarProps) {
const [offlineMode, setOfflineMode] = useState(false)
return (
<div className="flex items-center justify-between py-3 border-b mb-4">
<div className="flex items-center justify-between py-1.5 border-b mb-2">
<div className="flex items-center gap-3">
<Button variant="ghost" size="icon" className="size-8">
<IconSettings className="size-4" />

View File

@ -35,31 +35,37 @@ export function ScheduleView({
const [taskFormOpen, setTaskFormOpen] = useState(false)
return (
<div>
<div className="flex items-center justify-between mb-4">
<h1 className="text-2xl font-semibold">
<div className="flex flex-col flex-1 min-h-0">
<div className="flex items-center justify-between mb-2">
<h1 className="text-lg font-semibold">
{projectName} - Schedule
</h1>
<Tabs
value={topTab}
onValueChange={(v) => setTopTab(v as TopTab)}
>
<TabsList>
<TabsTrigger value="schedule">Schedule</TabsTrigger>
<TabsTrigger value="baseline">Baseline</TabsTrigger>
<TabsTrigger value="exceptions">
Workday Exceptions
</TabsTrigger>
</TabsList>
</Tabs>
</div>
<Tabs
value={topTab}
onValueChange={(v) => setTopTab(v as TopTab)}
className="flex flex-col flex-1 min-h-0"
>
<TabsList>
<TabsTrigger value="schedule">Schedule</TabsTrigger>
<TabsTrigger value="baseline">Baseline</TabsTrigger>
<TabsTrigger value="exceptions">
Workday Exceptions
</TabsTrigger>
</TabsList>
<TabsContent value="schedule" className="mt-0">
<TabsContent value="schedule" className="mt-0 flex flex-col flex-1 min-h-0">
<ScheduleToolbar onNewItem={() => setTaskFormOpen(true)} />
<Tabs
value={subTab}
onValueChange={(v) => setSubTab(v as ScheduleSubTab)}
className="flex flex-col flex-1 min-h-0"
>
<TabsList className="bg-transparent border-b rounded-none h-auto p-0 gap-4">
<TabsTrigger
@ -82,7 +88,7 @@ export function ScheduleView({
</TabsTrigger>
</TabsList>
<TabsContent value="calendar" className="mt-4">
<TabsContent value="calendar" className="mt-2 flex flex-col flex-1 min-h-0">
<ScheduleCalendarView
projectId={projectId}
tasks={initialData.tasks}
@ -90,7 +96,7 @@ export function ScheduleView({
/>
</TabsContent>
<TabsContent value="list" className="mt-4">
<TabsContent value="list" className="mt-2 flex flex-col flex-1 min-h-0">
<ScheduleListView
projectId={projectId}
tasks={initialData.tasks}
@ -98,7 +104,7 @@ export function ScheduleView({
/>
</TabsContent>
<TabsContent value="gantt" className="mt-4">
<TabsContent value="gantt" className="mt-2 flex flex-col flex-1 min-h-0">
<ScheduleGanttView
projectId={projectId}
tasks={initialData.tasks}
@ -108,7 +114,7 @@ export function ScheduleView({
</Tabs>
</TabsContent>
<TabsContent value="baseline" className="mt-4">
<TabsContent value="baseline" className="mt-2">
<ScheduleBaselineView
projectId={projectId}
baselines={baselines}
@ -116,7 +122,7 @@ export function ScheduleView({
/>
</TabsContent>
<TabsContent value="exceptions" className="mt-4">
<TabsContent value="exceptions" className="mt-2">
<WorkdayExceptionsView
projectId={projectId}
exceptions={initialData.exceptions}

View File

@ -19,6 +19,14 @@ import {
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Calendar } from "@/components/ui/calendar"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
import { IconCalendar } from "@tabler/icons-react"
import { format, parseISO } from "date-fns"
import { Slider } from "@/components/ui/slider"
import {
Select,
@ -178,9 +186,33 @@ export function TaskFormDialog({
render={({ field }) => (
<FormItem>
<FormLabel>Start Date</FormLabel>
<FormControl>
<Input type="date" {...field} />
</FormControl>
<Popover>
<PopoverTrigger asChild>
<FormControl>
<Button
variant="outline"
className="w-full justify-start text-left font-normal"
>
<IconCalendar className="size-4 mr-2 text-muted-foreground" />
{field.value
? format(parseISO(field.value), "MMM d, yyyy")
: "Pick a date"}
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={field.value ? parseISO(field.value) : undefined}
onSelect={(date) => {
if (date) {
field.onChange(format(date, "yyyy-MM-dd"))
}
}}
initialFocus
/>
</PopoverContent>
</Popover>
<FormMessage />
</FormItem>
)}

View File

@ -12,7 +12,7 @@ import {
export function SectionCards() {
return (
<div className="*:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card dark:*:data-[slot=card]:bg-card grid grid-cols-1 gap-4 px-4 *:data-[slot=card]:bg-gradient-to-t *:data-[slot=card]:shadow-xs lg:px-6 @xl/main:grid-cols-2 @5xl/main:grid-cols-4">
<div className="*:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card dark:*:data-[slot=card]:bg-card grid grid-cols-1 gap-3 px-3 *:data-[slot=card]:bg-gradient-to-t *:data-[slot=card]:shadow-xs lg:px-4 @xl/main:grid-cols-2 @5xl/main:grid-cols-4">
<Card className="@container/card">
<CardHeader>
<CardDescription>Total Revenue</CardDescription>