rebranded to BIT
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Tesseract - Task Decomposition Engine</title>
|
||||
<title>Break It Down - Task Decomposition Engine</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "tesseract-frontend",
|
||||
"name": "bit-frontend",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "tesseract-frontend",
|
||||
"name": "bit-frontend",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"lucide-react": "^0.303.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "tesseract-frontend",
|
||||
"name": "bit-frontend",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
|
||||
@@ -11,8 +11,8 @@ function App() {
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-cyber-orange">
|
||||
TESSERACT
|
||||
<span className="ml-3 text-sm text-gray-500">Task Decomposition Engine</span>
|
||||
Break It Down
|
||||
<span className="ml-3 text-sm text-gray-500">BIT - Task Decomposition Engine</span>
|
||||
<span className="ml-2 text-xs text-gray-600">v{import.meta.env.VITE_APP_VERSION || '0.1.6'}</span>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Plus, Upload, Trash2 } from 'lucide-react'
|
||||
import { getProjects, createProject, deleteProject, importJSON } from '../utils/api'
|
||||
import { Plus, Upload, Trash2, Archive, ArchiveRestore } from 'lucide-react'
|
||||
import { getProjects, createProject, deleteProject, importJSON, archiveProject, unarchiveProject } from '../utils/api'
|
||||
|
||||
function ProjectList() {
|
||||
const [projects, setProjects] = useState([])
|
||||
@@ -12,15 +12,22 @@ function ProjectList() {
|
||||
const [newProjectDesc, setNewProjectDesc] = useState('')
|
||||
const [importJSON_Text, setImportJSONText] = useState('')
|
||||
const [error, setError] = useState('')
|
||||
const [activeTab, setActiveTab] = useState('active') // 'active', 'archived', 'all'
|
||||
const navigate = useNavigate()
|
||||
|
||||
useEffect(() => {
|
||||
loadProjects()
|
||||
}, [])
|
||||
}, [activeTab])
|
||||
|
||||
const loadProjects = async () => {
|
||||
try {
|
||||
const data = await getProjects()
|
||||
setLoading(true)
|
||||
let archivedFilter = null
|
||||
if (activeTab === 'active') archivedFilter = false
|
||||
if (activeTab === 'archived') archivedFilter = true
|
||||
// 'all' tab uses null to get all projects
|
||||
|
||||
const data = await getProjects(archivedFilter)
|
||||
setProjects(data)
|
||||
} catch (err) {
|
||||
setError(err.message)
|
||||
@@ -72,13 +79,33 @@ function ProjectList() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleArchiveProject = async (projectId, e) => {
|
||||
e.stopPropagation()
|
||||
try {
|
||||
await archiveProject(projectId)
|
||||
setProjects(projects.filter(p => p.id !== projectId))
|
||||
} catch (err) {
|
||||
setError(err.message)
|
||||
}
|
||||
}
|
||||
|
||||
const handleUnarchiveProject = async (projectId, e) => {
|
||||
e.stopPropagation()
|
||||
try {
|
||||
await unarchiveProject(projectId)
|
||||
setProjects(projects.filter(p => p.id !== projectId))
|
||||
} catch (err) {
|
||||
setError(err.message)
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return <div className="text-center text-gray-400 py-12">Loading...</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-8">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h2 className="text-3xl font-bold text-gray-100">Projects</h2>
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
@@ -98,6 +125,40 @@ function ProjectList() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="flex gap-1 mb-6 border-b border-cyber-orange/20">
|
||||
<button
|
||||
onClick={() => setActiveTab('active')}
|
||||
className={`px-4 py-2 font-medium transition-colors ${
|
||||
activeTab === 'active'
|
||||
? 'text-cyber-orange border-b-2 border-cyber-orange'
|
||||
: 'text-gray-400 hover:text-gray-200'
|
||||
}`}
|
||||
>
|
||||
Active
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('archived')}
|
||||
className={`px-4 py-2 font-medium transition-colors ${
|
||||
activeTab === 'archived'
|
||||
? 'text-cyber-orange border-b-2 border-cyber-orange'
|
||||
: 'text-gray-400 hover:text-gray-200'
|
||||
}`}
|
||||
>
|
||||
Archived
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('all')}
|
||||
className={`px-4 py-2 font-medium transition-colors ${
|
||||
activeTab === 'all'
|
||||
? 'text-cyber-orange border-b-2 border-cyber-orange'
|
||||
: 'text-gray-400 hover:text-gray-200'
|
||||
}`}
|
||||
>
|
||||
All
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="mb-4 p-4 bg-red-900/30 border border-red-500/50 rounded text-red-300">
|
||||
{error}
|
||||
@@ -106,8 +167,14 @@ function ProjectList() {
|
||||
|
||||
{projects.length === 0 ? (
|
||||
<div className="text-center py-16 text-gray-500">
|
||||
<p className="text-xl mb-2">No projects yet</p>
|
||||
<p className="text-sm">Create a new project or import from JSON</p>
|
||||
<p className="text-xl mb-2">
|
||||
{activeTab === 'archived' ? 'No archived projects' : 'No projects yet'}
|
||||
</p>
|
||||
<p className="text-sm">
|
||||
{activeTab === 'archived'
|
||||
? 'Archive projects to keep them out of your active workspace'
|
||||
: 'Create a new project or import from JSON'}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
@@ -115,18 +182,45 @@ function ProjectList() {
|
||||
<div
|
||||
key={project.id}
|
||||
onClick={() => navigate(`/project/${project.id}`)}
|
||||
className="p-6 bg-cyber-darkest border border-cyber-orange/30 rounded-lg hover:border-cyber-orange hover:shadow-cyber transition-all cursor-pointer group"
|
||||
className={`p-6 bg-cyber-darkest border rounded-lg hover:border-cyber-orange hover:shadow-cyber transition-all cursor-pointer group ${
|
||||
project.is_archived
|
||||
? 'border-gray-700 opacity-75'
|
||||
: 'border-cyber-orange/30'
|
||||
}`}
|
||||
>
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<h3 className="text-xl font-semibold text-gray-100 group-hover:text-cyber-orange transition-colors">
|
||||
{project.name}
|
||||
{project.is_archived && (
|
||||
<span className="ml-2 text-xs text-gray-500">(archived)</span>
|
||||
)}
|
||||
</h3>
|
||||
<button
|
||||
onClick={(e) => handleDeleteProject(project.id, e)}
|
||||
className="text-gray-600 hover:text-red-400 transition-colors"
|
||||
>
|
||||
<Trash2 size={18} />
|
||||
</button>
|
||||
<div className="flex gap-2">
|
||||
{project.is_archived ? (
|
||||
<button
|
||||
onClick={(e) => handleUnarchiveProject(project.id, e)}
|
||||
className="text-gray-600 hover:text-cyber-orange transition-colors"
|
||||
title="Unarchive project"
|
||||
>
|
||||
<ArchiveRestore size={18} />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={(e) => handleArchiveProject(project.id, e)}
|
||||
className="text-gray-600 hover:text-yellow-400 transition-colors"
|
||||
title="Archive project"
|
||||
>
|
||||
<Archive size={18} />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={(e) => handleDeleteProject(project.id, e)}
|
||||
className="text-gray-600 hover:text-red-400 transition-colors"
|
||||
title="Delete project"
|
||||
>
|
||||
<Trash2 size={18} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{project.description && (
|
||||
<p className="text-gray-400 text-sm line-clamp-2">{project.description}</p>
|
||||
|
||||
@@ -22,7 +22,14 @@ async function fetchAPI(endpoint, options = {}) {
|
||||
}
|
||||
|
||||
// Projects
|
||||
export const getProjects = () => fetchAPI('/projects');
|
||||
export const getProjects = (archived = null) => {
|
||||
const params = new URLSearchParams();
|
||||
if (archived !== null) {
|
||||
params.append('archived', archived);
|
||||
}
|
||||
const queryString = params.toString();
|
||||
return fetchAPI(`/projects${queryString ? `?${queryString}` : ''}`);
|
||||
};
|
||||
export const getProject = (id) => fetchAPI(`/projects/${id}`);
|
||||
export const createProject = (data) => fetchAPI('/projects', {
|
||||
method: 'POST',
|
||||
@@ -33,6 +40,8 @@ export const updateProject = (id, data) => fetchAPI(`/projects/${id}`, {
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
export const deleteProject = (id) => fetchAPI(`/projects/${id}`, { method: 'DELETE' });
|
||||
export const archiveProject = (id) => updateProject(id, { is_archived: true });
|
||||
export const unarchiveProject = (id) => updateProject(id, { is_archived: false });
|
||||
|
||||
// Tasks
|
||||
export const getProjectTasks = (projectId) => fetchAPI(`/projects/${projectId}/tasks`);
|
||||
|
||||
Reference in New Issue
Block a user