Add right sidebar to board detail page
This commit is contained in:
parent
3711b0888d
commit
9958443c14
11 changed files with 226 additions and 111 deletions
16
frontend/package-lock.json
generated
16
frontend/package-lock.json
generated
|
|
@ -23,6 +23,7 @@
|
||||||
"@testing-library/jest-dom": "^6.1.5",
|
"@testing-library/jest-dom": "^6.1.5",
|
||||||
"@testing-library/react": "^14.1.2",
|
"@testing-library/react": "^14.1.2",
|
||||||
"@testing-library/user-event": "^14.5.1",
|
"@testing-library/user-event": "^14.5.1",
|
||||||
|
"@types/node": "^25.5.0",
|
||||||
"@types/react": "^18.3.28",
|
"@types/react": "^18.3.28",
|
||||||
"@types/react-dom": "^18.3.7",
|
"@types/react-dom": "^18.3.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
||||||
|
|
@ -1614,6 +1615,15 @@
|
||||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "25.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
|
||||||
|
"integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~7.18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/prop-types": {
|
"node_modules/@types/prop-types": {
|
||||||
"version": "15.7.15",
|
"version": "15.7.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||||
|
|
@ -7018,6 +7028,12 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "7.18.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||||
|
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/universalify": {
|
"node_modules/universalify": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
"@testing-library/jest-dom": "^6.1.5",
|
"@testing-library/jest-dom": "^6.1.5",
|
||||||
"@testing-library/react": "^14.1.2",
|
"@testing-library/react": "^14.1.2",
|
||||||
"@testing-library/user-event": "^14.5.1",
|
"@testing-library/user-event": "^14.5.1",
|
||||||
|
"@types/node": "^25.5.0",
|
||||||
"@types/react": "^18.3.28",
|
"@types/react": "^18.3.28",
|
||||||
"@types/react-dom": "^18.3.7",
|
"@types/react-dom": "^18.3.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import { Boards } from './pages/Boards';
|
||||||
import { BoardCreate } from './pages/BoardCreate';
|
import { BoardCreate } from './pages/BoardCreate';
|
||||||
import { BoardEdit } from './pages/BoardEdit';
|
import { BoardEdit } from './pages/BoardEdit';
|
||||||
import { BoardDetail } from './pages/BoardDetail';
|
import { BoardDetail } from './pages/BoardDetail';
|
||||||
|
import { BoardEpics } from './pages/BoardEpics';
|
||||||
import { CardDetail } from './pages/CardDetail';
|
import { CardDetail } from './pages/CardDetail';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
|
|
@ -35,7 +36,7 @@ const App = () => {
|
||||||
<ModalProvider>
|
<ModalProvider>
|
||||||
<div className="min-h-screen bg-gray-900 text-gray-100">
|
<div className="min-h-screen bg-gray-900 text-gray-100">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<main className="flex-1 p-8 mx-auto w-full max-w-7xl">
|
<main>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
path="/"
|
path="/"
|
||||||
|
|
@ -84,6 +85,14 @@ const App = () => {
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="/boards/:id/epics"
|
||||||
|
element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<BoardEpics />
|
||||||
|
</ProtectedRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/boards/:id/cards/:cardId"
|
path="/boards/:id/cards/:cardId"
|
||||||
element={
|
element={
|
||||||
|
|
|
||||||
40
frontend/src/components/BoardSidebar.tsx
Normal file
40
frontend/src/components/BoardSidebar.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
interface BoardSidebarProps {
|
||||||
|
boardId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BoardSidebar({ boardId }: BoardSidebarProps) {
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const menuItems = [
|
||||||
|
{ id: 'epics', label: 'Epics', icon: '📋', path: `/boards/${boardId}/epics` },
|
||||||
|
{ id: 'history', label: 'History', icon: '📜', path: `/boards/${boardId}/history` },
|
||||||
|
{ id: 'documents', label: 'Documents', icon: '📄', path: `/boards/${boardId}/documents` },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-64 bg-gray-800 rounded-lg p-4 border border-gray-700 h-fit sticky top-4">
|
||||||
|
<h3 className="text-white font-bold text-lg mb-4">Board Menu</h3>
|
||||||
|
<nav className="space-y-2">
|
||||||
|
{menuItems.map((item) => {
|
||||||
|
const isActive = location.pathname === item.path;
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={item.id}
|
||||||
|
to={item.path}
|
||||||
|
className={`flex items-center gap-3 px-3 py-2 rounded-md transition-colors ${
|
||||||
|
isActive
|
||||||
|
? 'bg-blue-600 text-white'
|
||||||
|
: 'text-gray-300 hover:bg-gray-700 hover:text-white'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span className="text-lg">{item.icon}</span>
|
||||||
|
<span className="font-medium">{item.label}</span>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
5
frontend/src/components/NarrowPageLayout.tsx
Normal file
5
frontend/src/components/NarrowPageLayout.tsx
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export const NarrowPageLayout = ({ children }: { children: ReactNode }) => {
|
||||||
|
return <div className="flex-1 p-8 mx-auto w-full max-w-7xl">{children}</div>;
|
||||||
|
};
|
||||||
5
frontend/src/components/WidePageLayout.tsx
Normal file
5
frontend/src/components/WidePageLayout.tsx
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export const WidePageLayout = ({ children }: { children: ReactNode }) => {
|
||||||
|
return <div className="flex-1 p-8 mx-auto w-full max-w-7xl">{children}</div>;
|
||||||
|
};
|
||||||
|
|
@ -5,6 +5,7 @@ import { useListMutations } from '../hooks/useListMutations';
|
||||||
import { SortableKanbanColumn } from '../components/kanban/SortableKanbanColumn';
|
import { SortableKanbanColumn } from '../components/kanban/SortableKanbanColumn';
|
||||||
import { CreateListModal } from '../components/kanban/CreateListModal';
|
import { CreateListModal } from '../components/kanban/CreateListModal';
|
||||||
import { CardPreviewModal } from '../components/CardPreviewModal';
|
import { CardPreviewModal } from '../components/CardPreviewModal';
|
||||||
|
import { BoardSidebar } from '../components/BoardSidebar';
|
||||||
import { useModal } from '../context/modals/useModal';
|
import { useModal } from '../context/modals/useModal';
|
||||||
import {
|
import {
|
||||||
DndContext,
|
DndContext,
|
||||||
|
|
@ -20,6 +21,7 @@ import {
|
||||||
import { SortableContext, horizontalListSortingStrategy } from '@dnd-kit/sortable';
|
import { SortableContext, horizontalListSortingStrategy } from '@dnd-kit/sortable';
|
||||||
import { Card as CardType, ListWithCards } from '../types/kanban';
|
import { Card as CardType, ListWithCards } from '../types/kanban';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { WidePageLayout } from '@/components/WidePageLayout';
|
||||||
|
|
||||||
export function BoardDetail() {
|
export function BoardDetail() {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
|
|
@ -65,12 +67,8 @@ export function BoardDetail() {
|
||||||
const handleDragOver = (event: DragOverEvent) => {
|
const handleDragOver = (event: DragOverEvent) => {
|
||||||
const { active, over } = event;
|
const { active, over } = event;
|
||||||
|
|
||||||
// console.log('---handleDragOver', event);
|
|
||||||
if (!over) return;
|
if (!over) return;
|
||||||
|
|
||||||
// const activeId = parseInt(active.id as string);
|
|
||||||
// const overId = parseInt(over.id as string);
|
|
||||||
|
|
||||||
const overIdStr = (over.id as string).split('_')[1];
|
const overIdStr = (over.id as string).split('_')[1];
|
||||||
const overId = parseInt(overIdStr, 10);
|
const overId = parseInt(overIdStr, 10);
|
||||||
const activeIdStr = (active.id as string).split('_')[1];
|
const activeIdStr = (active.id as string).split('_')[1];
|
||||||
|
|
@ -105,8 +103,6 @@ export function BoardDetail() {
|
||||||
|
|
||||||
// Handle column reordering
|
// Handle column reordering
|
||||||
if (activeType === 'COLUMN') {
|
if (activeType === 'COLUMN') {
|
||||||
// todo find over column id,
|
|
||||||
|
|
||||||
let overListIndex = -1;
|
let overListIndex = -1;
|
||||||
const activeList = board.lists.find((l) => l.id === activeId);
|
const activeList = board.lists.find((l) => l.id === activeId);
|
||||||
if (overType === 'CARD') {
|
if (overType === 'CARD') {
|
||||||
|
|
@ -118,12 +114,7 @@ export function BoardDetail() {
|
||||||
overListIndex = board.lists.findIndex((l) => l.id === overId);
|
overListIndex = board.lists.findIndex((l) => l.id === overId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log('-------active.id', active.id)
|
|
||||||
// console.log('-------overType.id', overType)
|
|
||||||
// console.log('-------overListIndex', overListIndex)
|
|
||||||
|
|
||||||
const activeListIndex = board.lists.findIndex((l) => l.id === activeId);
|
const activeListIndex = board.lists.findIndex((l) => l.id === activeId);
|
||||||
// overListIndex = board.lists.findIndex((l) => l.id === overId);
|
|
||||||
|
|
||||||
if (activeListIndex === -1 || overListIndex === -1 || !activeList) return;
|
if (activeListIndex === -1 || overListIndex === -1 || !activeList) return;
|
||||||
|
|
||||||
|
|
@ -133,15 +124,6 @@ export function BoardDetail() {
|
||||||
reorderedLists.splice(overListIndex, 0, movedList);
|
reorderedLists.splice(overListIndex, 0, movedList);
|
||||||
|
|
||||||
await updateList(activeList.id, { name: activeList.name, pos: overListIndex });
|
await updateList(activeList.id, { name: activeList.name, pos: overListIndex });
|
||||||
|
|
||||||
// // Update all list positions
|
|
||||||
// for (let i = 0; i < reorderedLists.length; i++) {
|
|
||||||
// const list = reorderedLists[i];
|
|
||||||
// if (list.pos !== i) {
|
|
||||||
// await updateList(list.id, { name: list.name, pos: i });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -236,71 +218,81 @@ export function BoardDetail() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex justify-between items-start">
|
<WidePageLayout>
|
||||||
<div>
|
<div className="flex justify-between items-start">
|
||||||
<Link to="/boards" className="text-gray-400 hover:text-white transition-colors text-sm">
|
<div>
|
||||||
← Back to Boards
|
<Link to="/boards" className="text-gray-400 hover:text-white transition-colors text-sm">
|
||||||
</Link>
|
← Back to Boards
|
||||||
<h1 className="text-3xl font-bold text-white mt-2">{board.name}</h1>
|
</Link>
|
||||||
{board.description && <p className="text-gray-400 mt-1">{board.description}</p>}
|
<h1 className="text-3xl font-bold text-white mt-2">{board.name}</h1>
|
||||||
|
{board.description && <p className="text-gray-400 mt-1">{board.description}</p>}
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Link
|
||||||
|
to={`/boards/${id}/edit`}
|
||||||
|
className="bg-gray-700 hover:bg-gray-600 text-white font-medium py-2 px-4 rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
Edit Board
|
||||||
|
</Link>
|
||||||
|
<button
|
||||||
|
onClick={handleAddList}
|
||||||
|
className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
+ Add List
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
</WidePageLayout>
|
||||||
<Link
|
|
||||||
to={`/boards/${id}/edit`}
|
<div className="px-4 sm:px-6 lg:px-8 max-w-7xl mx-auto">
|
||||||
className="bg-gray-700 hover:bg-gray-600 text-white font-medium py-2 px-4 rounded-lg transition-colors"
|
<div className="flex justify-between items-start gap-6">
|
||||||
>
|
<div className="flex-1">
|
||||||
Edit Board
|
<DndContext
|
||||||
</Link>
|
sensors={sensors}
|
||||||
<button
|
collisionDetection={closestCenter}
|
||||||
onClick={handleAddList}
|
onDragStart={handleDragStart}
|
||||||
className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-lg transition-colors"
|
onDragOver={handleDragOver}
|
||||||
>
|
onDragEnd={handleDragEnd}
|
||||||
+ Add List
|
>
|
||||||
</button>
|
<SortableContext
|
||||||
|
items={board.lists.map((list) => `COLUMN_${list.id}`)}
|
||||||
|
strategy={horizontalListSortingStrategy}
|
||||||
|
>
|
||||||
|
<div className="flex gap-4 overflow-x-auto pb-4">
|
||||||
|
{board.lists.map((list) => (
|
||||||
|
<SortableKanbanColumn
|
||||||
|
key={list.id}
|
||||||
|
list={list}
|
||||||
|
cards={list.cards}
|
||||||
|
onOpenCardModal={handleOpenCardModal}
|
||||||
|
onCardCreate={handleAddCard(list.id)}
|
||||||
|
onListEdit={(name) => handleEditList(list.id, name)}
|
||||||
|
onListDelete={() => handleDeleteList(list.id)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</SortableContext>
|
||||||
|
|
||||||
|
<DragOverlay>
|
||||||
|
{activeCard ? (
|
||||||
|
<div className="bg-gray-700 rounded-lg p-4 cursor-grabbing border border-gray-600 shadow-lg opacity-80">
|
||||||
|
<h3 className="text-white font-medium">{activeCard.name}</h3>
|
||||||
|
</div>
|
||||||
|
) : activeList ? (
|
||||||
|
<div className="bg-gray-800 rounded-lg p-4 cursor-grabbing border border-gray-700 shadow-lg opacity-80 min-w-[300px] max-w-[300px]">
|
||||||
|
<h2 className="text-white font-bold text-lg">{activeList.name}</h2>
|
||||||
|
<span className="bg-gray-600 text-gray-300 text-xs px-2 py-1 rounded-full inline-block mt-2">
|
||||||
|
{activeList.cards.length} cards
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</DragOverlay>
|
||||||
|
</DndContext>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BoardSidebar boardId={id || ''} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DndContext
|
|
||||||
sensors={sensors}
|
|
||||||
collisionDetection={closestCenter}
|
|
||||||
onDragStart={handleDragStart}
|
|
||||||
onDragOver={handleDragOver}
|
|
||||||
onDragEnd={handleDragEnd}
|
|
||||||
>
|
|
||||||
<SortableContext
|
|
||||||
items={board.lists.map((list) => `COLUMN_${list.id}`)}
|
|
||||||
strategy={horizontalListSortingStrategy}
|
|
||||||
>
|
|
||||||
<div className="flex gap-4 overflow-x-auto pb-4">
|
|
||||||
{board.lists.map((list) => (
|
|
||||||
<SortableKanbanColumn
|
|
||||||
key={list.id}
|
|
||||||
list={list}
|
|
||||||
cards={list.cards}
|
|
||||||
onOpenCardModal={handleOpenCardModal}
|
|
||||||
onCardCreate={handleAddCard(list.id)}
|
|
||||||
onListEdit={(name) => handleEditList(list.id, name)}
|
|
||||||
onListDelete={() => handleDeleteList(list.id)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</SortableContext>
|
|
||||||
|
|
||||||
<DragOverlay>
|
|
||||||
{activeCard ? (
|
|
||||||
<div className="bg-gray-700 rounded-lg p-4 cursor-grabbing border border-gray-600 shadow-lg opacity-80">
|
|
||||||
<h3 className="text-white font-medium">{activeCard.name}</h3>
|
|
||||||
</div>
|
|
||||||
) : activeList ? (
|
|
||||||
<div className="bg-gray-800 rounded-lg p-4 cursor-grabbing border border-gray-700 shadow-lg opacity-80 min-w-[300px] max-w-[300px]">
|
|
||||||
<h2 className="text-white font-bold text-lg">{activeList.name}</h2>
|
|
||||||
<span className="bg-gray-600 text-gray-300 text-xs px-2 py-1 rounded-full inline-block mt-2">
|
|
||||||
{activeList.cards.length} cards
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</DragOverlay>
|
|
||||||
</DndContext>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
35
frontend/src/pages/BoardEpics.tsx
Normal file
35
frontend/src/pages/BoardEpics.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { useParams, Link } from 'react-router-dom';
|
||||||
|
import { WidePageLayout } from '../components/WidePageLayout';
|
||||||
|
|
||||||
|
export function BoardEpics() {
|
||||||
|
const { id } = useParams<{ id: string }>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<WidePageLayout>
|
||||||
|
<div>
|
||||||
|
<Link
|
||||||
|
to={`/boards/${id}`}
|
||||||
|
className="text-gray-400 hover:text-white transition-colors text-sm"
|
||||||
|
>
|
||||||
|
← Back to Board
|
||||||
|
</Link>
|
||||||
|
<h1 className="text-3xl font-bold text-white mt-2">Epics</h1>
|
||||||
|
<p className="text-gray-400 mt-1">Manage and view epics for this board</p>
|
||||||
|
</div>
|
||||||
|
</WidePageLayout>
|
||||||
|
|
||||||
|
<WidePageLayout>
|
||||||
|
<div className="bg-gray-800 rounded-lg p-8 border border-gray-700">
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<div className="text-6xl mb-4">📋</div>
|
||||||
|
<h2 className="text-xl font-bold text-white mb-2">Epics Coming Soon</h2>
|
||||||
|
<p className="text-gray-400">
|
||||||
|
This page will allow you to create and manage epics for your board.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</WidePageLayout>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,42 +1,45 @@
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useBoards } from '../hooks/useBoards';
|
import { useBoards } from '../hooks/useBoards';
|
||||||
import { BoardCard } from '../components/kanban/BoardCard';
|
import { BoardCard } from '../components/kanban/BoardCard';
|
||||||
|
import { NarrowPageLayout } from '@/components/NarrowPageLayout';
|
||||||
|
|
||||||
export function Boards() {
|
export function Boards() {
|
||||||
const { boards, deleteBoard } = useBoards();
|
const { boards, deleteBoard } = useBoards();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<NarrowPageLayout>
|
||||||
<div className="flex justify-between items-center">
|
<div className="space-y-6">
|
||||||
<div>
|
<div className="flex justify-between items-center">
|
||||||
<h1 className="text-3xl font-bold text-white mb-2">My Boards</h1>
|
<div>
|
||||||
<p className="text-gray-400">Manage your Kanban boards</p>
|
<h1 className="text-3xl font-bold text-white mb-2">My Boards</h1>
|
||||||
</div>
|
<p className="text-gray-400">Manage your Kanban boards</p>
|
||||||
<Link
|
</div>
|
||||||
to="/boards/new"
|
|
||||||
className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-lg transition-colors"
|
|
||||||
>
|
|
||||||
+ Create Board
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{boards.length === 0 ? (
|
|
||||||
<div className="text-center py-16">
|
|
||||||
<p className="text-gray-400 text-lg mb-4">No boards yet</p>
|
|
||||||
<Link
|
<Link
|
||||||
to="/boards/new"
|
to="/boards/new"
|
||||||
className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-6 rounded-lg transition-colors inline-block"
|
className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-lg transition-colors"
|
||||||
>
|
>
|
||||||
Create your first board
|
+ Create Board
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
{boards.length === 0 ? (
|
||||||
{boards.map((board) => (
|
<div className="text-center py-16">
|
||||||
<BoardCard key={board.id} board={board} onDelete={deleteBoard} />
|
<p className="text-gray-400 text-lg mb-4">No boards yet</p>
|
||||||
))}
|
<Link
|
||||||
</div>
|
to="/boards/new"
|
||||||
)}
|
className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-6 rounded-lg transition-colors inline-block"
|
||||||
</div>
|
>
|
||||||
|
Create your first board
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
{boards.map((board) => (
|
||||||
|
<BoardCard key={board.id} board={board} onDelete={deleteBoard} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</NarrowPageLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,10 @@
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowSyntheticDefaultImports": true
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"include": ["vite.config.ts"]
|
"include": ["vite.config.ts"]
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,15 @@
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, './src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
proxy: {
|
proxy: {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue