update first - 84
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user