Files
panel/src/components/ProjectForm.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

442 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { Card, CardHeader, CardContent } from "@/components/ui/Card";
import Button from "@/components/ui/Button";
import { Input } from "@/components/ui/Input";
import { formatDateForInput } from "@/lib/utils";
import { useTranslation } from "@/lib/i18n";
export default function ProjectForm({ initialData = null }) {
const { t } = useTranslation();
const [form, setForm] = useState({
contract_id: "",
project_name: "",
address: "",
plot: "",
district: "",
unit: "",
city: "",
investment_number: "",
finish_date: "",
wp: "",
contact: "",
notes: "",
coordinates: "",
project_type: "design",
assigned_to: "",
});
const [contracts, setContracts] = useState([]);
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const router = useRouter();
const isEdit = !!initialData;
useEffect(() => {
// Fetch contracts
fetch("/api/contracts")
.then((res) => res.json())
.then(setContracts);
// Fetch users for assignment
fetch("/api/projects/users")
.then((res) => res.json())
.then(setUsers);
}, []);
// Update form state when initialData changes (for edit mode)
useEffect(() => {
if (initialData) {
setForm({
contract_id: "",
project_name: "",
address: "",
plot: "",
district: "",
unit: "",
city: "",
investment_number: "",
finish_date: "",
wp: "",
contact: "",
notes: "",
coordinates: "",
project_type: "design",
assigned_to: "",
...initialData,
// Ensure these defaults are preserved if not in initialData
project_type: initialData.project_type || "design",
assigned_to: initialData.assigned_to || "",
// Format finish_date for input if it exists
finish_date: initialData.finish_date
? formatDateForInput(initialData.finish_date)
: "",
});
}
}, [initialData]);
function handleChange(e) {
setForm({ ...form, [e.target.name]: e.target.value });
}
async function handleSubmit(e) {
e.preventDefault();
setLoading(true);
try {
const res = await fetch(
isEdit ? `/api/projects/${initialData.project_id}` : "/api/projects",
{
method: isEdit ? "PUT" : "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(form),
}
);
if (res.ok) {
const project = await res.json();
if (isEdit) {
router.push(`/projects/${project.project_id}`);
} else {
router.push("/projects");
}
} else {
alert(t('projects.saveError'));
}
} catch (error) {
console.error("Error saving project:", error);
alert(t('projects.saveError'));
} finally {
setLoading(false);
}
}
return (
<Card>
<CardHeader>
<h2 className="text-xl font-semibold text-gray-900">
{isEdit ? t('projects.editProjectDetails') : t('projects.projectDetails')}
</h2>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-6">
{/* Contract and Project Type Section */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('projects.contract')} <span className="text-red-500">*</span>
</label>
<select
name="contract_id"
value={form.contract_id || ""}
onChange={handleChange}
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
required
>
<option value="">{t('projects.selectContract')}</option>
{contracts.map((contract) => (
<option
key={contract.contract_id}
value={contract.contract_id}
>
{contract.contract_number} {contract.contract_name}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('projects.type')} <span className="text-red-500">*</span>
</label>
<select
name="project_type"
value={form.project_type}
onChange={handleChange}
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
required
>
<option value="design">{t('projectType.design')}</option>
<option value="construction">{t('projectType.construction')}</option>
<option value="design+construction">
{t('projectType.design+construction')}
</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('projects.assignedTo')}
</label>
<select
name="assigned_to"
value={form.assigned_to || ""}
onChange={handleChange}
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none 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.username})
</option>
))}
</select>
</div>
</div>
{/* Basic Information Section */}
<div className="border-t pt-6">
<h3 className="text-lg font-medium text-gray-900 mb-4">
{t('projects.basicInformation')}
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('projects.projectName')} <span className="text-red-500">*</span>
</label>
<Input
type="text"
name="project_name"
value={form.project_name || ""}
onChange={handleChange}
placeholder={t('projects.enterProjectName')}
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('projects.city')}
</label>
<Input
type="text"
name="city"
value={form.city || ""}
onChange={handleChange}
placeholder={t('projects.enterCity')}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('projects.address')}
</label>
<Input
type="text"
name="address"
value={form.address || ""}
onChange={handleChange}
placeholder={t('projects.enterAddress')}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('projects.plot')}
</label>
<Input
type="text"
name="plot"
value={form.plot || ""}
onChange={handleChange}
placeholder={t('projects.enterPlotNumber')}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('projects.district')}
</label>
<Input
type="text"
name="district"
value={form.district || ""}
onChange={handleChange}
placeholder={t('projects.enterDistrict')}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('projects.unit')}
</label>
<Input
type="text"
name="unit"
value={form.unit || ""}
onChange={handleChange}
placeholder={t('projects.enterUnit')}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('projects.finishDate')}
</label>
<Input
type="date"
name="finish_date"
value={formatDateForInput(form.finish_date)}
onChange={handleChange}
/>
</div>
</div>
</div>
{/* Additional Information Section */}
<div className="border-t pt-6">
<h3 className="text-lg font-medium text-gray-900 mb-4">
{t('projects.additionalInfo')}
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('projects.investmentNumber')}
</label>
<Input
type="text"
name="investment_number"
value={form.investment_number || ""}
onChange={handleChange}
placeholder={t('projects.placeholders.investmentNumber')}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
WP
</label>
<Input
type="text"
name="wp"
value={form.wp || ""}
onChange={handleChange}
placeholder={t('projects.placeholders.wp')}
/>
</div>
<div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('projects.contact')}
</label>
<Input
type="text"
name="contact"
value={form.contact || ""}
onChange={handleChange}
placeholder={t('projects.placeholders.contact')}
/>
</div>
<div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('projects.coordinates')}
</label>
<Input
type="text"
name="coordinates"
value={form.coordinates || ""}
onChange={handleChange}
placeholder={t('projects.placeholders.coordinates')}
/>
</div>
<div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('projects.notes')}
</label>
<textarea
name="notes"
value={form.notes || ""}
onChange={handleChange}
rows={4}
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
placeholder={t('projects.placeholders.notes')}
/>
</div>
</div>
</div>
{/* Form Actions */}
<div className="border-t pt-6 flex items-center justify-end gap-4">
<Button
type="button"
variant="outline"
onClick={() => router.back()}
disabled={loading}
>
{t('common.cancel')}
</Button>
<Button type="submit" variant="primary" disabled={loading}>
{loading ? (
<>
<svg
className="animate-spin -ml-1 mr-3 h-4 w-4 text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
{isEdit ? t('projects.updating') : t('projects.creating')}
</>
) : (
<>
{isEdit ? (
<>
<svg
className="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 13l4 4L19 7"
/>
</svg>
{t('projects.updateProject')}
</>
) : (
<>
<svg
className="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 4v16m8-8H4"
/>
</svg>
{t('projects.createProject')}
</>
)}
</>
)}
</Button>
</div>
</form>
</CardContent>
</Card>
);
}