feat: add task_category field to tasks with validation and update related forms
This commit is contained in:
@@ -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(
|
||||||
|
|||||||
@@ -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 });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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')}
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
Reference in New Issue
Block a user