- 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.
189 lines
5.0 KiB
JavaScript
189 lines
5.0 KiB
JavaScript
"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>
|
|
);
|
|
} |