Files
panel/src/components/ProjectTaskForm.js
RKWojs 0dd988730f feat: Implement internationalization for task management components
- Added translation support for task-related strings in ProjectTaskForm and ProjectTasksSection components.
- Integrated translation for navigation items in the Navigation component.
- Created ProjectCalendarWidget component with Polish translations for project statuses and deadlines.
- Developed Tooltip component for enhanced user experience with tooltips.
- Established a field change history logging system in the database with associated queries.
- Enhanced task update logging to include translated status and priority changes.
- Introduced server-side translations for system messages to improve localization.
2025-09-11 15:49:07 +02:00

222 lines
6.9 KiB
JavaScript

"use client";
import { useState, useEffect } from "react";
import Button from "./ui/Button";
import Badge from "./ui/Badge";
import { useTranslation } from "@/lib/i18n";
export default function ProjectTaskForm({ projectId, onTaskAdded }) {
const { t } = useTranslation();
const [taskTemplates, setTaskTemplates] = useState([]);
const [users, setUsers] = useState([]);
const [taskType, setTaskType] = useState("template"); // "template" or "custom"
const [selectedTemplate, setSelectedTemplate] = useState("");
const [customTaskName, setCustomTaskName] = useState("");
const [customMaxWaitDays, setCustomMaxWaitDays] = useState("");
const [customDescription, setCustomDescription] = useState("");
const [priority, setPriority] = useState("normal");
const [assignedTo, setAssignedTo] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
useEffect(() => {
// Fetch available task templates
fetch("/api/tasks/templates")
.then((res) => res.json())
.then(setTaskTemplates);
// Fetch users for assignment
fetch("/api/project-tasks/users")
.then((res) => res.json())
.then(setUsers);
}, []);
async function handleSubmit(e) {
e.preventDefault();
// Validate based on task type
if (taskType === "template" && !selectedTemplate) return;
if (taskType === "custom" && !customTaskName.trim()) return;
setIsSubmitting(true);
try {
const requestData = {
project_id: parseInt(projectId),
priority,
assigned_to: assignedTo || null,
};
if (taskType === "template") {
requestData.task_template_id = parseInt(selectedTemplate);
} else {
requestData.custom_task_name = customTaskName.trim();
requestData.custom_max_wait_days = parseInt(customMaxWaitDays) || 0;
requestData.custom_description = customDescription.trim();
}
const res = await fetch("/api/project-tasks", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(requestData),
});
if (res.ok) {
// Reset form
setSelectedTemplate("");
setCustomTaskName("");
setCustomMaxWaitDays("");
setCustomDescription("");
setPriority("normal");
setAssignedTo("");
if (onTaskAdded) onTaskAdded();
} else {
alert(t("tasks.addTaskError"));
}
} catch (error) {
alert(t("tasks.addTaskError"));
} finally {
setIsSubmitting(false);
}
}
return (
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-3">
{t("tasks.taskType")}
</label>
<div className="flex space-x-6">
<label className="flex items-center">
<input
type="radio"
value="template"
checked={taskType === "template"}
onChange={(e) => setTaskType(e.target.value)}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300"
/>
<span className="ml-2 text-sm text-gray-900">{t("tasks.fromTemplate")}</span>
</label>
<label className="flex items-center">
<input
type="radio"
value="custom"
checked={taskType === "custom"}
onChange={(e) => setTaskType(e.target.value)}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300"
/>
<span className="ml-2 text-sm text-gray-900">{t("tasks.customTask")}</span>
</label>
</div>
</div>
{taskType === "template" ? (
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t("tasks.selectTemplate")}
</label>{" "}
<select
value={selectedTemplate}
onChange={(e) => setSelectedTemplate(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
required
>
<option value="">{t("tasks.chooseTemplate")}</option>
{taskTemplates.map((template) => (
<option key={template.task_id} value={template.task_id}>
{template.name} ({template.max_wait_days} {t("tasks.days")})
</option>
))}
</select>
</div>
) : (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t("tasks.taskName")}
</label>
<input
type="text"
value={customTaskName}
onChange={(e) => setCustomTaskName(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder={t("tasks.enterTaskName")}
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t("tasks.maxWait")}
</label>
<input
type="number"
value={customMaxWaitDays}
onChange={(e) => setCustomMaxWaitDays(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="0"
min="0"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t("tasks.description")}
</label>
<textarea
value={customDescription}
onChange={(e) => setCustomDescription(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder={t("tasks.enterDescription")}
rows={3}
/>
</div>
</div>
)}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t("tasks.assignedTo")} <span className="text-gray-500 text-xs">({t("common.optional")})</span>
</label>
<select
value={assignedTo}
onChange={(e) => setAssignedTo(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="">{t("projects.unassigned")}</option>
{users.map((user) => (
<option key={user.id} value={user.id}>
{user.name} ({user.email})
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t("tasks.priority")}
</label>
<select
value={priority}
onChange={(e) => setPriority(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value="low">{t("tasks.low")}</option>
<option value="normal">{t("tasks.normal")}</option>
<option value="high">{t("tasks.high")}</option>
<option value="urgent">{t("tasks.urgent")}</option>
</select>
</div>
<div className="flex justify-end">
<Button
type="submit"
variant="primary"
disabled={
isSubmitting ||
(taskType === "template" && !selectedTemplate) ||
(taskType === "custom" && !customTaskName.trim())
}
>
{isSubmitting ? t("tasks.adding") : t("tasks.addTask")}
</Button>
</div>
</form>
);
}