Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/assets/pulse/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>Pulse</title>
</head>
<body>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/insights/InsightsFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ export function InsightsFiltersBar({
const hasActiveFilters = selectedDepts.length > 0 || selectedBranches.length > 0 || filters.employee;

return (
<div className="flex flex-wrap items-center gap-2">
<div className="flex flex-wrap items-center gap-2 overflow-x-auto">
{/* Date range presets */}
<div className="flex gap-1 rounded-lg border border-zinc-800 bg-zinc-900/50 p-1">
<div className="flex gap-1 rounded-lg border border-zinc-800 bg-zinc-900/50 p-1 shrink-0 overflow-x-auto">
{PRESETS.map(({ id, label }) => (
<button
key={id}
Expand Down
18 changes: 12 additions & 6 deletions frontend/src/components/layout/AppLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useState } from 'react';
import { Sidebar } from './Sidebar';
import { Topbar } from './Topbar';
import { BottomNav } from './BottomNav';
import { Outlet } from 'react-router-dom';
import { useAuth } from '../../store/AuthContext';

Expand All @@ -21,12 +22,12 @@ export function AppLayout() {

if (!currentUser) {
return (
<div className="h-screen w-screen flex items-center justify-center bg-zinc-950 text-zinc-100">
<div className="h-screen w-screen flex items-center justify-center bg-zinc-950 text-zinc-100 px-6">
<div className="flex flex-col items-center gap-4 max-w-sm text-center">
<p className="text-zinc-400 text-sm">You must be logged in and have a Pulse account to use this app.</p>
<a
href="/login?redirect-to=/pulse"
className="text-indigo-400 hover:text-indigo-300 text-sm font-medium underline underline-offset-2"
className="inline-flex items-center justify-center h-10 px-6 text-indigo-400 hover:text-indigo-300 text-sm font-medium underline underline-offset-2"
>
Go to login
</a>
Expand All @@ -37,18 +38,23 @@ export function AppLayout() {

return (
<div className="h-screen w-full flex overflow-hidden bg-zinc-950 text-zinc-50 font-sans selection:bg-indigo-500/30">
{/* Sidebar */}
<Sidebar collapsed={sidebarCollapsed} onToggleCollapse={() => setSidebarCollapsed((c) => !c)} />
{/* Sidebar — hidden on mobile */}
<div className="hidden md:flex">
<Sidebar collapsed={sidebarCollapsed} onToggleCollapse={() => setSidebarCollapsed((c) => !c)} />
</div>

{/* Main Content Area */}
<div className="flex-1 flex flex-col min-w-0 overflow-hidden relative border-l border-zinc-800/60 bg-zinc-950/50">
<div className="flex-1 flex flex-col min-w-0 overflow-hidden relative md:border-l border-zinc-800/60 bg-zinc-950/50">
<Topbar />
<main className="flex-1 overflow-y-auto p-6 md:p-8 lg:p-10 scrollbar-thin scrollbar-thumb-zinc-800 scrollbar-track-transparent">
<main className="flex-1 overflow-x-hidden overflow-y-auto p-3 sm:p-6 md:p-8 lg:p-10 pb-20 md:pb-8 lg:pb-10 scrollbar-thin scrollbar-thumb-zinc-800 scrollbar-track-transparent">
<div className="max-w-6xl mx-auto h-full">
<Outlet />
</div>
</main>
</div>

{/* Bottom Nav — visible only on mobile */}
<BottomNav />
</div>
);
}
68 changes: 68 additions & 0 deletions frontend/src/components/layout/BottomNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { NavLink, useLocation } from 'react-router-dom';
import {
LayoutDashboard,
CheckSquare,
Users,
Network,
FileText,
BarChart3,
} from 'lucide-react';
import { useAuth } from '../../store/AuthContext';
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

export function BottomNav() {
const { currentUser } = useAuth();
const location = useLocation();

const navItems = [
{ name: 'Dashboard', path: '/', icon: LayoutDashboard },
{ name: 'Tasks', path: '/tasks', icon: CheckSquare },
{ name: 'Team', path: '/team', icon: Users, hideFor: ['Pulse User'] },
{ name: 'Ops', path: '/operations', icon: Network, hideFor: ['Pulse User', 'Pulse Manager'] },
{ name: 'Insights', path: '/insights', icon: BarChart3, hideFor: ['Pulse User', 'Pulse Manager'] },
{ name: 'SOPs', path: '/templates', icon: FileText, hideFor: ['Pulse User'] },
];

const visibleItems = navItems.filter(
(item) => !item.hideFor?.includes(currentUser?.systemRole || '')
);

return (
<nav className="fixed bottom-0 left-0 right-0 z-50 md:hidden bg-[#09090b]/95 backdrop-blur-lg border-t border-zinc-800/60 pb-[env(safe-area-inset-bottom)]">
<div className="flex items-center justify-around h-14 px-2">
{visibleItems.map((item) => {
const isActive =
location.pathname === item.path ||
(item.path !== '/' && location.pathname.startsWith(item.path));

return (
<NavLink
key={item.path}
to={item.path}
className={cn(
'flex flex-col items-center justify-center gap-0.5 flex-1 py-1 rounded-lg transition-colors min-w-0',
isActive
? 'text-indigo-400'
: 'text-zinc-500 active:text-zinc-300'
)}
>
<item.icon
size={20}
strokeWidth={isActive ? 2.5 : 2}
className="shrink-0"
/>
<span className="text-[10px] font-medium leading-none truncate">
{item.name}
</span>
</NavLink>
);
})}
</div>
</nav>
);
}
4 changes: 2 additions & 2 deletions frontend/src/components/layout/Topbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function Topbar() {
pathParts.length > 0 ? pathParts[0].charAt(0).toUpperCase() + pathParts[0].slice(1) : 'Dashboard';

return (
<header className="h-12 w-full flex items-center justify-between px-6 border-b border-zinc-800/60 bg-zinc-950/80 backdrop-blur-md sticky top-0 z-10">
<header className="h-12 w-full flex items-center justify-between px-3 sm:px-6 border-b border-zinc-800/60 bg-zinc-950/80 backdrop-blur-md sticky top-0 z-10">
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-zinc-100 hidden sm:block">Pulse</span>
<span className="text-zinc-600 hidden sm:block">/</span>
Expand All @@ -25,7 +25,7 @@ export function Topbar() {
{currentUser?.name?.charAt(0) ?? '?'}
</AvatarFallback>
</Avatar>
<div className="flex flex-col items-start translate-y-[-1px] text-left">
<div className="flex-col items-start translate-y-[-1px] text-left hidden sm:flex">
<span className="text-xs font-medium leading-none text-zinc-200">{currentUser?.name}</span>
<span className="text-[10px] leading-none text-zinc-500 mt-1">{currentUser?.role}</span>
</div>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/shared/ScoreBreakdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export function ScoreBreakdown({ userId, date, periodType, open, onOpenChange }:
</div>
) : breakdown ? (
<>
<SheetHeader className="p-6 border-b border-zinc-800/80 bg-zinc-900/30">
<SheetHeader className="p-4 sm:p-6 border-b border-zinc-800/80 bg-zinc-900/30">
<div className="flex items-center gap-4">
<Activity className="text-indigo-400" size={24} />
<div className="flex flex-col">
Expand Down Expand Up @@ -110,7 +110,7 @@ export function ScoreBreakdown({ userId, date, periodType, open, onOpenChange }:
</div>
</SheetHeader>

<div className="flex-1 overflow-y-auto px-6 py-8 flex flex-col gap-8">
<div className="flex-1 overflow-y-auto px-4 sm:px-6 py-6 sm:py-8 flex flex-col gap-6 sm:gap-8">
<div className="grid grid-cols-3 gap-4">
<div className="bg-zinc-900/50 border border-zinc-800 p-4 rounded-xl flex flex-col gap-1">
<span className="text-[10px] text-zinc-500 font-mono uppercase tracking-widest font-bold">
Expand Down
18 changes: 9 additions & 9 deletions frontend/src/pages/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export function Dashboard() {
)}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h1 className="text-3xl font-semibold tracking-tight text-white">Execution Dashboard</h1>
<h1 className="text-2xl sm:text-3xl font-semibold tracking-tight text-white">Execution Dashboard</h1>
<p className="text-zinc-400 text-sm mt-1">
{currentUser.systemRole === 'Pulse User'
? 'Your performance overview.'
Expand Down Expand Up @@ -188,25 +188,25 @@ export function Dashboard() {
) : (
<>
<div className="grid gap-6 md:grid-cols-3">
<Card className="bg-[#141415] border-zinc-800 md:col-span-2 p-8 relative overflow-hidden flex items-center gap-12 group hover:border-zinc-700/50 transition-all">
<Card className="bg-[#141415] border-zinc-800 md:col-span-2 p-5 sm:p-8 relative overflow-hidden flex flex-col items-center md:flex-row md:items-center gap-6 md:gap-12 group hover:border-zinc-700/50 transition-all">
<div className="absolute top-0 right-0 w-64 h-64 bg-indigo-500/5 rounded-full blur-3xl -mr-32 -mt-32 pointer-events-none" />
<Gauge
value={combinedPct}
size={220}
size={typeof window !== 'undefined' && window.innerWidth < 768 ? 160 : 220}
label={`${periodType} KPI`}
mode="gradient"
showTicks
showGlow
/>
<div className="flex flex-col justify-center gap-6 flex-1">
<div>
<h2 className="text-2xl font-bold text-white tracking-tight">Execution Health</h2>
<p className="text-sm text-zinc-500 mt-2 max-w-sm leading-relaxed">
<div className="flex flex-col justify-center gap-4 sm:gap-6 flex-1 w-full">
<div className="text-center md:text-left">
<h2 className="text-xl sm:text-2xl font-bold text-white tracking-tight">Execution Health</h2>
<p className="text-xs sm:text-sm text-zinc-500 mt-2 max-w-sm leading-relaxed mx-auto md:mx-0">
Your overall performance rating based on {completedItems} completed tasks and team roll-ups for
this {periodType.toLowerCase()}.
</p>
</div>
<div className="flex items-center gap-6">
<div className="flex items-center justify-center md:justify-start gap-6">
<div className="flex flex-col">
<span className="text-[10px] text-zinc-500 font-mono uppercase tracking-widest font-bold">
Trend
Expand Down Expand Up @@ -280,7 +280,7 @@ export function Dashboard() {
</div>
<Calendar className="h-4 w-4 text-zinc-600" />
</CardHeader>
<CardContent className="h-[300px] w-full mt-4">
<CardContent className="h-[220px] sm:h-[300px] w-full mt-4">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={barChartData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#27272a" />
Expand Down
22 changes: 11 additions & 11 deletions frontend/src/pages/Insights.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ export function Insights() {
<div className="flex flex-col gap-4">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h1 className="text-3xl font-semibold tracking-tight text-white">Insights</h1>
<h1 className="text-2xl sm:text-3xl font-semibold tracking-tight text-white">Insights</h1>
<p className="text-zinc-400 text-sm mt-1">Organizational analytics and performance trends.</p>
</div>
<div className="flex flex-wrap items-center gap-2">
Expand Down Expand Up @@ -219,13 +219,13 @@ export function Insights() {
Clear
</Button>
</CardHeader>
<CardContent>
<CardContent className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow className="border-zinc-800">
<TableHead className="text-zinc-400">Name</TableHead>
<TableHead className="text-zinc-400">Role</TableHead>
<TableHead className="text-zinc-400">Branch</TableHead>
<TableHead className="text-zinc-400 hidden sm:table-cell">Role</TableHead>
<TableHead className="text-zinc-400 hidden sm:table-cell">Branch</TableHead>
<TableHead className="text-zinc-400 text-right">Own</TableHead>
<TableHead className="text-zinc-400 text-right">Team</TableHead>
<TableHead className="text-zinc-400 text-right">Combined</TableHead>
Expand All @@ -239,8 +239,8 @@ export function Insights() {
onClick={() => navigate(`/operations/${row.userId}`)}
>
<TableCell className="text-zinc-200 font-medium">{row.user?.name}</TableCell>
<TableCell className="text-zinc-400">{row.user?.role ?? '—'}</TableCell>
<TableCell className="text-zinc-400">{row.user?.branch ?? '—'}</TableCell>
<TableCell className="text-zinc-400 hidden sm:table-cell">{row.user?.role ?? '—'}</TableCell>
<TableCell className="text-zinc-400 hidden sm:table-cell">{row.user?.branch ?? '—'}</TableCell>
<TableCell className="text-right text-zinc-300">{Math.round((row.own_score ?? 0) * 100)}%</TableCell>
<TableCell className="text-right text-zinc-300">{Math.round((row.team_score ?? 0) * 100)}%</TableCell>
<TableCell className="text-right font-medium text-white">{Math.round((row.combined_score ?? 0) * 100)}%</TableCell>
Expand Down Expand Up @@ -303,7 +303,7 @@ export function Insights() {
</Card>
</div>

<div className="grid gap-4 md:grid-cols-4">
<div className="grid grid-cols-2 gap-3 sm:gap-4 md:grid-cols-4">
<Card className="bg-[#141415] border-zinc-800">
<CardHeader className="pb-2">
<CardTitle className="text-xs text-zinc-500 uppercase">Dept Avg</CardTitle>
Expand Down Expand Up @@ -513,17 +513,17 @@ export function Insights() {
<TableHeader>
<TableRow className="border-zinc-800">
<TableHead className="text-zinc-400">Item</TableHead>
<TableHead className="text-zinc-400">Template</TableHead>
<TableHead className="text-zinc-400">Department</TableHead>
<TableHead className="text-zinc-400 hidden sm:table-cell">Template</TableHead>
<TableHead className="text-zinc-400 hidden md:table-cell">Department</TableHead>
<TableHead className="text-zinc-400 text-right">Misses</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{mostMissed.map((m) => (
<TableRow key={m.checklist_item + m.template_title} className="border-zinc-800">
<TableCell className="text-zinc-200">{m.checklist_item}</TableCell>
<TableCell className="text-zinc-400">{m.template_title}</TableCell>
<TableCell className="text-zinc-400">{m.department}</TableCell>
<TableCell className="text-zinc-400 hidden sm:table-cell">{m.template_title}</TableCell>
<TableCell className="text-zinc-400 hidden md:table-cell">{m.department}</TableCell>
<TableCell className="text-right font-bold text-rose-400">{m.misses}</TableCell>
</TableRow>
))}
Expand Down
14 changes: 7 additions & 7 deletions frontend/src/pages/MyTasks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function MyTasks() {
return (
<div className="animate-in fade-in duration-500 flex flex-col gap-6 pb-10">
<div>
<h1 className="text-3xl font-semibold tracking-tight text-white">My Tasks</h1>
<h1 className="text-2xl sm:text-3xl font-semibold tracking-tight text-white">My Tasks</h1>
<p className="text-zinc-400 text-sm mt-1">
Manage your active operations and standard operating procedures.
</p>
Expand Down Expand Up @@ -188,8 +188,8 @@ function ChecklistRunner({

return (
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent className="bg-[#18181b] border-zinc-800 sm:max-w-md w-full p-0 flex flex-col h-full text-zinc-100">
<div className="p-6 border-b border-zinc-800">
<SheetContent className="bg-[#18181b] border-zinc-800 w-full sm:max-w-md p-0 flex flex-col h-full text-zinc-100">
<div className="p-4 sm:p-6 border-b border-zinc-800">
<SheetHeader className="text-left">
<div className="flex items-center justify-between">
<Badge variant="outline" className="text-zinc-400 border-zinc-700">
Expand Down Expand Up @@ -219,12 +219,12 @@ function ChecklistRunner({
</div>
</div>
</div>
<div className="flex-1 overflow-y-auto p-6 scrollbar-thin">
<div className="space-y-4">
<div className="flex-1 overflow-y-auto p-4 sm:p-6 scrollbar-thin">
<div className="space-y-3 sm:space-y-4">
{details.items.map((item) => (
<div
key={item.name}
className={`flex items-start gap-4 p-4 rounded-lg border ${
className={`flex items-start gap-3 sm:gap-4 p-3 sm:p-4 rounded-lg border ${
item.status === 'Completed'
? 'bg-indigo-500/5 border-indigo-500/20'
: 'bg-zinc-900/50 border-zinc-800'
Expand Down Expand Up @@ -257,7 +257,7 @@ function ChecklistRunner({
</div>
</div>
{!isReadOnly && (
<div className="p-6 border-t border-zinc-800 bg-zinc-950/50 sticky bottom-0">
<div className="p-4 sm:p-6 border-t border-zinc-800 bg-zinc-950/50 sticky bottom-0">
<Button className="w-full bg-indigo-600 hover:bg-indigo-700 text-white" onClick={completeRunHandler}>
Submit & Close Checklist
</Button>
Expand Down
Loading