fix: dashboard layout spacing and CSS cascade layer bug

Move CSS reset into @layer base so Tailwind utilities aren't overridden.
Fix settings panel: search icon overlap, cramped widget items, spacing
between header card and widget grid using inline margin styles.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ashwin Kumar 2026-03-27 20:10:08 +01:00
parent 7c4ed94975
commit bed0942d12
2 changed files with 57 additions and 42 deletions

View file

@ -21,7 +21,9 @@
--font-family-sans: 'Exo 2', sans-serif; --font-family-sans: 'Exo 2', sans-serif;
} }
@layer base {
* { box-sizing: border-box; margin: 0; padding: 0; } * { box-sizing: border-box; margin: 0; padding: 0; }
}
body { body {
font-family: 'Exo 2', sans-serif; font-family: 'Exo 2', sans-serif;

View file

@ -322,18 +322,18 @@ export default function AdminHomePage() {
return ( return (
<AdminShell> <AdminShell>
<div class="w-full bg-[#F9FAFB] pb-12"> <div class="w-full">
<div class="overflow-hidden rounded-2xl border border-[#E5E7EB] bg-white px-8 pt-10 pb-12 shadow-sm md:px-10 md:pt-11 md:pb-13"> <div class="rounded-2xl border border-[#E5E7EB] bg-white px-6 py-5 shadow-sm md:px-8" style="margin-bottom: 28px">
<div class="flex flex-col gap-5 lg:flex-row lg:items-center lg:justify-between"> <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div class="max-w-3xl" style="padding-left: 40px; padding-right: 20px;"> <div>
<h1 class="text-[28px] font-bold leading-tight text-[#111827]">Dashboard Overview</h1> <h1 class="text-[22px] font-bold leading-tight text-[#111827]">Dashboard Overview</h1>
<p class="mt-1 text-[14px] text-[#6B7280]">Manage widget layout, visibility, sizing, and dashboard presentation</p> <p class="mt-0.5 text-[13px] text-[#6B7280]">Manage widget layout, visibility, sizing, and dashboard presentation</p>
</div> </div>
<div class="flex w-full lg:w-auto lg:justify-end" style="margin-right: 20px;"> <div class="flex shrink-0">
<button <button
type="button" type="button"
class="inline-flex h-9 w-full items-center justify-center gap-1.5 whitespace-nowrap rounded-xl bg-[#0D0D2A] px-4 text-[13px] font-semibold leading-none text-white shadow-sm lg:w-auto lg:min-w-[190px]" class="inline-flex h-9 items-center gap-1.5 whitespace-nowrap rounded-xl bg-[#0D0D2A] px-5 text-[13px] font-semibold leading-none text-white shadow-sm"
onClick={() => { onClick={() => {
const next = !settingsOpen(); const next = !settingsOpen();
setSettingsOpen(next); setSettingsOpen(next);
@ -347,14 +347,13 @@ export default function AdminHomePage() {
</div> </div>
</div> </div>
<div class={settingsOpen() ? 'h-10' : 'h-0'} />
<Show when={settingsOpen()}> <Show when={settingsOpen()}>
<div class="rounded-2xl border border-[#E5E7EB] bg-white p-8 shadow-sm md:p-9"> <div class="rounded-2xl border border-[#E5E7EB] bg-white p-6 shadow-sm md:p-7">
<div class="flex flex-wrap items-center justify-between gap-4"> {/* Settings header */}
<div class="pl-2 pt-1"> <div class="flex flex-wrap items-start justify-between gap-4">
<div>
<h2 class="text-[15px] font-semibold text-[#111827]">Widget Settings</h2> <h2 class="text-[15px] font-semibold text-[#111827]">Widget Settings</h2>
<p class="mt-2 text-[13px] text-[#6B7280]">Choose visible widgets and select a grid layout.</p> <p class="mt-1 text-[13px] text-[#6B7280]">Choose visible widgets and select a grid layout.</p>
</div> </div>
<button <button
type="button" type="button"
@ -365,68 +364,83 @@ export default function AdminHomePage() {
Reset Layout Reset Layout
</button> </button>
</div> </div>
<div class="mt-6 grid gap-5 md:grid-cols-2 xl:grid-cols-4">
<label class="relative"> {/* Filter controls */}
<Search size={14} class="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-[#FA5014]" /> <div class="mt-5 flex flex-wrap gap-3">
<div class="relative min-w-[200px] flex-1">
<Search size={14} class="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-[#FA5014]" style="z-index:1" />
<input <input
value={search()} value={search()}
onInput={(event) => setSearch(event.currentTarget.value)} onInput={(event) => setSearch(event.currentTarget.value)}
placeholder="Search widgets" placeholder="Search widgets"
class="h-11 w-full rounded-lg border border-[#E5E7EB] bg-white pl-9 pr-3 text-sm text-[#111827] outline-none" class="h-10 w-full rounded-lg border border-[#E5E7EB] bg-white pl-10 pr-3 text-sm text-[#111827] outline-none focus:border-[#FA5014]"
/> />
</label> </div>
<select <select
class="h-11 rounded-lg border border-[#E5E7EB] bg-white px-3 text-sm text-[#111827]" class="h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-sm text-[#111827]"
value={filterMode()} value={filterMode()}
onChange={(event) => setFilterMode(event.currentTarget.value as FilterMode)} onChange={(event) => setFilterMode(event.currentTarget.value as FilterMode)}
> >
<option value="all">Filter: All Widgets</option> <option value="all">All Widgets</option>
<option value="summary">Filter: Summary Widgets</option> <option value="summary">Summary Only</option>
<option value="analytics">Filter: Analytical Widgets</option> <option value="analytics">Analytical Only</option>
</select> </select>
<select <select
class="h-11 rounded-lg border border-[#E5E7EB] bg-white px-3 text-sm text-[#111827]" class="h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-sm text-[#111827]"
value={sortMode()} value={sortMode()}
onChange={(event) => setSortMode(event.currentTarget.value as SortMode)} onChange={(event) => setSortMode(event.currentTarget.value as SortMode)}
> >
<option value="layout">Sort: Layout Order</option> <option value="layout">Layout Order</option>
<option value="name">Sort: Name</option> <option value="name">Name</option>
<option value="status">Sort: Status</option> <option value="status">Status</option>
</select> </select>
<select <select
class="h-11 rounded-lg border border-[#E5E7EB] bg-white px-3 text-sm text-[#111827]" class="h-10 rounded-lg border border-[#E5E7EB] bg-white px-3 text-sm text-[#111827]"
value={gridLayout()} value={gridLayout()}
onChange={(event) => setGridLayout(event.currentTarget.value as GridLayoutMode)} onChange={(event) => setGridLayout(event.currentTarget.value as GridLayoutMode)}
> >
<option value="3x4">Grid: 3 x 4</option> <option value="3x4">Grid 3 × 4</option>
<option value="3x3">Grid: 3 x 3</option> <option value="3x3">Grid 3 × 3</option>
</select> </select>
</div> </div>
<div class="mt-6 grid gap-5 md:grid-cols-2">
{/* Widget list */}
<div class="mt-5 grid gap-3 md:grid-cols-2">
<For each={ADMIN_DASHBOARD_WIDGETS}> <For each={ADMIN_DASHBOARD_WIDGETS}>
{(definition) => { {(definition) => {
const visible = () => layout().visibility[definition.widgetKey] !== false; const visible = () => layout().visibility[definition.widgetKey] !== false;
const meta = WIDGET_META[definition.widgetKey];
const state = meta?.state || 'empty';
return ( return (
<div class="rounded-2xl border border-[#E5E7EB] bg-[#F9FAFB] px-4 py-5"> <div class="flex items-center justify-between gap-4 rounded-xl border border-[#E5E7EB] bg-[#F9FAFB] px-4 py-4">
<div class="flex items-center justify-between gap-3"> <div class="flex items-center gap-3 min-w-0">
<div> <div class="shrink-0 text-[#FA5014]">
<p class="text-sm font-semibold text-[#111827]">{definition.title}</p> {iconForWidget(definition.widgetKey)}
<p class="mt-0.5 text-[11px] font-semibold uppercase tracking-wide text-[#9CA3AF]">{definition.moduleKey}</p>
</div> </div>
<div class="min-w-0">
<p class="text-sm font-semibold text-[#111827]">{definition.title}</p>
<p class="mt-0.5 text-[11px] font-medium uppercase tracking-wide text-[#9CA3AF]">{definition.moduleKey}</p>
</div>
</div>
<div class="flex shrink-0 items-center gap-2">
<span class={`hidden rounded-full border px-2 py-0.5 text-[10px] font-semibold sm:inline-flex ${badgeClass(state)}`}>
{meta?.statusLabel || 'No Data'}
</span>
<button <button
type="button" type="button"
class={`inline-flex h-8 items-center gap-1 rounded-md border px-3 text-xs font-semibold ${ class={`inline-flex h-8 items-center gap-1.5 rounded-md border px-3 text-xs font-semibold ${
visible() visible()
? 'border-[#FDBA8C] bg-[#FFF1EB] text-[#FA5014]' ? 'border-[#FDBA8C] bg-[#FFF1EB] text-[#FA5014]'
: 'border-[#E5E7EB] bg-white text-[#6B7280]' : 'border-[#E5E7EB] bg-white text-[#6B7280]'
}`} }`}
onClick={() => setWidgetVisibility(definition.widgetKey, !visible())} onClick={() => setWidgetVisibility(definition.widgetKey, !visible())}
> >
{visible() ? <Eye size={13} class="text-[#FA5014]" /> : <EyeOff size={13} class="text-[#FA5014]" />} {visible() ? 'Visible' : 'Hidden'} {visible() ? <Eye size={12} /> : <EyeOff size={12} />}
{visible() ? 'Visible' : 'Hidden'}
</button> </button>
</div> </div>
</div> </div>
@ -434,15 +448,14 @@ export default function AdminHomePage() {
}} }}
</For> </For>
</div> </div>
<Show when={isAutoSaving() || autoSaveNotice()}> <Show when={isAutoSaving() || autoSaveNotice()}>
<p class="mt-4 text-xs text-[#6B7280]">{isAutoSaving() ? 'Saving layout...' : autoSaveNotice()}</p> <p class="mt-4 text-xs text-[#6B7280]">{isAutoSaving() ? 'Saving layout...' : autoSaveNotice()}</p>
</Show> </Show>
</div> </div>
</Show> </Show>
<div class={settingsOpen() ? 'h-8' : 'h-12'} /> <div class="grid grid-cols-1 gap-5 xl:grid-cols-12" style="margin-top: 28px">
<div class="grid grid-cols-1 gap-7 xl:grid-cols-12">
<For each={orderedWidgets()}> <For each={orderedWidgets()}>
{(definition) => { {(definition) => {
const meta = WIDGET_META[definition.widgetKey]; const meta = WIDGET_META[definition.widgetKey];