feat: add task sets functionality with CRUD operations and UI integration
- Implemented NewTaskSetPage for creating task sets with templates. - Created TaskSetsPage for listing and filtering task sets. - Enhanced TaskTemplatesPage with navigation to task sets. - Updated ProjectTaskForm to support task set selection. - Modified PageHeader to support multiple action buttons. - Initialized database with task_sets and task_set_templates tables. - Added queries for task sets including creation, retrieval, and deletion. - Implemented applyTaskSetToProject function for bulk task creation. - Added test script for verifying task sets functionality.
This commit is contained in:
189
src/app/task-sets/page.js
Normal file
189
src/app/task-sets/page.js
Normal file
@@ -0,0 +1,189 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import { Card, CardHeader, CardContent } from "@/components/ui/Card";
|
||||
import Button from "@/components/ui/Button";
|
||||
import Badge from "@/components/ui/Badge";
|
||||
import PageContainer from "@/components/ui/PageContainer";
|
||||
import PageHeader from "@/components/ui/PageHeader";
|
||||
import { useTranslation } from "@/lib/i18n";
|
||||
|
||||
export default function TaskSetsPage() {
|
||||
const { t } = useTranslation();
|
||||
const [taskSets, setTaskSets] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [filter, setFilter] = useState("all");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchTaskSets = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/task-sets');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setTaskSets(data);
|
||||
} else {
|
||||
console.error('Failed to fetch task sets');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching task sets:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchTaskSets();
|
||||
}, []);
|
||||
|
||||
const filteredTaskSets = taskSets.filter(taskSet => {
|
||||
if (filter === "all") return true;
|
||||
return taskSet.task_category === filter;
|
||||
});
|
||||
|
||||
const getTaskCategoryBadge = (taskCategory) => {
|
||||
const colors = {
|
||||
design: "bg-blue-100 text-blue-800",
|
||||
construction: "bg-green-100 text-green-800"
|
||||
};
|
||||
|
||||
return (
|
||||
<Badge className={colors[taskCategory] || "bg-gray-100 text-gray-800"}>
|
||||
{taskCategory === "design" ? "Zadania projektowe" : taskCategory === "construction" ? "Zadania budowlane" : taskCategory}
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageHeader
|
||||
title="Zestawy zadań"
|
||||
description="Zarządzaj zestawami zadań"
|
||||
/>
|
||||
<div className="flex justify-center items-center h-64">
|
||||
<div className="text-gray-500">Ładowanie...</div>
|
||||
</div>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageHeader
|
||||
title="Zestawy zadań"
|
||||
description="Zarządzaj zestawami zadań"
|
||||
action={
|
||||
<Link href="/task-sets/new">
|
||||
<Button variant="primary" size="lg">
|
||||
<svg
|
||||
className="w-5 h-5 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M12 4v16m8-8H4"
|
||||
/>
|
||||
</svg>
|
||||
Nowy zestaw
|
||||
</Button>
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Filter buttons */}
|
||||
<div className="mb-6">
|
||||
<div className="flex space-x-2">
|
||||
{["all", "design", "construction"].map(type => (
|
||||
<Button
|
||||
key={type}
|
||||
variant={filter === type ? "primary" : "secondary"}
|
||||
size="sm"
|
||||
onClick={() => setFilter(type)}
|
||||
>
|
||||
{type === "all" ? "Wszystkie" :
|
||||
type === "design" ? "Projektowanie" :
|
||||
"Budowa"}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Task sets grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{filteredTaskSets.map((taskSet) => (
|
||||
<Card key={taskSet.set_id} className="hover:shadow-lg transition-shadow">
|
||||
<CardHeader>
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">
|
||||
{taskSet.name}
|
||||
</h3>
|
||||
{taskSet.description && (
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
{taskSet.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{getTaskCategoryBadge(taskSet.task_category)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
<div className="text-sm text-gray-600">
|
||||
<span className="font-medium">Szablony zadań:</span>{" "}
|
||||
{taskSet.templates?.length || 0}
|
||||
</div>
|
||||
|
||||
{taskSet.templates && taskSet.templates.length > 0 && (
|
||||
<div className="text-xs text-gray-500">
|
||||
<ul className="list-disc list-inside space-y-1">
|
||||
{taskSet.templates.slice(0, 3).map((template) => (
|
||||
<li key={template.task_id}>{template.name}</li>
|
||||
))}
|
||||
{taskSet.templates.length > 3 && (
|
||||
<li>...i {taskSet.templates.length - 3} więcej</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex space-x-2 pt-2">
|
||||
<Link href={`/task-sets/${taskSet.set_id}`}>
|
||||
<Button variant="secondary" size="sm">
|
||||
Edytuj
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href={`/task-sets/${taskSet.set_id}/apply`}>
|
||||
<Button variant="primary" size="sm">
|
||||
Zastosuj
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{filteredTaskSets.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<div className="text-gray-500 mb-4">
|
||||
{filter === "all"
|
||||
? "Brak zestawów zadań"
|
||||
: `Brak zestawów zadań dla typu "${filter}"`
|
||||
}
|
||||
</div>
|
||||
<Link href="/task-sets/new">
|
||||
<Button variant="primary">
|
||||
Utwórz pierwszy zestaw
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user