feat: updade workspace

This commit is contained in:
thanhnv
2026-07-11 15:56:31 +09:00
parent 4fc72332f5
commit 193a449829
120 changed files with 868 additions and 350 deletions
@@ -0,0 +1,17 @@
import { Outlet } from 'react-router-dom';
import { Header } from './Header.js';
import { Sidebar } from './Sidebar.js';
export function AppLayout(): JSX.Element {
return (
<div className="min-h-screen bg-gray-50">
<Sidebar />
<Header />
<main className="ml-64 pt-20">
<div className="mx-auto max-w-6xl px-6 py-6">
<Outlet />
</div>
</main>
</div>
);
}
@@ -0,0 +1,29 @@
import { Link } from 'react-router-dom';
import { Button } from '../ui/Button.js';
import { useAuth } from '../../hooks/useAuth.js';
export function Header(): JSX.Element {
const { user, logout } = useAuth();
return (
<header className="fixed left-64 right-0 top-0 z-10 border-b border-gray-200 bg-white px-6 py-4">
<div className="flex items-center justify-between gap-4">
<div className="flex flex-1 items-center gap-4">
<select className="rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
<option>All FPT</option>
</select>
<input
className="w-full max-w-xl rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Search"
/>
</div>
<Link to="/objectives/new">
<Button>NEW OKR</Button>
</Link>
<div className="text-sm text-gray-500">{user?.name}</div>
<Button variant="secondary" onClick={logout}>
Sign out
</Button>
</div>
</header>
);
}
@@ -0,0 +1,35 @@
import { Link, useLocation } from 'react-router-dom';
const links = [
{ label: 'My OKRs', href: '/' },
{ label: 'I created', href: '/' },
{ label: 'I manage', href: '/' },
{ label: 'Members', href: '/' },
{ label: 'OKR - all', href: '/' },
];
export function Sidebar(): JSX.Element {
const location = useLocation();
return (
<aside className="fixed left-0 top-0 h-screen w-64 border-r border-gray-200 bg-white p-5">
<div className="mb-8 text-xl font-semibold text-blue-600">FOKR</div>
<div className="mb-4 text-sm font-medium text-gray-500">2026</div>
<nav className="space-y-1">
{links.map((link) => {
const active = location.pathname === link.href && link.label === 'My OKRs';
return (
<Link
key={link.label}
to={link.href}
className={`block rounded-lg px-3 py-2 text-sm transition-colors ${
active ? 'bg-blue-50 font-medium text-blue-600' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-800'
}`}
>
{link.label}
</Link>
);
})}
</nav>
</aside>
);
}
@@ -0,0 +1,21 @@
import type { ObjectiveStatus } from '../../types/okr.types.js';
interface BadgeProps {
status: ObjectiveStatus;
}
const labels: Record<ObjectiveStatus, string> = {
NOT_STARTED: 'Not Started',
IN_PROGRESS: 'In Progress',
COMPLETED: 'Completed',
};
const classes: Record<ObjectiveStatus, string> = {
NOT_STARTED: 'bg-gray-100 text-gray-600',
IN_PROGRESS: 'bg-orange-100 text-orange-700',
COMPLETED: 'bg-green-100 text-green-700',
};
export function Badge({ status }: BadgeProps): JSX.Element {
return <span className={`rounded-full px-2 py-1 text-xs font-medium ${classes[status]}`}>{labels[status]}</span>;
}
@@ -0,0 +1,14 @@
import type { ButtonHTMLAttributes } from 'react';
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary';
}
export function Button({ className = '', variant = 'primary', ...props }: ButtonProps): JSX.Element {
const base = 'inline-flex items-center justify-center rounded-lg px-4 py-2 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-60';
const variants = {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'border border-gray-300 text-gray-700 hover:bg-gray-50',
};
return <button className={`${base} ${variants[variant]} ${className}`} {...props} />;
}
@@ -0,0 +1,12 @@
interface ProgressBarProps {
value: number;
}
export function ProgressBar({ value }: ProgressBarProps): JSX.Element {
const width = `${Math.min(Math.max(value, 0), 100)}%`;
return (
<div className="w-full rounded-full bg-gray-200 h-2">
<div className="h-2 rounded-full bg-blue-600 transition-all" style={{ width }} />
</div>
);
}