feat(i18n): Implement multilingual support with Polish and English translations
- Added translation context and provider for managing language state. - Integrated translation functionality into existing components (TaskStatusDropdown, Navigation). - Created LanguageSwitcher component for language selection. - Updated task statuses and navigation labels to use translations. - Added Polish translations for various UI elements, including navigation, tasks, projects, and contracts. - Refactored utility functions to return localized strings for deadlines and date formatting.
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "@/lib/i18n";
|
||||
|
||||
export default function NoteForm({ projectId }) {
|
||||
const { t } = useTranslation();
|
||||
const [note, setNote] = useState("");
|
||||
const [status, setStatus] = useState(null);
|
||||
|
||||
@@ -17,10 +19,10 @@ export default function NoteForm({ projectId }) {
|
||||
|
||||
if (res.ok) {
|
||||
setNote("");
|
||||
setStatus("Note added");
|
||||
setStatus(t("common.addNoteSuccess"));
|
||||
window.location.reload();
|
||||
} else {
|
||||
setStatus("Failed to save note");
|
||||
setStatus(t("common.addNoteError"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +31,7 @@ export default function NoteForm({ projectId }) {
|
||||
<textarea
|
||||
value={note}
|
||||
onChange={(e) => setNote(e.target.value)}
|
||||
placeholder="Add a new note..."
|
||||
placeholder={t("common.addNotePlaceholder")}
|
||||
className="border p-2 w-full"
|
||||
rows={3}
|
||||
required
|
||||
@@ -38,7 +40,7 @@ export default function NoteForm({ projectId }) {
|
||||
type="submit"
|
||||
className="bg-blue-600 text-white px-4 py-2 rounded"
|
||||
>
|
||||
Add Note
|
||||
{t("common.addNote")}
|
||||
</button>
|
||||
{status && <p className="text-sm text-gray-600">{status}</p>}
|
||||
</form>
|
||||
|
||||
@@ -6,8 +6,10 @@ 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: "",
|
||||
@@ -146,7 +148,7 @@ export default function ProjectForm({ initialData = null }) {
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Project Type <span className="text-red-500">*</span>
|
||||
Typ projektu <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<select
|
||||
name="project_type"
|
||||
@@ -155,17 +157,17 @@ export default function ProjectForm({ initialData = null }) {
|
||||
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">Design (Projektowanie)</option>
|
||||
<option value="construction">Construction (Realizacja)</option>
|
||||
<option value="design">{t('projectType.design')}</option>
|
||||
<option value="construction">{t('projectType.construction')}</option>
|
||||
<option value="design+construction">
|
||||
Design + Construction (Projektowanie + Realizacja)
|
||||
{t('projectType.design+construction')}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Assigned To
|
||||
{t('projects.assignedTo')}
|
||||
</label>
|
||||
<select
|
||||
name="assigned_to"
|
||||
@@ -173,7 +175,7 @@ export default function ProjectForm({ initialData = null }) {
|
||||
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="">Unassigned</option>
|
||||
<option value="">{t('projects.unassigned')}</option>
|
||||
{users.map((user) => (
|
||||
<option key={user.id} value={user.id}>
|
||||
{user.name} ({user.email})
|
||||
@@ -186,92 +188,92 @@ export default function ProjectForm({ initialData = null }) {
|
||||
{/* Basic Information Section */}
|
||||
<div className="border-t pt-6">
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-4">
|
||||
Basic Information
|
||||
{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">
|
||||
Project Name <span className="text-red-500">*</span>
|
||||
{t('projects.projectName')} <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
name="project_name"
|
||||
value={form.project_name || ""}
|
||||
onChange={handleChange}
|
||||
placeholder="Enter project name"
|
||||
placeholder={t('projects.enterProjectName')}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
City
|
||||
{t('projects.city')}
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
name="city"
|
||||
value={form.city || ""}
|
||||
onChange={handleChange}
|
||||
placeholder="Enter city"
|
||||
placeholder={t('projects.enterCity')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Address
|
||||
{t('projects.address')}
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
name="address"
|
||||
value={form.address || ""}
|
||||
onChange={handleChange}
|
||||
placeholder="Enter address"
|
||||
placeholder={t('projects.enterAddress')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Plot
|
||||
{t('projects.plot')}
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
name="plot"
|
||||
value={form.plot || ""}
|
||||
onChange={handleChange}
|
||||
placeholder="Enter plot number"
|
||||
placeholder={t('projects.enterPlotNumber')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
District
|
||||
{t('projects.district')}
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
name="district"
|
||||
value={form.district || ""}
|
||||
onChange={handleChange}
|
||||
placeholder="Enter district"
|
||||
placeholder={t('projects.enterDistrict')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Unit
|
||||
{t('projects.unit')}
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
name="unit"
|
||||
value={form.unit || ""}
|
||||
onChange={handleChange}
|
||||
placeholder="Enter unit"
|
||||
placeholder={t('projects.enterUnit')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Finish Date
|
||||
</label>{" "}
|
||||
{t('projects.finishDate')}
|
||||
</label>
|
||||
<Input
|
||||
type="date"
|
||||
name="finish_date"
|
||||
@@ -316,33 +318,33 @@ export default function ProjectForm({ initialData = null }) {
|
||||
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Contact Information
|
||||
{t('projects.contact')}
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
name="contact"
|
||||
value={form.contact || ""}
|
||||
onChange={handleChange}
|
||||
placeholder="Enter contact details"
|
||||
placeholder={t('projects.placeholders.contact')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Coordinates
|
||||
{t('projects.coordinates')}
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
name="coordinates"
|
||||
value={form.coordinates || ""}
|
||||
onChange={handleChange}
|
||||
placeholder="e.g., 49.622958,20.629562"
|
||||
placeholder={t('projects.placeholders.coordinates')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="md:col-span-2">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Notes
|
||||
{t('projects.notes')}
|
||||
</label>
|
||||
<textarea
|
||||
name="notes"
|
||||
@@ -350,7 +352,7 @@ export default function ProjectForm({ initialData = null }) {
|
||||
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="Enter any additional notes"
|
||||
placeholder={t('projects.placeholders.notes')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -364,7 +366,7 @@ export default function ProjectForm({ initialData = null }) {
|
||||
onClick={() => router.back()}
|
||||
disabled={loading}
|
||||
>
|
||||
Cancel
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button type="submit" variant="primary" disabled={loading}>
|
||||
{loading ? (
|
||||
@@ -389,7 +391,7 @@ export default function ProjectForm({ initialData = null }) {
|
||||
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 ? "Updating..." : "Creating..."}
|
||||
{isEdit ? t('projects.updating') : t('projects.creating')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
@@ -408,7 +410,7 @@ export default function ProjectForm({ initialData = null }) {
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
Update Project
|
||||
{t('projects.updateProject')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
@@ -425,7 +427,7 @@ export default function ProjectForm({ initialData = null }) {
|
||||
d="M12 4v16m8-8H4"
|
||||
/>
|
||||
</svg>
|
||||
Create Project
|
||||
{t('projects.createProject')}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import Badge from "@/components/ui/Badge";
|
||||
import { useTranslation } from "@/lib/i18n";
|
||||
|
||||
export default function ProjectStatusDropdown({
|
||||
project,
|
||||
size = "md",
|
||||
showDropdown = true,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [status, setStatus] = useState(project.project_status);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
@@ -21,19 +23,19 @@ export default function ProjectStatusDropdown({
|
||||
|
||||
const statusConfig = {
|
||||
registered: {
|
||||
label: "Registered",
|
||||
label: t("projectStatus.registered"),
|
||||
variant: "secondary",
|
||||
},
|
||||
in_progress_design: {
|
||||
label: "In Progress (Design)",
|
||||
label: t("projectStatus.in_progress_design"),
|
||||
variant: "primary",
|
||||
},
|
||||
in_progress_construction: {
|
||||
label: "In Progress (Construction)",
|
||||
label: t("projectStatus.in_progress_construction"),
|
||||
variant: "primary",
|
||||
},
|
||||
fulfilled: {
|
||||
label: "Completed",
|
||||
label: t("projectStatus.fulfilled"),
|
||||
variant: "success",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -7,8 +7,10 @@ import { Card, CardHeader, CardContent } from "./ui/Card";
|
||||
import Button from "./ui/Button";
|
||||
import Badge from "./ui/Badge";
|
||||
import { formatDate } from "@/lib/utils";
|
||||
import { useTranslation } from "@/lib/i18n";
|
||||
|
||||
export default function ProjectTasksSection({ projectId }) {
|
||||
const { t } = useTranslation();
|
||||
const [projectTasks, setProjectTasks] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [taskNotes, setTaskNotes] = useState({});
|
||||
@@ -180,14 +182,14 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
if (res.ok) {
|
||||
refetchTasks(); // Refresh the list
|
||||
} else {
|
||||
alert("Failed to update task status");
|
||||
alert(t("errors.generic"));
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Error updating task status");
|
||||
alert(t("errors.generic"));
|
||||
}
|
||||
};
|
||||
const handleDeleteTask = async (taskId) => {
|
||||
if (!confirm("Are you sure you want to delete this task?")) return;
|
||||
if (!confirm(t("common.deleteConfirm"))) return;
|
||||
try {
|
||||
const res = await fetch(`/api/project-tasks/${taskId}`, {
|
||||
method: "DELETE",
|
||||
@@ -197,7 +199,7 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
refetchTasks(); // Refresh the list
|
||||
} else {
|
||||
const errorData = await res.json();
|
||||
alert("Failed to delete task: " + (errorData.error || "Unknown error"));
|
||||
alert(t("errors.generic") + ": " + (errorData.error || t("errors.unknown")));
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Error deleting task: " + error.message);
|
||||
@@ -227,10 +229,10 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
setTaskNotes((prev) => ({ ...prev, [taskId]: notes }));
|
||||
setNewNote((prev) => ({ ...prev, [taskId]: "" }));
|
||||
} else {
|
||||
alert("Failed to add note");
|
||||
alert(t("errors.generic"));
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Error adding note");
|
||||
alert(t("errors.generic"));
|
||||
} finally {
|
||||
setLoadingNotes((prev) => ({ ...prev, [taskId]: false }));
|
||||
}
|
||||
@@ -278,10 +280,10 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
refetchTasks();
|
||||
handleCloseEditModal();
|
||||
} else {
|
||||
alert("Failed to update task");
|
||||
alert(t("errors.generic"));
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Error updating task");
|
||||
alert(t("errors.generic"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -327,10 +329,10 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-xl font-semibold text-gray-900">Project Tasks</h2>
|
||||
<h2 className="text-xl font-semibold text-gray-900">{t("tasks.title")}</h2>
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge variant="default" className="text-sm">
|
||||
{projectTasks.length} {projectTasks.length === 1 ? "task" : "tasks"}
|
||||
{projectTasks.length} {projectTasks.length === 1 ? t("tasks.task") : t("tasks.tasks")}
|
||||
</Badge>
|
||||
<Button
|
||||
variant="primary"
|
||||
@@ -350,7 +352,7 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
d="M12 4v16m8-8H4"
|
||||
/>
|
||||
</svg>
|
||||
Add Task
|
||||
{t("tasks.newTask")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>{" "}
|
||||
@@ -404,7 +406,7 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
{/* Current Tasks */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h3 className="text-lg font-medium text-gray-900">Current Tasks</h3>
|
||||
<h3 className="text-lg font-medium text-gray-900">{t("tasks.title")}</h3>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
{loading ? (
|
||||
@@ -430,10 +432,10 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
</svg>
|
||||
</div>
|
||||
<p className="text-gray-500 text-sm">
|
||||
No tasks assigned to this project yet.
|
||||
{t("tasks.noTasksMessage")}
|
||||
</p>
|
||||
<p className="text-gray-400 text-xs mt-1">
|
||||
Add a task above to get started.
|
||||
{t("tasks.addTaskMessage")}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
@@ -442,22 +444,22 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
<thead className="bg-gray-50 border-b border-gray-200">
|
||||
<tr>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Task
|
||||
{t("tasks.taskName")}
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Priority
|
||||
{t("tasks.priority")}
|
||||
</th>{" "}
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Max Wait
|
||||
{t("tasks.maxWait")}
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Date Started
|
||||
{t("tasks.dateStarted")}
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Status
|
||||
{t("tasks.status")}
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-48">
|
||||
Actions
|
||||
{t("tasks.actions")}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -503,16 +505,16 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
variant={getPriorityVariant(task.priority)}
|
||||
size="sm"
|
||||
>
|
||||
{task.priority}
|
||||
{t(`tasks.${task.priority}`)}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="px-4 py-4 text-sm text-gray-600">
|
||||
{task.max_wait_days} days
|
||||
{task.max_wait_days} {t("tasks.days")}
|
||||
</td>{" "}
|
||||
<td className="px-4 py-4 text-sm text-gray-600">
|
||||
{task.date_started
|
||||
? formatDate(task.date_started)
|
||||
: "Not started"}
|
||||
: t("tasks.notStarted")}
|
||||
</td>
|
||||
<td className="px-4 py-4">
|
||||
<TaskStatusDropdownSimple
|
||||
@@ -564,7 +566,7 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
{t("common.edit")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="danger"
|
||||
@@ -585,7 +587,7 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
d="19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
Delete
|
||||
{t("common.delete")}
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
@@ -596,7 +598,7 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
<td colSpan="6" className="px-4 py-3">
|
||||
<div className="text-sm text-gray-700">
|
||||
<span className="font-medium text-gray-900">
|
||||
Description:
|
||||
{t("tasks.description")}:
|
||||
</span>
|
||||
<p className="mt-1">{task.description}</p>
|
||||
</div>
|
||||
@@ -609,7 +611,7 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
<td colSpan="6" className="px-4 py-4">
|
||||
<div className="space-y-3">
|
||||
<h5 className="text-sm font-medium text-gray-900">
|
||||
Notes ({taskNotes[task.id]?.length || 0})
|
||||
{t("tasks.comments")} ({taskNotes[task.id]?.length || 0})
|
||||
</h5>
|
||||
|
||||
{/* Existing Notes */}
|
||||
@@ -629,7 +631,7 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
{note.is_system ? (
|
||||
<span className="px-2 py-1 text-xs bg-blue-100 text-blue-700 rounded-full font-medium">
|
||||
System
|
||||
{t("admin.system")}
|
||||
</span>
|
||||
) : null}
|
||||
{note.created_by_name && (
|
||||
@@ -670,7 +672,7 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Add a note..."
|
||||
placeholder={t("tasks.addComment")}
|
||||
value={newNote[task.id] || ""}
|
||||
onChange={(e) =>
|
||||
setNewNote((prev) => ({
|
||||
@@ -694,7 +696,7 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
!newNote[task.id]?.trim()
|
||||
}
|
||||
>
|
||||
{loadingNotes[task.id] ? "Adding..." : "Add"}
|
||||
{loadingNotes[task.id] ? t("common.saving") : t("common.add")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -715,7 +717,7 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
<div className="bg-white rounded-lg p-6 w-full max-w-md mx-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900">
|
||||
Edit Task:{" "}
|
||||
{t("tasks.editTask")}:{" "}
|
||||
{editingTask?.custom_task_name || editingTask?.task_name}
|
||||
</h3>
|
||||
<button
|
||||
@@ -740,7 +742,7 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
{/* Assignment */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Assigned To
|
||||
{t("tasks.assignedTo")}
|
||||
</label>
|
||||
<select
|
||||
value={editTaskForm.assigned_to}
|
||||
@@ -752,7 +754,7 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
<option value="">Unassigned</option>
|
||||
<option value="">{t("projects.unassigned")}</option>
|
||||
{users.map((user) => (
|
||||
<option key={user.id} value={user.id}>
|
||||
{user.name}
|
||||
@@ -764,7 +766,7 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
{/* Priority */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Priority
|
||||
{t("tasks.priority")}
|
||||
</label>
|
||||
<select
|
||||
value={editTaskForm.priority}
|
||||
@@ -776,18 +778,18 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
<option value="">Select Priority</option>
|
||||
<option value="low">Low</option>
|
||||
<option value="normal">Normal</option>
|
||||
<option value="high">High</option>
|
||||
<option value="urgent">Urgent</option>
|
||||
<option value="">{t("common.selectOption")}</option>
|
||||
<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>
|
||||
|
||||
{/* Date Started */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Date Started
|
||||
{t("tasks.dateStarted")}
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
@@ -805,7 +807,7 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
{/* Status */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Status
|
||||
{t("tasks.status")}
|
||||
</label>
|
||||
<select
|
||||
value={editTaskForm.status}
|
||||
@@ -817,21 +819,21 @@ export default function ProjectTasksSection({ projectId }) {
|
||||
}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
<option value="">Select Status</option>
|
||||
<option value="pending">Pending</option>
|
||||
<option value="in_progress">In Progress</option>
|
||||
<option value="completed">Completed</option>
|
||||
<option value="cancelled">Cancelled</option>
|
||||
<option value="">{t("common.selectOption")}</option>
|
||||
<option value="pending">{t("taskStatus.pending")}</option>
|
||||
<option value="in_progress">{t("taskStatus.in_progress")}</option>
|
||||
<option value="completed">{t("taskStatus.completed")}</option>
|
||||
<option value="cancelled">{t("taskStatus.cancelled")}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-3 mt-6">
|
||||
<Button variant="outline" onClick={handleCloseEditModal}>
|
||||
Cancel
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button variant="primary" onClick={handleUpdateTask}>
|
||||
Update Task
|
||||
{t("tasks.updateTask")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,13 +3,16 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import Badge from "@/components/ui/Badge";
|
||||
import { useTranslation } from "@/lib/i18n";
|
||||
|
||||
export default function TaskStatusDropdown({
|
||||
task,
|
||||
size = "sm",
|
||||
showDropdown = true,
|
||||
onStatusChange,
|
||||
}) { const [status, setStatus] = useState(task.status);
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [status, setStatus] = useState(task.status);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [dropdownPosition, setDropdownPosition] = useState({ x: 0, y: 0, position: 'bottom' });
|
||||
@@ -23,19 +26,19 @@ export default function TaskStatusDropdown({
|
||||
|
||||
const statusConfig = {
|
||||
pending: {
|
||||
label: "Pending",
|
||||
label: t("taskStatus.pending"),
|
||||
variant: "warning",
|
||||
},
|
||||
in_progress: {
|
||||
label: "In Progress",
|
||||
label: t("taskStatus.in_progress"),
|
||||
variant: "primary",
|
||||
},
|
||||
completed: {
|
||||
label: "Completed",
|
||||
label: t("taskStatus.completed"),
|
||||
variant: "success",
|
||||
},
|
||||
cancelled: {
|
||||
label: "Cancelled",
|
||||
label: t("taskStatus.cancelled"),
|
||||
variant: "danger",
|
||||
},
|
||||
};
|
||||
|
||||
42
src/components/ui/LanguageSwitcher.js
Normal file
42
src/components/ui/LanguageSwitcher.js
Normal file
@@ -0,0 +1,42 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslation } from '@/lib/i18n';
|
||||
|
||||
const LanguageSwitcher = ({ className = '' }) => {
|
||||
const { language, changeLanguage, availableLanguages, t } = useTranslation();
|
||||
|
||||
const languageNames = {
|
||||
pl: 'Polski',
|
||||
en: 'English'
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`relative ${className}`}>
|
||||
<select
|
||||
value={language}
|
||||
onChange={(e) => changeLanguage(e.target.value)}
|
||||
className="appearance-none bg-white border border-gray-300 rounded-md px-3 py-1 pr-8 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
title={t('common.selectLanguage')}
|
||||
>
|
||||
{availableLanguages.map((lang) => (
|
||||
<option key={lang} value={lang}>
|
||||
{languageNames[lang] || lang.toUpperCase()}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
{/* Custom dropdown arrow */}
|
||||
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
|
||||
<svg
|
||||
className="fill-current h-4 w-4"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LanguageSwitcher;
|
||||
@@ -3,10 +3,13 @@
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useSession, signOut } from "next-auth/react";
|
||||
import { useTranslation } from "@/lib/i18n";
|
||||
import LanguageSwitcher from "./LanguageSwitcher";
|
||||
|
||||
const Navigation = () => {
|
||||
const pathname = usePathname();
|
||||
const { data: session, status } = useSession();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const isActive = (path) => {
|
||||
if (path === "/") return pathname === "/";
|
||||
@@ -18,16 +21,16 @@ const Navigation = () => {
|
||||
};
|
||||
|
||||
const navItems = [
|
||||
{ href: "/", label: "Dashboard" },
|
||||
{ href: "/projects", label: "Projects" },
|
||||
{ href: "/tasks/templates", label: "Task Templates" },
|
||||
{ href: "/project-tasks", label: "Project Tasks" },
|
||||
{ href: "/contracts", label: "Contracts" },
|
||||
{ href: "/", label: t('navigation.dashboard') },
|
||||
{ href: "/projects", label: t('navigation.projects') },
|
||||
{ href: "/tasks/templates", label: t('navigation.taskTemplates') },
|
||||
{ href: "/project-tasks", label: t('navigation.projectTasks') },
|
||||
{ href: "/contracts", label: t('navigation.contracts') },
|
||||
];
|
||||
|
||||
// Add admin-only items
|
||||
if (session?.user?.role === 'admin') {
|
||||
navItems.push({ href: "/admin/users", label: "User Management" });
|
||||
navItems.push({ href: "/admin/users", label: t('navigation.userManagement') });
|
||||
}
|
||||
|
||||
const handleSignOut = async () => {
|
||||
@@ -45,13 +48,13 @@ const Navigation = () => {
|
||||
<div className="flex items-center justify-between h-16">
|
||||
<div className="flex items-center">
|
||||
<Link href="/" className="text-xl font-bold text-gray-900">
|
||||
Project Panel
|
||||
{t('navigation.projectPanel')}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4">
|
||||
{status === "loading" ? (
|
||||
<div className="text-gray-500">Loading...</div>
|
||||
<div className="text-gray-500">{t('navigation.loading')}</div>
|
||||
) : session ? (
|
||||
<>
|
||||
<div className="flex space-x-8">
|
||||
@@ -71,10 +74,12 @@ const Navigation = () => {
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4 ml-8 pl-8 border-l border-gray-200">
|
||||
<LanguageSwitcher />
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="text-sm">
|
||||
<div className="font-medium text-gray-900">{session.user.name}</div>
|
||||
<div className="text-gray-500 capitalize">{session.user.role?.replace('_', ' ')}</div>
|
||||
<div className="text-gray-500 capitalize">{t(`userRoles.${session.user.role}`) || session.user.role?.replace('_', ' ')}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -82,17 +87,20 @@ const Navigation = () => {
|
||||
onClick={handleSignOut}
|
||||
className="bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-2 rounded-md text-sm font-medium transition-colors"
|
||||
>
|
||||
Sign Out
|
||||
{t('navigation.signOut')}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<Link
|
||||
href="/auth/signin"
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm font-medium transition-colors"
|
||||
>
|
||||
Sign In
|
||||
</Link>
|
||||
<>
|
||||
<LanguageSwitcher />
|
||||
<Link
|
||||
href="/auth/signin"
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm font-medium transition-colors"
|
||||
>
|
||||
{t('navigation.signIn')}
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user