feat: redesign dashboard layout with calendar, open sessions, and glass-card panels
New layout: greeting → calendar+actions → sessions+stats → activity Replaces old QuickStats and SessionsPanel with new dashboard components Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,8 +12,7 @@ import { usePinnedFlowsStore } from '@/store/pinnedFlowsStore'
|
|||||||
import { useUserPreferencesStore } from '@/store/userPreferencesStore'
|
import { useUserPreferencesStore } from '@/store/userPreferencesStore'
|
||||||
import { usePaginationParams } from '@/hooks/usePaginationParams'
|
import { usePaginationParams } from '@/hooks/usePaginationParams'
|
||||||
import { useCachedQuota } from '@/hooks/useCachedQuota'
|
import { useCachedQuota } from '@/hooks/useCachedQuota'
|
||||||
import { QuickStats } from '@/components/dashboard/QuickStats'
|
// QuickStats and SessionsPanel replaced by new dashboard panels
|
||||||
import { SessionsPanel } from '@/components/dashboard/SessionsPanel'
|
|
||||||
import { TreeGridView } from '@/components/library/TreeGridView'
|
import { TreeGridView } from '@/components/library/TreeGridView'
|
||||||
import { TreeListView } from '@/components/library/TreeListView'
|
import { TreeListView } from '@/components/library/TreeListView'
|
||||||
import { TreeTableView } from '@/components/library/TreeTableView'
|
import { TreeTableView } from '@/components/library/TreeTableView'
|
||||||
@@ -22,6 +21,10 @@ import { AIFlowBuilderModal } from '@/components/ai-builder/AIFlowBuilderModal'
|
|||||||
import { CreateFlowDropdown } from '@/components/common/CreateFlowDropdown'
|
import { CreateFlowDropdown } from '@/components/common/CreateFlowDropdown'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { toast } from '@/lib/toast'
|
import { toast } from '@/lib/toast'
|
||||||
|
import { WeeklyCalendar } from '@/components/dashboard/WeeklyCalendar'
|
||||||
|
import { QuickActions } from '@/components/dashboard/QuickActions'
|
||||||
|
import { OpenSessions } from '@/components/dashboard/OpenSessions'
|
||||||
|
import { RecentActivity } from '@/components/dashboard/RecentActivity'
|
||||||
|
|
||||||
function timeAgo(dateStr: string): string {
|
function timeAgo(dateStr: string): string {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
@@ -215,16 +218,22 @@ export function QuickStartPage() {
|
|||||||
const now = new Date()
|
const now = new Date()
|
||||||
return d.toDateString() === now.toDateString()
|
return d.toDateString() === now.toDateString()
|
||||||
}).length
|
}).length
|
||||||
const completedSessions = allSessions.filter(s => s.completed_at).length
|
// completedSessions removed — no longer displayed in new layout
|
||||||
|
|
||||||
const recentSessionItems = allSessions.slice(0, 5).map(s => ({
|
// Open sessions for the new panel (3 oldest)
|
||||||
|
const openSessionItems = activeSessions
|
||||||
|
.sort((a, b) => new Date(a.started_at).getTime() - new Date(b.started_at).getTime())
|
||||||
|
.slice(0, 3)
|
||||||
|
.map(s => ({
|
||||||
id: s.id,
|
id: s.id,
|
||||||
treeName: s.tree_snapshot?.name || 'Unknown',
|
treeName: s.tree_snapshot?.name || 'Unknown',
|
||||||
status: (s.completed_at ? 'completed' : 'in_progress') as 'completed' | 'in_progress',
|
treeId: s.tree_id,
|
||||||
ticketNumber: s.ticket_number || undefined,
|
treeType: (s.tree_snapshot as unknown as Record<string, unknown>)?.tree_type as string | undefined,
|
||||||
timeAgo: timeAgo(s.started_at),
|
timeAgo: timeAgo(s.started_at),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// recentSessionItems removed — replaced by RecentActivity component
|
||||||
|
|
||||||
// Favorites display
|
// Favorites display
|
||||||
const MAX_VISIBLE_FAVORITES = 8
|
const MAX_VISIBLE_FAVORITES = 8
|
||||||
const visibleFavorites = showAllFavorites ? pinnedItems : pinnedItems.slice(0, MAX_VISIBLE_FAVORITES)
|
const visibleFavorites = showAllFavorites ? pinnedItems : pinnedItems.slice(0, MAX_VISIBLE_FAVORITES)
|
||||||
@@ -270,27 +279,61 @@ export function QuickStartPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 space-y-6">
|
<div className="p-6 space-y-6">
|
||||||
{/* Page Header */}
|
{/* Greeting */}
|
||||||
<div className="flex items-start justify-between">
|
<div className="fade-in" style={{ animationDelay: '100ms' }}>
|
||||||
<div>
|
<h1 className="font-heading text-4xl font-extrabold tracking-tight text-foreground">
|
||||||
<h1 className="font-heading text-[1.375rem] font-bold tracking-tight text-foreground">
|
Good {new Date().getHours() < 12 ? 'morning' : new Date().getHours() < 18 ? 'afternoon' : 'evening'}, {user?.name?.split(' ')[0] || 'there'}
|
||||||
Dashboard
|
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-1 text-sm text-muted-foreground">
|
<p className="mt-1 text-sm text-muted-foreground">
|
||||||
Welcome back. Here's what's happening with your flows.
|
{new Date().toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' })}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Row 1: Calendar + Quick Actions */}
|
||||||
|
<div className="flex gap-4" style={{ alignItems: 'stretch' }}>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<WeeklyCalendar />
|
||||||
|
</div>
|
||||||
|
<div className="w-72 shrink-0">
|
||||||
|
<QuickActions />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Quick Stats */}
|
{/* Row 2: Open Sessions + Stats 2x2 */}
|
||||||
<QuickStats
|
<div className="flex gap-4" style={{ alignItems: 'stretch' }}>
|
||||||
stats={[
|
<div className="flex-1 min-w-0">
|
||||||
{ label: 'My Flows', value: myFlows.length, gradient: true },
|
<OpenSessions sessions={openSessionItems} />
|
||||||
{ label: 'Sessions Today', value: todaySessions, color: '#f59e0b' },
|
</div>
|
||||||
{ label: 'Open Sessions', value: openSessions, meta: `${completedSessions} completed` },
|
<div className="w-72 shrink-0">
|
||||||
|
<div className="grid grid-cols-2 gap-3 h-full">
|
||||||
|
{[
|
||||||
|
{ label: 'Active Flows', value: myFlows.length, gradient: true, glow: true },
|
||||||
|
{ label: 'This Week', value: todaySessions },
|
||||||
|
{ label: 'Open Sessions', value: openSessions },
|
||||||
{ label: 'Favorites', value: pinnedItems.length },
|
{ label: 'Favorites', value: pinnedItems.length },
|
||||||
]}
|
].map((stat, i) => (
|
||||||
/>
|
<div
|
||||||
|
key={stat.label}
|
||||||
|
className={cn('glass-card p-4 flex flex-col justify-between fade-in', stat.glow && 'active-glow')}
|
||||||
|
style={{ animationDelay: `${500 + i * 70}ms` }}
|
||||||
|
>
|
||||||
|
<p className="font-label text-[0.625rem] font-medium uppercase tracking-[0.1em] text-muted-foreground">
|
||||||
|
{stat.label}
|
||||||
|
</p>
|
||||||
|
<p className={cn('font-heading text-2xl font-extrabold tracking-tight', stat.gradient && 'text-gradient-brand')}>
|
||||||
|
{stat.value}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Row 3: Recent Activity */}
|
||||||
|
<RecentActivity />
|
||||||
|
|
||||||
|
{/* ── Existing content below ── */}
|
||||||
|
<div style={{ borderTop: '1px solid var(--glass-border)' }} className="pt-6 space-y-6">
|
||||||
|
|
||||||
{/* Search */}
|
{/* Search */}
|
||||||
<div ref={searchRef} className="relative">
|
<div ref={searchRef} className="relative">
|
||||||
@@ -332,9 +375,6 @@ export function QuickStartPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Recent Sessions */}
|
|
||||||
<SessionsPanel sessions={recentSessionItems} delay={150} />
|
|
||||||
|
|
||||||
{/* Favorites Section */}
|
{/* Favorites Section */}
|
||||||
<div>
|
<div>
|
||||||
<div className="mb-3 flex items-center justify-between">
|
<div className="mb-3 flex items-center justify-between">
|
||||||
@@ -562,6 +602,7 @@ export function QuickStartPage() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Fork Modal */}
|
{/* Fork Modal */}
|
||||||
{forkTarget && (
|
{forkTarget && (
|
||||||
|
|||||||
Reference in New Issue
Block a user