feat: add task_category field to tasks with validation and update related forms

This commit is contained in:
2025-10-07 22:22:10 +02:00
parent 952caf10d1
commit a6ef325813
6 changed files with 72 additions and 13 deletions

View File

@@ -30,19 +30,23 @@ async function getTaskHandler(req, { params }) {
async function updateTaskHandler(req, { params }) { async function updateTaskHandler(req, { params }) {
const { id } = await params; const { id } = await params;
try { try {
const { name, max_wait_days, description } = await req.json(); const { name, max_wait_days, description, task_category } = await req.json();
if (!name) { if (!name) {
return NextResponse.json({ error: "Name is required" }, { status: 400 }); return NextResponse.json({ error: "Name is required" }, { status: 400 });
} }
if (task_category && !['design', 'construction'].includes(task_category)) {
return NextResponse.json({ error: "Invalid task_category (must be design or construction)" }, { status: 400 });
}
const result = db const result = db
.prepare( .prepare(
`UPDATE tasks `UPDATE tasks
SET name = ?, max_wait_days = ?, description = ? SET name = ?, max_wait_days = ?, description = ?, task_category = ?
WHERE task_id = ? AND is_standard = 1` WHERE task_id = ? AND is_standard = 1`
) )
.run(name, max_wait_days || 0, description || null, id); .run(name, max_wait_days || 0, description || null, task_category, id);
if (result.changes === 0) { if (result.changes === 0) {
return NextResponse.json( return NextResponse.json(

View File

@@ -5,18 +5,22 @@ import { getAllTaskTemplates } from "@/lib/queries/tasks";
// POST: create new template // POST: create new template
async function createTaskHandler(req) { async function createTaskHandler(req) {
const { name, max_wait_days, description } = await req.json(); const { name, max_wait_days, description, task_category } = await req.json();
if (!name) { if (!name) {
return NextResponse.json({ error: "Name is required" }, { status: 400 }); return NextResponse.json({ error: "Name is required" }, { status: 400 });
} }
if (!task_category || !['design', 'construction'].includes(task_category)) {
return NextResponse.json({ error: "Valid task_category is required (design or construction)" }, { status: 400 });
}
db.prepare( db.prepare(
` `
INSERT INTO tasks (name, max_wait_days, description, is_standard) INSERT INTO tasks (name, max_wait_days, description, is_standard, task_category)
VALUES (?, ?, ?, 1) VALUES (?, ?, ?, 1, ?)
` `
).run(name, max_wait_days || 0, description || null); ).run(name, max_wait_days || 0, description || null, task_category);
return NextResponse.json({ success: true }); return NextResponse.json({ success: true });
} }

View File

@@ -16,7 +16,7 @@ export default function NewTaskSetPage() {
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
name: "", name: "",
description: "", description: "",
project_type: "design", task_category: "design",
selectedTemplates: [] selectedTemplates: []
}); });
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
@@ -61,7 +61,7 @@ export default function NewTaskSetPage() {
body: JSON.stringify({ body: JSON.stringify({
name: formData.name.trim(), name: formData.name.trim(),
description: formData.description.trim(), description: formData.description.trim(),
project_type: formData.project_type task_category: formData.task_category
}) })
}); });

View File

@@ -34,6 +34,19 @@ export default function TaskTemplatesPage() {
fetchTemplates(); fetchTemplates();
}, []); }, []);
const getTaskCategoryBadge = (taskCategory) => {
const colors = {
design: "bg-blue-100 text-blue-800",
construction: "bg-green-100 text-green-800"
};
return (
<span className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${colors[taskCategory] || "bg-gray-100 text-gray-800"}`}>
{taskCategory === "design" ? "Projektowe" : taskCategory === "construction" ? "Budowlane" : taskCategory}
</span>
);
};
if (loading) { if (loading) {
return ( return (
<PageContainer> <PageContainer>
@@ -170,9 +183,12 @@ export default function TaskTemplatesPage() {
<h3 className="text-lg font-semibold text-gray-900 truncate pr-2"> <h3 className="text-lg font-semibold text-gray-900 truncate pr-2">
{template.name} {template.name}
</h3> </h3>
<Badge variant="primary" size="sm"> <div className="flex flex-col gap-1">
{template.max_wait_days} {t('common.days')} <Badge variant="primary" size="sm">
</Badge> {template.max_wait_days} {t('common.days')}
</Badge>
{getTaskCategoryBadge(template.task_category)}
</div>
</div> </div>
{template.description && ( {template.description && (
<p className="text-gray-600 text-sm mb-4 line-clamp-2"> <p className="text-gray-600 text-sm mb-4 line-clamp-2">

View File

@@ -14,6 +14,7 @@ export default function TaskTemplateForm({
const [name, setName] = useState(""); const [name, setName] = useState("");
const [max_wait_days, setRequiredWaitDays] = useState(""); const [max_wait_days, setRequiredWaitDays] = useState("");
const [description, setDescription] = useState(""); const [description, setDescription] = useState("");
const [task_category, setTaskCategory] = useState("design");
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const router = useRouter(); const router = useRouter();
@@ -26,6 +27,7 @@ export default function TaskTemplateForm({
setName(initialData.name || ""); setName(initialData.name || "");
setRequiredWaitDays(initialData.max_wait_days?.toString() || ""); setRequiredWaitDays(initialData.max_wait_days?.toString() || "");
setDescription(initialData.description || ""); setDescription(initialData.description || "");
setTaskCategory(initialData.task_category || "design");
} }
} }
}, [templateId, initialData]); }, [templateId, initialData]);
@@ -45,6 +47,7 @@ export default function TaskTemplateForm({
name, name,
max_wait_days: parseInt(max_wait_days, 10) || 0, max_wait_days: parseInt(max_wait_days, 10) || 0,
description: description || null, description: description || null,
task_category,
}), }),
}); });
@@ -78,6 +81,21 @@ export default function TaskTemplateForm({
/> />
</div> </div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Kategoria zadania *
</label>
<select
value={task_category}
onChange={(e) => setTaskCategory(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
required
>
<option value="design">Zadania projektowe</option>
<option value="construction">Zadania budowlane</option>
</select>
</div>
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">
{t('taskTemplates.estimatedDuration')} {t('taskTemplates.estimatedDuration')}

View File

@@ -40,7 +40,8 @@ export default function initializeDatabase() {
task_id INTEGER PRIMARY KEY AUTOINCREMENT, task_id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, name TEXT NOT NULL,
max_wait_days INTEGER DEFAULT 0, max_wait_days INTEGER DEFAULT 0,
is_standard INTEGER NOT NULL DEFAULT 0 is_standard INTEGER NOT NULL DEFAULT 0,
task_category TEXT CHECK(task_category IN ('design', 'construction')) NOT NULL DEFAULT 'design'
); );
-- Table: task_sets -- Table: task_sets
@@ -382,6 +383,22 @@ export default function initializeDatabase() {
console.warn("Migration warning:", e.message); console.warn("Migration warning:", e.message);
} }
// Migration: Add task_category column to tasks table
try {
const tableInfo = db.prepare("PRAGMA table_info(tasks)").all();
const hasTaskCategory = tableInfo.some(col => col.name === 'task_category');
if (!hasTaskCategory) {
// Add the task_category column
db.exec(`
ALTER TABLE tasks ADD COLUMN task_category TEXT CHECK(task_category IN ('design', 'construction')) NOT NULL DEFAULT 'design';
`);
console.log("✅ Added task_category column to tasks table");
}
} catch (e) {
console.warn("Migration warning:", e.message);
}
// Generic file attachments table // Generic file attachments table
db.exec(` db.exec(`
CREATE TABLE IF NOT EXISTS file_attachments ( CREATE TABLE IF NOT EXISTS file_attachments (