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:
@@ -12,8 +12,10 @@ import PageContainer from "@/components/ui/PageContainer";
|
|||||||
import PageHeader from "@/components/ui/PageHeader";
|
import PageHeader from "@/components/ui/PageHeader";
|
||||||
import { LoadingState } from "@/components/ui/States";
|
import { LoadingState } from "@/components/ui/States";
|
||||||
import { formatDate } from "@/lib/utils";
|
import { formatDate } from "@/lib/utils";
|
||||||
|
import { useTranslation } from "@/lib/i18n";
|
||||||
|
|
||||||
export default function UserManagementPage() {
|
export default function UserManagementPage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [users, setUsers] = useState([]);
|
const [users, setUsers] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
@@ -54,7 +56,7 @@ export default function UserManagementPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteUser = async (userId) => {
|
const handleDeleteUser = async (userId) => {
|
||||||
if (!confirm("Are you sure you want to delete this user?")) return;
|
if (!confirm(t('admin.deleteUser') + "?")) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/admin/users/${userId}`, {
|
const response = await fetch(`/api/admin/users/${userId}`, {
|
||||||
@@ -141,7 +143,7 @@ export default function UserManagementPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<PageHeader title="User Management" description="Manage system users and permissions">
|
<PageHeader title={t('admin.userManagement')} description={t('admin.subtitle')}>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
onClick={() => setShowCreateForm(true)}
|
onClick={() => setShowCreateForm(true)}
|
||||||
@@ -149,7 +151,7 @@ export default function UserManagementPage() {
|
|||||||
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
|
||||||
</svg>
|
</svg>
|
||||||
Add User
|
{t('admin.newUser')}
|
||||||
</Button>
|
</Button>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ import SearchBar from "@/components/ui/SearchBar";
|
|||||||
import FilterBar from "@/components/ui/FilterBar";
|
import FilterBar from "@/components/ui/FilterBar";
|
||||||
import { LoadingState } from "@/components/ui/States";
|
import { LoadingState } from "@/components/ui/States";
|
||||||
import { formatDate } from "@/lib/utils";
|
import { formatDate } from "@/lib/utils";
|
||||||
|
import { useTranslation } from "@/lib/i18n";
|
||||||
|
|
||||||
export default function ContractsMainPage() {
|
export default function ContractsMainPage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [contracts, setContracts] = useState([]);
|
const [contracts, setContracts] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
@@ -133,13 +135,13 @@ export default function ContractsMainPage() {
|
|||||||
const getStatusBadge = (status) => {
|
const getStatusBadge = (status) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "active":
|
case "active":
|
||||||
return <Badge variant="success">Aktywna</Badge>;
|
return <Badge variant="success">{t('contracts.active')}</Badge>;
|
||||||
case "completed":
|
case "completed":
|
||||||
return <Badge variant="secondary">Zakończona</Badge>;
|
return <Badge variant="secondary">{t('common.completed')}</Badge>;
|
||||||
case "ongoing":
|
case "ongoing":
|
||||||
return <Badge variant="primary">W trakcie</Badge>;
|
return <Badge variant="primary">{t('contracts.withoutEndDate')}</Badge>;
|
||||||
default:
|
default:
|
||||||
return <Badge>Nieznany</Badge>;
|
return <Badge>{t('common.unknown')}</Badge>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -170,17 +172,17 @@ export default function ContractsMainPage() {
|
|||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Umowy"
|
title={t('contracts.title')}
|
||||||
description="Zarządzaj swoimi umowami i kontraktami"
|
description={t('contracts.subtitle')}
|
||||||
>
|
>
|
||||||
<Link href="/contracts/new">
|
<Link href="/contracts/new">
|
||||||
<Button variant="primary" size="lg">
|
<Button variant="primary" size="lg">
|
||||||
<span className="mr-2">➕</span>
|
<span className="mr-2">➕</span>
|
||||||
Nowa umowa
|
{t('contracts.newContract')}
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<LoadingState message="Ładowanie umów..." />
|
<LoadingState message={t('navigation.loading')} />
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -225,13 +227,13 @@ export default function ContractsMainPage() {
|
|||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Umowy"
|
title={t('contracts.title')}
|
||||||
description="Zarządzaj swoimi umowami i kontraktami"
|
description={t('contracts.subtitle')}
|
||||||
>
|
>
|
||||||
<Link href="/contracts/new">
|
<Link href="/contracts/new">
|
||||||
<Button variant="primary" size="lg">
|
<Button variant="primary" size="lg">
|
||||||
<span className="mr-2">➕</span>
|
<span className="mr-2">➕</span>
|
||||||
Nowa umowa
|
{t('contracts.newContract')}
|
||||||
</Button>
|
</Button>
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Geist, Geist_Mono } from "next/font/google";
|
|||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import Navigation from "@/components/ui/Navigation";
|
import Navigation from "@/components/ui/Navigation";
|
||||||
import { AuthProvider } from "@/components/auth/AuthProvider";
|
import { AuthProvider } from "@/components/auth/AuthProvider";
|
||||||
|
import { TranslationProvider } from "@/lib/i18n";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const geistSans = Geist({
|
||||||
variable: "--font-geist-sans",
|
variable: "--font-geist-sans",
|
||||||
@@ -14,20 +15,22 @@ const geistMono = Geist_Mono({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: "Project Management Panel",
|
title: "Panel Zarządzania Projektami",
|
||||||
description: "Professional project management dashboard",
|
description: "Profesjonalny panel zarządzania projektami",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({ children }) {
|
export default function RootLayout({ children }) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="pl">
|
||||||
<body
|
<body
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||||
>
|
>
|
||||||
<AuthProvider>
|
<TranslationProvider initialLanguage="pl">
|
||||||
<Navigation />
|
<AuthProvider>
|
||||||
<main>{children}</main>
|
<Navigation />
|
||||||
</AuthProvider>
|
<main>{children}</main>
|
||||||
|
</AuthProvider>
|
||||||
|
</TranslationProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import TaskStatusDropdownSimple from "@/components/TaskStatusDropdownSimple";
|
|||||||
import PageContainer from "@/components/ui/PageContainer";
|
import PageContainer from "@/components/ui/PageContainer";
|
||||||
import PageHeader from "@/components/ui/PageHeader";
|
import PageHeader from "@/components/ui/PageHeader";
|
||||||
import { LoadingState } from "@/components/ui/States";
|
import { LoadingState } from "@/components/ui/States";
|
||||||
|
import { useTranslation } from "@/lib/i18n";
|
||||||
import {
|
import {
|
||||||
differenceInCalendarDays,
|
differenceInCalendarDays,
|
||||||
parseISO,
|
parseISO,
|
||||||
@@ -26,6 +27,7 @@ import TaskStatusChart from "@/components/ui/TaskStatusChart";
|
|||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const { data: session, status } = useSession();
|
const { data: session, status } = useSession();
|
||||||
|
const { t } = useTranslation();
|
||||||
const [stats, setStats] = useState({
|
const [stats, setStats] = useState({
|
||||||
totalProjects: 0,
|
totalProjects: 0,
|
||||||
activeProjects: 0,
|
activeProjects: 0,
|
||||||
@@ -258,17 +260,17 @@ export default function Home() {
|
|||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Dashboard"
|
title={t('dashboard.title')}
|
||||||
description="Overview of your projects, contracts, and tasks"
|
description={t('dashboard.subtitle')}
|
||||||
/>
|
/>
|
||||||
<LoadingState message="Loading dashboard data..." />
|
<LoadingState message={t('common.loading')} />
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show loading state while session is being fetched
|
// Show loading state while session is being fetched
|
||||||
if (status === "loading") {
|
if (status === "loading") {
|
||||||
return <LoadingState message="Loading authentication..." />;
|
return <LoadingState message={t('navigation.loading')} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show sign-in prompt if not authenticated
|
// Show sign-in prompt if not authenticated
|
||||||
@@ -277,16 +279,16 @@ export default function Home() {
|
|||||||
<PageContainer>
|
<PageContainer>
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<h1 className="text-4xl font-bold text-gray-900 mb-6">
|
<h1 className="text-4xl font-bold text-gray-900 mb-6">
|
||||||
Welcome to Project Management Panel
|
Witamy w Panelu Zarządzania Projektami
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xl text-gray-600 mb-8">
|
<p className="text-xl text-gray-600 mb-8">
|
||||||
Please sign in to access the project management system.
|
Zaloguj się, aby uzyskać dostęp do systemu zarządzania projektami.
|
||||||
</p>
|
</p>
|
||||||
<Link
|
<Link
|
||||||
href="/auth/signin"
|
href="/auth/signin"
|
||||||
className="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700"
|
className="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700"
|
||||||
>
|
>
|
||||||
Sign In
|
{t('auth.signIn')}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
@@ -296,8 +298,8 @@ export default function Home() {
|
|||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title={`Welcome back, ${session.user.name}!`}
|
title={`Witaj ponownie, ${session.user.name}!`}
|
||||||
description="Overview of your projects, contracts, and tasks"
|
description={t('dashboard.subtitle')}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Link href="/projects/new">
|
<Link href="/projects/new">
|
||||||
@@ -315,7 +317,7 @@ export default function Home() {
|
|||||||
d="M12 4v16m8-8H4"
|
d="M12 4v16m8-8H4"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
New Project
|
{t('projects.newProject')}
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/contracts/new">
|
<Link href="/contracts/new">
|
||||||
@@ -333,7 +335,7 @@ export default function Home() {
|
|||||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
New Contract
|
{t('contracts.newContract')}
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@@ -347,13 +349,13 @@ export default function Home() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-medium text-gray-600 uppercase tracking-wide">
|
<p className="text-xs font-medium text-gray-600 uppercase tracking-wide">
|
||||||
Total Projects
|
{t('dashboard.totalProjects')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-2xl font-bold text-gray-900">
|
<p className="text-2xl font-bold text-gray-900">
|
||||||
{stats.totalProjects}
|
{stats.totalProjects}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-green-600 mt-1">
|
<p className="text-xs text-green-600 mt-1">
|
||||||
{stats.activeProjects} active
|
{stats.activeProjects} {t('dashboard.activeProjects').toLowerCase()}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
|
<div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
|
||||||
@@ -380,13 +382,13 @@ export default function Home() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-medium text-gray-600 uppercase tracking-wide">
|
<p className="text-xs font-medium text-gray-600 uppercase tracking-wide">
|
||||||
Contracts
|
{t('dashboard.totalContracts')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-2xl font-bold text-gray-900">
|
<p className="text-2xl font-bold text-gray-900">
|
||||||
{stats.totalContracts}
|
{stats.totalContracts}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-green-600 mt-1">
|
<p className="text-xs text-green-600 mt-1">
|
||||||
{stats.activeContracts} active
|
{stats.activeContracts} {t('dashboard.activeContracts').toLowerCase()}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center">
|
<div className="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center">
|
||||||
@@ -413,12 +415,12 @@ export default function Home() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-medium text-gray-600 uppercase tracking-wide">
|
<p className="text-xs font-medium text-gray-600 uppercase tracking-wide">
|
||||||
Pending Tasks
|
{t('dashboard.pendingTasks')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-2xl font-bold text-gray-900">
|
<p className="text-2xl font-bold text-gray-900">
|
||||||
{stats.pendingTasks}
|
{stats.pendingTasks}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-gray-500 mt-1">Awaiting start</p>
|
<p className="text-xs text-gray-500 mt-1">Oczekują rozpoczęcia</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-10 h-10 bg-yellow-100 rounded-lg flex items-center justify-center">
|
<div className="w-10 h-10 bg-yellow-100 rounded-lg flex items-center justify-center">
|
||||||
<svg
|
<svg
|
||||||
@@ -444,12 +446,12 @@ export default function Home() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-medium text-gray-600 uppercase tracking-wide">
|
<p className="text-xs font-medium text-gray-600 uppercase tracking-wide">
|
||||||
In Progress
|
{t('dashboard.inProgressTasks')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-2xl font-bold text-gray-900">
|
<p className="text-2xl font-bold text-gray-900">
|
||||||
{stats.inProgressTasks}
|
{stats.inProgressTasks}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-blue-600 mt-1">Active tasks</p>
|
<p className="text-xs text-blue-600 mt-1">Aktywne zadania</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
|
<div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
|
||||||
<svg
|
<svg
|
||||||
@@ -475,12 +477,12 @@ export default function Home() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-medium text-gray-600 uppercase tracking-wide">
|
<p className="text-xs font-medium text-gray-600 uppercase tracking-wide">
|
||||||
Completed
|
{t('dashboard.completedTasks')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-2xl font-bold text-gray-900">
|
<p className="text-2xl font-bold text-gray-900">
|
||||||
{stats.completedTasks}
|
{stats.completedTasks}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-green-600 mt-1">Tasks done</p>
|
<p className="text-xs text-green-600 mt-1">Zadania wykonane</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center">
|
<div className="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center">
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
<PageContainer>
|
<PageContainer>
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="text-center py-8">
|
<CardContent className="text-center py-8">
|
||||||
<p className="text-red-600 text-lg">Project not found.</p>
|
<p className="text-red-600 text-lg">Projekt nie został znaleziony.</p>
|
||||||
<Link href="/projects" className="mt-4 inline-block">
|
<Link href="/projects" className="mt-4 inline-block">
|
||||||
<Button variant="primary">Back to Projects</Button>
|
<Button variant="primary">Powrót do projektów</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -55,10 +55,10 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
{daysRemaining !== null && (
|
{daysRemaining !== null && (
|
||||||
<Badge variant={getDeadlineVariant(daysRemaining)} size="md">
|
<Badge variant={getDeadlineVariant(daysRemaining)} size="md">
|
||||||
{daysRemaining === 0
|
{daysRemaining === 0
|
||||||
? "Due Today"
|
? "Termin dzisiaj"
|
||||||
: daysRemaining > 0
|
: daysRemaining > 0
|
||||||
? `${daysRemaining} days left`
|
? `${daysRemaining} dni pozostało`
|
||||||
: `${Math.abs(daysRemaining)} days overdue`}
|
: `${Math.abs(daysRemaining)} dni po terminie`}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
<Link href="/projects">
|
<Link href="/projects">
|
||||||
@@ -76,7 +76,7 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
d="M15 19l-7-7 7-7"
|
d="M15 19l-7-7 7-7"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
Back to Projects
|
Powrót do projektów
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href={`/projects/${id}/edit`}>
|
<Link href={`/projects/${id}/edit`}>
|
||||||
@@ -94,7 +94,7 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
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"
|
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>
|
</svg>
|
||||||
Edit Project
|
Edytuj projekt
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@@ -108,7 +108,7 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
{" "}
|
{" "}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="text-xl font-semibold text-gray-900">
|
<h2 className="text-xl font-semibold text-gray-900">
|
||||||
Project Information
|
Informacje o projekcie
|
||||||
</h2>
|
</h2>
|
||||||
<Badge
|
<Badge
|
||||||
variant={
|
variant={
|
||||||
@@ -123,12 +123,12 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
{project.project_type === "design"
|
{project.project_type === "design"
|
||||||
? "Design (P)"
|
? "Projektowanie (P)"
|
||||||
: project.project_type === "construction"
|
: project.project_type === "construction"
|
||||||
? "Construction (R)"
|
? "Realizacja (R)"
|
||||||
: project.project_type === "design+construction"
|
: project.project_type === "design+construction"
|
||||||
? "Design + Construction (P+R)"
|
? "Projektowanie + Realizacja (P+R)"
|
||||||
: "Unknown"}
|
: "Nieznany"}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -136,7 +136,7 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-sm font-medium text-gray-500 block mb-1">
|
<span className="text-sm font-medium text-gray-500 block mb-1">
|
||||||
Location
|
Lokalizacja
|
||||||
</span>
|
</span>
|
||||||
<p className="text-gray-900 font-medium">
|
<p className="text-gray-900 font-medium">
|
||||||
{project.city || "N/A"}
|
{project.city || "N/A"}
|
||||||
@@ -144,7 +144,7 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-sm font-medium text-gray-500 block mb-1">
|
<span className="text-sm font-medium text-gray-500 block mb-1">
|
||||||
Address
|
Adres
|
||||||
</span>
|
</span>
|
||||||
<p className="text-gray-900 font-medium">
|
<p className="text-gray-900 font-medium">
|
||||||
{project.address || "N/A"}
|
{project.address || "N/A"}
|
||||||
@@ -152,7 +152,7 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-sm font-medium text-gray-500 block mb-1">
|
<span className="text-sm font-medium text-gray-500 block mb-1">
|
||||||
Plot
|
Działka
|
||||||
</span>
|
</span>
|
||||||
<p className="text-gray-900 font-medium">
|
<p className="text-gray-900 font-medium">
|
||||||
{project.plot || "N/A"}
|
{project.plot || "N/A"}
|
||||||
@@ -160,7 +160,7 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-sm font-medium text-gray-500 block mb-1">
|
<span className="text-sm font-medium text-gray-500 block mb-1">
|
||||||
District
|
Dzielnica
|
||||||
</span>
|
</span>
|
||||||
<p className="text-gray-900 font-medium">
|
<p className="text-gray-900 font-medium">
|
||||||
{project.district || "N/A"}
|
{project.district || "N/A"}
|
||||||
@@ -168,7 +168,7 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-sm font-medium text-gray-500 block mb-1">
|
<span className="text-sm font-medium text-gray-500 block mb-1">
|
||||||
Unit
|
Jednostka
|
||||||
</span>
|
</span>
|
||||||
<p className="text-gray-900 font-medium">
|
<p className="text-gray-900 font-medium">
|
||||||
{project.unit || "N/A"}
|
{project.unit || "N/A"}
|
||||||
@@ -176,7 +176,7 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
</div>{" "}
|
</div>{" "}
|
||||||
<div>
|
<div>
|
||||||
<span className="text-sm font-medium text-gray-500 block mb-1">
|
<span className="text-sm font-medium text-gray-500 block mb-1">
|
||||||
Deadline
|
Termin zakończenia
|
||||||
</span>
|
</span>
|
||||||
<p className="text-gray-900 font-medium">
|
<p className="text-gray-900 font-medium">
|
||||||
{project.finish_date
|
{project.finish_date
|
||||||
@@ -194,7 +194,7 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-sm font-medium text-gray-500 block mb-1">
|
<span className="text-sm font-medium text-gray-500 block mb-1">
|
||||||
Investment Number
|
Numer inwestycji
|
||||||
</span>
|
</span>
|
||||||
<p className="text-gray-900 font-medium">
|
<p className="text-gray-900 font-medium">
|
||||||
{project.investment_number || "N/A"}
|
{project.investment_number || "N/A"}
|
||||||
@@ -205,7 +205,7 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
{project.contact && (
|
{project.contact && (
|
||||||
<div className="border-t pt-4">
|
<div className="border-t pt-4">
|
||||||
<span className="text-sm font-medium text-gray-500 block mb-1">
|
<span className="text-sm font-medium text-gray-500 block mb-1">
|
||||||
Contact
|
Kontakt
|
||||||
</span>
|
</span>
|
||||||
<p className="text-gray-900 font-medium">{project.contact}</p>
|
<p className="text-gray-900 font-medium">{project.contact}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -214,7 +214,7 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
{project.coordinates && (
|
{project.coordinates && (
|
||||||
<div className="border-t pt-4">
|
<div className="border-t pt-4">
|
||||||
<span className="text-sm font-medium text-gray-500 block mb-1">
|
<span className="text-sm font-medium text-gray-500 block mb-1">
|
||||||
Coordinates
|
Współrzędne
|
||||||
</span>
|
</span>
|
||||||
<p className="text-gray-900 font-medium font-mono text-sm">
|
<p className="text-gray-900 font-medium font-mono text-sm">
|
||||||
{formatCoordinates(project.coordinates)}
|
{formatCoordinates(project.coordinates)}
|
||||||
@@ -237,14 +237,14 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<h2 className="text-xl font-semibold text-gray-900">
|
<h2 className="text-xl font-semibold text-gray-900">
|
||||||
Contract Details
|
Szczegóły umowy
|
||||||
</h2>
|
</h2>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-sm font-medium text-gray-500 block mb-1">
|
<span className="text-sm font-medium text-gray-500 block mb-1">
|
||||||
Contract Number
|
Numer umowy
|
||||||
</span>
|
</span>
|
||||||
<p className="text-gray-900 font-medium">
|
<p className="text-gray-900 font-medium">
|
||||||
{project.contract_number || "N/A"}
|
{project.contract_number || "N/A"}
|
||||||
@@ -252,7 +252,7 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-sm font-medium text-gray-500 block mb-1">
|
<span className="text-sm font-medium text-gray-500 block mb-1">
|
||||||
Contract Name
|
Nazwa umowy
|
||||||
</span>
|
</span>
|
||||||
<p className="text-gray-900 font-medium">
|
<p className="text-gray-900 font-medium">
|
||||||
{project.contract_name || "N/A"}
|
{project.contract_name || "N/A"}
|
||||||
@@ -260,7 +260,7 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-sm font-medium text-gray-500 block mb-1">
|
<span className="text-sm font-medium text-gray-500 block mb-1">
|
||||||
Customer
|
Klient
|
||||||
</span>
|
</span>
|
||||||
<p className="text-gray-900 font-medium">
|
<p className="text-gray-900 font-medium">
|
||||||
{project.customer || "N/A"}
|
{project.customer || "N/A"}
|
||||||
@@ -268,7 +268,7 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-sm font-medium text-gray-500 block mb-1">
|
<span className="text-sm font-medium text-gray-500 block mb-1">
|
||||||
Investor
|
Inwestor
|
||||||
</span>
|
</span>
|
||||||
<p className="text-gray-900 font-medium">
|
<p className="text-gray-900 font-medium">
|
||||||
{project.investor || "N/A"}
|
{project.investor || "N/A"}
|
||||||
@@ -284,21 +284,21 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<h2 className="text-lg font-semibold text-gray-900">
|
<h2 className="text-lg font-semibold text-gray-900">
|
||||||
Project Status
|
Status projektu
|
||||||
</h2>
|
</h2>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
{" "}
|
{" "}
|
||||||
<div>
|
<div>
|
||||||
<span className="text-sm font-medium text-gray-500 block mb-2">
|
<span className="text-sm font-medium text-gray-500 block mb-2">
|
||||||
Current Status
|
Aktualny status
|
||||||
</span>
|
</span>
|
||||||
<ProjectStatusDropdown project={project} size="md" />
|
<ProjectStatusDropdown project={project} size="md" />
|
||||||
</div>
|
</div>
|
||||||
{daysRemaining !== null && (
|
{daysRemaining !== null && (
|
||||||
<div className="border-t pt-4">
|
<div className="border-t pt-4">
|
||||||
<span className="text-sm font-medium text-gray-500 block mb-2">
|
<span className="text-sm font-medium text-gray-500 block mb-2">
|
||||||
Timeline
|
Harmonogram
|
||||||
</span>
|
</span>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Badge
|
<Badge
|
||||||
@@ -306,10 +306,10 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
size="lg"
|
size="lg"
|
||||||
>
|
>
|
||||||
{daysRemaining === 0
|
{daysRemaining === 0
|
||||||
? "Due Today"
|
? "Termin dzisiaj"
|
||||||
: daysRemaining > 0
|
: daysRemaining > 0
|
||||||
? `${daysRemaining} days remaining`
|
? `${daysRemaining} dni pozostało`
|
||||||
: `${Math.abs(daysRemaining)} days overdue`}
|
: `${Math.abs(daysRemaining)} dni po terminie`}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -321,7 +321,7 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<h2 className="text-lg font-semibold text-gray-900">
|
<h2 className="text-lg font-semibold text-gray-900">
|
||||||
Quick Actions
|
Szybkie akcje
|
||||||
</h2>
|
</h2>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-3">
|
||||||
@@ -344,7 +344,7 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
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"
|
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>
|
</svg>
|
||||||
Edit Project
|
Edytuj projekt
|
||||||
</Button>
|
</Button>
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
<Link href="/projects" className="block">
|
<Link href="/projects" className="block">
|
||||||
@@ -366,7 +366,7 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
d="M15 19l-7-7 7-7"
|
d="M15 19l-7-7 7-7"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
Back to Projects
|
Powrót do projektów
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/projects/map" className="block">
|
<Link href="/projects/map" className="block">
|
||||||
@@ -388,7 +388,7 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-1.447-.894L15 4m0 13V4m0 0L9 7"
|
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-1.447-.894L15 4m0 13V4m0 0L9 7"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
View All on Map
|
Zobacz wszystkie na mapie
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -404,7 +404,7 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
{" "}
|
{" "}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="text-xl font-semibold text-gray-900">
|
<h2 className="text-xl font-semibold text-gray-900">
|
||||||
Project Location
|
Lokalizacja projektu
|
||||||
</h2>
|
</h2>
|
||||||
{project.coordinates && (
|
{project.coordinates && (
|
||||||
<Link
|
<Link
|
||||||
@@ -428,7 +428,7 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-1.447-.894L15 4m0 13V4m0 0L9 7"
|
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-1.447-.894L15 4m0 13V4m0 0L9 7"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
View on Full Map
|
Zobacz na pełnej mapie
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
@@ -454,7 +454,7 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
{/* Notes Section */}
|
{/* Notes Section */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<h2 className="text-xl font-semibold text-gray-900">Notes</h2>
|
<h2 className="text-xl font-semibold text-gray-900">Notatki</h2>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
@@ -475,10 +475,10 @@ export default async function ProjectViewPage({ params }) {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
||||||
No notes yet
|
Brak notatek
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-500">
|
<p className="text-gray-500">
|
||||||
Add your first note using the form above.
|
Dodaj swoją pierwszą notatkę używając formularza powyżej.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ import PageHeader from "@/components/ui/PageHeader";
|
|||||||
import SearchBar from "@/components/ui/SearchBar";
|
import SearchBar from "@/components/ui/SearchBar";
|
||||||
import { LoadingState } from "@/components/ui/States";
|
import { LoadingState } from "@/components/ui/States";
|
||||||
import { formatDate } from "@/lib/utils";
|
import { formatDate } from "@/lib/utils";
|
||||||
|
import { useTranslation } from "@/lib/i18n";
|
||||||
|
|
||||||
export default function ProjectListPage() {
|
export default function ProjectListPage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [projects, setProjects] = useState([]);
|
const [projects, setProjects] = useState([]);
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const [filteredProjects, setFilteredProjects] = useState([]);
|
const [filteredProjects, setFilteredProjects] = useState([]);
|
||||||
@@ -45,7 +47,7 @@ export default function ProjectListPage() {
|
|||||||
}, [searchTerm, projects]);
|
}, [searchTerm, projects]);
|
||||||
|
|
||||||
async function handleDelete(id) {
|
async function handleDelete(id) {
|
||||||
const confirmed = confirm("Are you sure you want to delete this project?");
|
const confirmed = confirm(t('projects.deleteConfirm'));
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
|
|
||||||
const res = await fetch(`/api/projects/${id}`, {
|
const res = await fetch(`/api/projects/${id}`, {
|
||||||
@@ -61,7 +63,7 @@ export default function ProjectListPage() {
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<PageHeader title="Projects" description="Manage and track your projects">
|
<PageHeader title={t('projects.title')} description={t('projects.subtitle')}>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Link href="/projects/map">
|
<Link href="/projects/map">
|
||||||
<Button variant="outline" size="lg">
|
<Button variant="outline" size="lg">
|
||||||
@@ -78,7 +80,7 @@ export default function ProjectListPage() {
|
|||||||
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7"
|
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
Map View
|
Widok mapy
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/projects/new">
|
<Link href="/projects/new">
|
||||||
@@ -96,7 +98,7 @@ export default function ProjectListPage() {
|
|||||||
d="M12 4v16m8-8H4"
|
d="M12 4v16m8-8H4"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
Add Project
|
{t('projects.newProject')}
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@@ -105,9 +107,9 @@ export default function ProjectListPage() {
|
|||||||
<SearchBar
|
<SearchBar
|
||||||
searchTerm={searchTerm}
|
searchTerm={searchTerm}
|
||||||
onSearchChange={handleSearchChange}
|
onSearchChange={handleSearchChange}
|
||||||
placeholder="Search by project name, WP, plot, or investment number..."
|
placeholder={t('projects.searchPlaceholder')}
|
||||||
resultsCount={filteredProjects.length}
|
resultsCount={filteredProjects.length}
|
||||||
resultsText="projects"
|
resultsText="projektów"
|
||||||
/>
|
/>
|
||||||
{filteredProjects.length === 0 && searchTerm ? (
|
{filteredProjects.length === 0 && searchTerm ? (
|
||||||
<Card>
|
<Card>
|
||||||
@@ -126,14 +128,13 @@ export default function ProjectListPage() {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
||||||
No projects found
|
{t('common.noResults')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-500 mb-6">
|
<p className="text-gray-500 mb-6">
|
||||||
No projects match your search criteria. Try adjusting your search
|
Brak projektów pasujących do kryteriów wyszukiwania. Spróbuj zmienić wyszukiwane frazy.
|
||||||
terms.
|
|
||||||
</p>
|
</p>
|
||||||
<Button variant="outline" onClick={() => setSearchTerm("")}>
|
<Button variant="outline" onClick={() => setSearchTerm("")}>
|
||||||
Clear Search
|
Wyczyść wyszukiwanie
|
||||||
</Button>
|
</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -154,13 +155,13 @@ export default function ProjectListPage() {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
||||||
No projects yet
|
{t('projects.noProjects')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-500 mb-6">
|
<p className="text-gray-500 mb-6">
|
||||||
Get started by creating your first project
|
{t('projects.noProjectsMessage')}
|
||||||
</p>
|
</p>
|
||||||
<Link href="/projects/new">
|
<Link href="/projects/new">
|
||||||
<Button variant="primary">Create First Project</Button>
|
<Button variant="primary">Utwórz pierwszy projekt</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -170,34 +171,34 @@ export default function ProjectListPage() {
|
|||||||
<thead>
|
<thead>
|
||||||
<tr className="bg-gray-100 border-b">
|
<tr className="bg-gray-100 border-b">
|
||||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-32">
|
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-32">
|
||||||
No.
|
Nr.
|
||||||
</th>
|
</th>
|
||||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700">
|
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700">
|
||||||
Project Name
|
{t('projects.projectName')}
|
||||||
</th>
|
</th>
|
||||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-40">
|
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-40">
|
||||||
WP
|
WP
|
||||||
</th>
|
</th>
|
||||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-20">
|
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-20">
|
||||||
City
|
{t('projects.city')}
|
||||||
</th>
|
</th>
|
||||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-40">
|
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-40">
|
||||||
Address
|
{t('projects.address')}
|
||||||
</th>
|
</th>
|
||||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-20">
|
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-20">
|
||||||
Plot
|
{t('projects.plot')}
|
||||||
</th>
|
</th>
|
||||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-24">
|
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-24">
|
||||||
Finish
|
{t('projects.finishDate')}
|
||||||
</th>
|
</th>
|
||||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-12">
|
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-12">
|
||||||
Type
|
Typ
|
||||||
</th>
|
</th>
|
||||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-24">
|
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-24">
|
||||||
Status
|
Status
|
||||||
</th>
|
</th>
|
||||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-20">
|
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-20">
|
||||||
Actions
|
Akcje
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -282,7 +283,7 @@ export default function ProjectListPage() {
|
|||||||
size="sm"
|
size="sm"
|
||||||
className="text-xs px-2 py-1"
|
className="text-xs px-2 py-1"
|
||||||
>
|
>
|
||||||
View
|
Wyświetl
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -14,8 +14,10 @@ import PageHeader from "@/components/ui/PageHeader";
|
|||||||
import SearchBar from "@/components/ui/SearchBar";
|
import SearchBar from "@/components/ui/SearchBar";
|
||||||
import FilterBar from "@/components/ui/FilterBar";
|
import FilterBar from "@/components/ui/FilterBar";
|
||||||
import { LoadingState } from "@/components/ui/States";
|
import { LoadingState } from "@/components/ui/States";
|
||||||
|
import { useTranslation } from "@/lib/i18n";
|
||||||
|
|
||||||
export default function ProjectTasksPage() {
|
export default function ProjectTasksPage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [allTasks, setAllTasks] = useState([]);
|
const [allTasks, setAllTasks] = useState([]);
|
||||||
const [filteredTasks, setFilteredTasks] = useState([]);
|
const [filteredTasks, setFilteredTasks] = useState([]);
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
@@ -148,25 +150,25 @@ export default function ProjectTasksPage() {
|
|||||||
|
|
||||||
const filterOptions = [
|
const filterOptions = [
|
||||||
{
|
{
|
||||||
label: "Status",
|
label: t('tasks.status'),
|
||||||
value: statusFilter,
|
value: statusFilter,
|
||||||
onChange: (e) => setStatusFilter(e.target.value),
|
onChange: (e) => setStatusFilter(e.target.value),
|
||||||
options: [
|
options: [
|
||||||
{ value: "all", label: "All" },
|
{ value: "all", label: t('common.all') },
|
||||||
{ value: "pending", label: "Pending" },
|
{ value: "pending", label: t('taskStatus.pending') },
|
||||||
{ value: "in_progress", label: "In Progress" },
|
{ value: "in_progress", label: t('taskStatus.in_progress') },
|
||||||
{ value: "completed", label: "Completed" },
|
{ value: "completed", label: t('taskStatus.completed') },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Priority",
|
label: t('tasks.priority'),
|
||||||
value: priorityFilter,
|
value: priorityFilter,
|
||||||
onChange: (e) => setPriorityFilter(e.target.value),
|
onChange: (e) => setPriorityFilter(e.target.value),
|
||||||
options: [
|
options: [
|
||||||
{ value: "all", label: "All" },
|
{ value: "all", label: t('common.all') },
|
||||||
{ value: "high", label: "High" },
|
{ value: "high", label: t('tasks.high') },
|
||||||
{ value: "normal", label: "Normal" },
|
{ value: "normal", label: t('tasks.medium') },
|
||||||
{ value: "low", label: "Low" },
|
{ value: "low", label: t('tasks.low') },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -174,8 +176,8 @@ export default function ProjectTasksPage() {
|
|||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Project Tasks"
|
title={t('tasks.title')}
|
||||||
description="Monitor and manage tasks across all projects"
|
description={t('tasks.subtitle')}
|
||||||
/>
|
/>
|
||||||
<SearchBar
|
<SearchBar
|
||||||
searchTerm={searchTerm}
|
searchTerm={searchTerm}
|
||||||
@@ -206,7 +208,7 @@ export default function ProjectTasksPage() {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4">
|
<div className="ml-4">
|
||||||
<p className="text-sm font-medium text-gray-600">Total Tasks</p>
|
<p className="text-sm font-medium text-gray-600">{t('dashboard.totalTasks')}</p>
|
||||||
<p className="text-2xl font-bold text-gray-900">
|
<p className="text-2xl font-bold text-gray-900">
|
||||||
{statusCounts.all}
|
{statusCounts.all}
|
||||||
</p>
|
</p>
|
||||||
@@ -233,7 +235,7 @@ export default function ProjectTasksPage() {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4">
|
<div className="ml-4">
|
||||||
<p className="text-sm font-medium text-gray-600">Pending</p>
|
<p className="text-sm font-medium text-gray-600">{t('taskStatus.pending')}</p>
|
||||||
<p className="text-2xl font-bold text-gray-900">
|
<p className="text-2xl font-bold text-gray-900">
|
||||||
{statusCounts.pending}
|
{statusCounts.pending}
|
||||||
</p>
|
</p>
|
||||||
@@ -260,7 +262,7 @@ export default function ProjectTasksPage() {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4">
|
<div className="ml-4">
|
||||||
<p className="text-sm font-medium text-gray-600">In Progress</p>
|
<p className="text-sm font-medium text-gray-600">{t('taskStatus.in_progress')}</p>
|
||||||
<p className="text-2xl font-bold text-gray-900">
|
<p className="text-2xl font-bold text-gray-900">
|
||||||
{statusCounts.in_progress}
|
{statusCounts.in_progress}
|
||||||
</p>
|
</p>
|
||||||
@@ -287,7 +289,7 @@ export default function ProjectTasksPage() {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4">
|
<div className="ml-4">
|
||||||
<p className="text-sm font-medium text-gray-600">Completed</p>
|
<p className="text-sm font-medium text-gray-600">{t('taskStatus.completed')}</p>
|
||||||
<p className="text-2xl font-bold text-gray-900">
|
<p className="text-2xl font-bold text-gray-900">
|
||||||
{statusCounts.completed}
|
{statusCounts.completed}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "@/lib/i18n";
|
||||||
|
|
||||||
export default function NoteForm({ projectId }) {
|
export default function NoteForm({ projectId }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [note, setNote] = useState("");
|
const [note, setNote] = useState("");
|
||||||
const [status, setStatus] = useState(null);
|
const [status, setStatus] = useState(null);
|
||||||
|
|
||||||
@@ -17,10 +19,10 @@ export default function NoteForm({ projectId }) {
|
|||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
setNote("");
|
setNote("");
|
||||||
setStatus("Note added");
|
setStatus(t("common.addNoteSuccess"));
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
} else {
|
} else {
|
||||||
setStatus("Failed to save note");
|
setStatus(t("common.addNoteError"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +31,7 @@ export default function NoteForm({ projectId }) {
|
|||||||
<textarea
|
<textarea
|
||||||
value={note}
|
value={note}
|
||||||
onChange={(e) => setNote(e.target.value)}
|
onChange={(e) => setNote(e.target.value)}
|
||||||
placeholder="Add a new note..."
|
placeholder={t("common.addNotePlaceholder")}
|
||||||
className="border p-2 w-full"
|
className="border p-2 w-full"
|
||||||
rows={3}
|
rows={3}
|
||||||
required
|
required
|
||||||
@@ -38,7 +40,7 @@ export default function NoteForm({ projectId }) {
|
|||||||
type="submit"
|
type="submit"
|
||||||
className="bg-blue-600 text-white px-4 py-2 rounded"
|
className="bg-blue-600 text-white px-4 py-2 rounded"
|
||||||
>
|
>
|
||||||
Add Note
|
{t("common.addNote")}
|
||||||
</button>
|
</button>
|
||||||
{status && <p className="text-sm text-gray-600">{status}</p>}
|
{status && <p className="text-sm text-gray-600">{status}</p>}
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import { Card, CardHeader, CardContent } from "@/components/ui/Card";
|
|||||||
import Button from "@/components/ui/Button";
|
import Button from "@/components/ui/Button";
|
||||||
import { Input } from "@/components/ui/Input";
|
import { Input } from "@/components/ui/Input";
|
||||||
import { formatDateForInput } from "@/lib/utils";
|
import { formatDateForInput } from "@/lib/utils";
|
||||||
|
import { useTranslation } from "@/lib/i18n";
|
||||||
|
|
||||||
export default function ProjectForm({ initialData = null }) {
|
export default function ProjectForm({ initialData = null }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [form, setForm] = useState({
|
const [form, setForm] = useState({
|
||||||
contract_id: "",
|
contract_id: "",
|
||||||
project_name: "",
|
project_name: "",
|
||||||
@@ -146,7 +148,7 @@ export default function ProjectForm({ initialData = null }) {
|
|||||||
|
|
||||||
<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">
|
||||||
Project Type <span className="text-red-500">*</span>
|
Typ projektu <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
name="project_type"
|
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"
|
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
|
required
|
||||||
>
|
>
|
||||||
<option value="design">Design (Projektowanie)</option>
|
<option value="design">{t('projectType.design')}</option>
|
||||||
<option value="construction">Construction (Realizacja)</option>
|
<option value="construction">{t('projectType.construction')}</option>
|
||||||
<option value="design+construction">
|
<option value="design+construction">
|
||||||
Design + Construction (Projektowanie + Realizacja)
|
{t('projectType.design+construction')}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</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">
|
||||||
Assigned To
|
{t('projects.assignedTo')}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
name="assigned_to"
|
name="assigned_to"
|
||||||
@@ -173,7 +175,7 @@ export default function ProjectForm({ initialData = null }) {
|
|||||||
onChange={handleChange}
|
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"
|
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) => (
|
{users.map((user) => (
|
||||||
<option key={user.id} value={user.id}>
|
<option key={user.id} value={user.id}>
|
||||||
{user.name} ({user.email})
|
{user.name} ({user.email})
|
||||||
@@ -186,92 +188,92 @@ export default function ProjectForm({ initialData = null }) {
|
|||||||
{/* Basic Information Section */}
|
{/* Basic Information Section */}
|
||||||
<div className="border-t pt-6">
|
<div className="border-t pt-6">
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-4">
|
<h3 className="text-lg font-medium text-gray-900 mb-4">
|
||||||
Basic Information
|
{t('projects.basicInformation')}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div className="md:col-span-2">
|
<div className="md:col-span-2">
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-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>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
name="project_name"
|
name="project_name"
|
||||||
value={form.project_name || ""}
|
value={form.project_name || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder="Enter project name"
|
placeholder={t('projects.enterProjectName')}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
||||||
City
|
{t('projects.city')}
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
name="city"
|
name="city"
|
||||||
value={form.city || ""}
|
value={form.city || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder="Enter city"
|
placeholder={t('projects.enterCity')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
||||||
Address
|
{t('projects.address')}
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
name="address"
|
name="address"
|
||||||
value={form.address || ""}
|
value={form.address || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder="Enter address"
|
placeholder={t('projects.enterAddress')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
||||||
Plot
|
{t('projects.plot')}
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
name="plot"
|
name="plot"
|
||||||
value={form.plot || ""}
|
value={form.plot || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder="Enter plot number"
|
placeholder={t('projects.enterPlotNumber')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
||||||
District
|
{t('projects.district')}
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
name="district"
|
name="district"
|
||||||
value={form.district || ""}
|
value={form.district || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder="Enter district"
|
placeholder={t('projects.enterDistrict')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
||||||
Unit
|
{t('projects.unit')}
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
name="unit"
|
name="unit"
|
||||||
value={form.unit || ""}
|
value={form.unit || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder="Enter unit"
|
placeholder={t('projects.enterUnit')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
||||||
Finish Date
|
{t('projects.finishDate')}
|
||||||
</label>{" "}
|
</label>
|
||||||
<Input
|
<Input
|
||||||
type="date"
|
type="date"
|
||||||
name="finish_date"
|
name="finish_date"
|
||||||
@@ -316,33 +318,33 @@ export default function ProjectForm({ initialData = null }) {
|
|||||||
|
|
||||||
<div className="md:col-span-2">
|
<div className="md:col-span-2">
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
Contact Information
|
{t('projects.contact')}
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
name="contact"
|
name="contact"
|
||||||
value={form.contact || ""}
|
value={form.contact || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder="Enter contact details"
|
placeholder={t('projects.placeholders.contact')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="md:col-span-2">
|
<div className="md:col-span-2">
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
Coordinates
|
{t('projects.coordinates')}
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
name="coordinates"
|
name="coordinates"
|
||||||
value={form.coordinates || ""}
|
value={form.coordinates || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder="e.g., 49.622958,20.629562"
|
placeholder={t('projects.placeholders.coordinates')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="md:col-span-2">
|
<div className="md:col-span-2">
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
Notes
|
{t('projects.notes')}
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
name="notes"
|
name="notes"
|
||||||
@@ -350,7 +352,7 @@ export default function ProjectForm({ initialData = null }) {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
rows={4}
|
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"
|
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>
|
||||||
</div>
|
</div>
|
||||||
@@ -364,7 +366,7 @@ export default function ProjectForm({ initialData = null }) {
|
|||||||
onClick={() => router.back()}
|
onClick={() => router.back()}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
Cancel
|
{t('common.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" variant="primary" disabled={loading}>
|
<Button type="submit" variant="primary" disabled={loading}>
|
||||||
{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"
|
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>
|
></path>
|
||||||
</svg>
|
</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"
|
d="M5 13l4 4L19 7"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
Update Project
|
{t('projects.updateProject')}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -425,7 +427,7 @@ export default function ProjectForm({ initialData = null }) {
|
|||||||
d="M12 4v16m8-8H4"
|
d="M12 4v16m8-8H4"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
Create Project
|
{t('projects.createProject')}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -3,12 +3,14 @@
|
|||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import Badge from "@/components/ui/Badge";
|
import Badge from "@/components/ui/Badge";
|
||||||
|
import { useTranslation } from "@/lib/i18n";
|
||||||
|
|
||||||
export default function ProjectStatusDropdown({
|
export default function ProjectStatusDropdown({
|
||||||
project,
|
project,
|
||||||
size = "md",
|
size = "md",
|
||||||
showDropdown = true,
|
showDropdown = true,
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [status, setStatus] = useState(project.project_status);
|
const [status, setStatus] = useState(project.project_status);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
@@ -21,19 +23,19 @@ export default function ProjectStatusDropdown({
|
|||||||
|
|
||||||
const statusConfig = {
|
const statusConfig = {
|
||||||
registered: {
|
registered: {
|
||||||
label: "Registered",
|
label: t("projectStatus.registered"),
|
||||||
variant: "secondary",
|
variant: "secondary",
|
||||||
},
|
},
|
||||||
in_progress_design: {
|
in_progress_design: {
|
||||||
label: "In Progress (Design)",
|
label: t("projectStatus.in_progress_design"),
|
||||||
variant: "primary",
|
variant: "primary",
|
||||||
},
|
},
|
||||||
in_progress_construction: {
|
in_progress_construction: {
|
||||||
label: "In Progress (Construction)",
|
label: t("projectStatus.in_progress_construction"),
|
||||||
variant: "primary",
|
variant: "primary",
|
||||||
},
|
},
|
||||||
fulfilled: {
|
fulfilled: {
|
||||||
label: "Completed",
|
label: t("projectStatus.fulfilled"),
|
||||||
variant: "success",
|
variant: "success",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ import { Card, CardHeader, CardContent } from "./ui/Card";
|
|||||||
import Button from "./ui/Button";
|
import Button from "./ui/Button";
|
||||||
import Badge from "./ui/Badge";
|
import Badge from "./ui/Badge";
|
||||||
import { formatDate } from "@/lib/utils";
|
import { formatDate } from "@/lib/utils";
|
||||||
|
import { useTranslation } from "@/lib/i18n";
|
||||||
|
|
||||||
export default function ProjectTasksSection({ projectId }) {
|
export default function ProjectTasksSection({ projectId }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [projectTasks, setProjectTasks] = useState([]);
|
const [projectTasks, setProjectTasks] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [taskNotes, setTaskNotes] = useState({});
|
const [taskNotes, setTaskNotes] = useState({});
|
||||||
@@ -180,14 +182,14 @@ export default function ProjectTasksSection({ projectId }) {
|
|||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
refetchTasks(); // Refresh the list
|
refetchTasks(); // Refresh the list
|
||||||
} else {
|
} else {
|
||||||
alert("Failed to update task status");
|
alert(t("errors.generic"));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert("Error updating task status");
|
alert(t("errors.generic"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const handleDeleteTask = async (taskId) => {
|
const handleDeleteTask = async (taskId) => {
|
||||||
if (!confirm("Are you sure you want to delete this task?")) return;
|
if (!confirm(t("common.deleteConfirm"))) return;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/project-tasks/${taskId}`, {
|
const res = await fetch(`/api/project-tasks/${taskId}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
@@ -197,7 +199,7 @@ export default function ProjectTasksSection({ projectId }) {
|
|||||||
refetchTasks(); // Refresh the list
|
refetchTasks(); // Refresh the list
|
||||||
} else {
|
} else {
|
||||||
const errorData = await res.json();
|
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) {
|
} catch (error) {
|
||||||
alert("Error deleting task: " + error.message);
|
alert("Error deleting task: " + error.message);
|
||||||
@@ -227,10 +229,10 @@ export default function ProjectTasksSection({ projectId }) {
|
|||||||
setTaskNotes((prev) => ({ ...prev, [taskId]: notes }));
|
setTaskNotes((prev) => ({ ...prev, [taskId]: notes }));
|
||||||
setNewNote((prev) => ({ ...prev, [taskId]: "" }));
|
setNewNote((prev) => ({ ...prev, [taskId]: "" }));
|
||||||
} else {
|
} else {
|
||||||
alert("Failed to add note");
|
alert(t("errors.generic"));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert("Error adding note");
|
alert(t("errors.generic"));
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingNotes((prev) => ({ ...prev, [taskId]: false }));
|
setLoadingNotes((prev) => ({ ...prev, [taskId]: false }));
|
||||||
}
|
}
|
||||||
@@ -278,10 +280,10 @@ export default function ProjectTasksSection({ projectId }) {
|
|||||||
refetchTasks();
|
refetchTasks();
|
||||||
handleCloseEditModal();
|
handleCloseEditModal();
|
||||||
} else {
|
} else {
|
||||||
alert("Failed to update task");
|
alert(t("errors.generic"));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert("Error updating task");
|
alert(t("errors.generic"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -327,10 +329,10 @@ export default function ProjectTasksSection({ projectId }) {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center justify-between">
|
<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">
|
<div className="flex items-center gap-3">
|
||||||
<Badge variant="default" className="text-sm">
|
<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>
|
</Badge>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
@@ -350,7 +352,7 @@ export default function ProjectTasksSection({ projectId }) {
|
|||||||
d="M12 4v16m8-8H4"
|
d="M12 4v16m8-8H4"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
Add Task
|
{t("tasks.newTask")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>{" "}
|
</div>{" "}
|
||||||
@@ -404,7 +406,7 @@ export default function ProjectTasksSection({ projectId }) {
|
|||||||
{/* Current Tasks */}
|
{/* Current Tasks */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<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>
|
</CardHeader>
|
||||||
<CardContent className="p-0">
|
<CardContent className="p-0">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
@@ -430,10 +432,10 @@ export default function ProjectTasksSection({ projectId }) {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-500 text-sm">
|
<p className="text-gray-500 text-sm">
|
||||||
No tasks assigned to this project yet.
|
{t("tasks.noTasksMessage")}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-400 text-xs mt-1">
|
<p className="text-gray-400 text-xs mt-1">
|
||||||
Add a task above to get started.
|
{t("tasks.addTaskMessage")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -442,22 +444,22 @@ export default function ProjectTasksSection({ projectId }) {
|
|||||||
<thead className="bg-gray-50 border-b border-gray-200">
|
<thead className="bg-gray-50 border-b border-gray-200">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Task
|
{t("tasks.taskName")}
|
||||||
</th>
|
</th>
|
||||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Priority
|
{t("tasks.priority")}
|
||||||
</th>{" "}
|
</th>{" "}
|
||||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<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>
|
||||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<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>
|
||||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Status
|
{t("tasks.status")}
|
||||||
</th>
|
</th>
|
||||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-48">
|
<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>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -503,16 +505,16 @@ export default function ProjectTasksSection({ projectId }) {
|
|||||||
variant={getPriorityVariant(task.priority)}
|
variant={getPriorityVariant(task.priority)}
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
{task.priority}
|
{t(`tasks.${task.priority}`)}
|
||||||
</Badge>
|
</Badge>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-4 text-sm text-gray-600">
|
<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>{" "}
|
||||||
<td className="px-4 py-4 text-sm text-gray-600">
|
<td className="px-4 py-4 text-sm text-gray-600">
|
||||||
{task.date_started
|
{task.date_started
|
||||||
? formatDate(task.date_started)
|
? formatDate(task.date_started)
|
||||||
: "Not started"}
|
: t("tasks.notStarted")}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-4">
|
<td className="px-4 py-4">
|
||||||
<TaskStatusDropdownSimple
|
<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"
|
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>
|
</svg>
|
||||||
Edit
|
{t("common.edit")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="danger"
|
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"
|
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>
|
</svg>
|
||||||
Delete
|
{t("common.delete")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -596,7 +598,7 @@ export default function ProjectTasksSection({ projectId }) {
|
|||||||
<td colSpan="6" className="px-4 py-3">
|
<td colSpan="6" className="px-4 py-3">
|
||||||
<div className="text-sm text-gray-700">
|
<div className="text-sm text-gray-700">
|
||||||
<span className="font-medium text-gray-900">
|
<span className="font-medium text-gray-900">
|
||||||
Description:
|
{t("tasks.description")}:
|
||||||
</span>
|
</span>
|
||||||
<p className="mt-1">{task.description}</p>
|
<p className="mt-1">{task.description}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -609,7 +611,7 @@ export default function ProjectTasksSection({ projectId }) {
|
|||||||
<td colSpan="6" className="px-4 py-4">
|
<td colSpan="6" className="px-4 py-4">
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<h5 className="text-sm font-medium text-gray-900">
|
<h5 className="text-sm font-medium text-gray-900">
|
||||||
Notes ({taskNotes[task.id]?.length || 0})
|
{t("tasks.comments")} ({taskNotes[task.id]?.length || 0})
|
||||||
</h5>
|
</h5>
|
||||||
|
|
||||||
{/* Existing Notes */}
|
{/* Existing Notes */}
|
||||||
@@ -629,7 +631,7 @@ export default function ProjectTasksSection({ projectId }) {
|
|||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
{note.is_system ? (
|
{note.is_system ? (
|
||||||
<span className="px-2 py-1 text-xs bg-blue-100 text-blue-700 rounded-full font-medium">
|
<span className="px-2 py-1 text-xs bg-blue-100 text-blue-700 rounded-full font-medium">
|
||||||
System
|
{t("admin.system")}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
{note.created_by_name && (
|
{note.created_by_name && (
|
||||||
@@ -670,7 +672,7 @@ export default function ProjectTasksSection({ projectId }) {
|
|||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Add a note..."
|
placeholder={t("tasks.addComment")}
|
||||||
value={newNote[task.id] || ""}
|
value={newNote[task.id] || ""}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setNewNote((prev) => ({
|
setNewNote((prev) => ({
|
||||||
@@ -694,7 +696,7 @@ export default function ProjectTasksSection({ projectId }) {
|
|||||||
!newNote[task.id]?.trim()
|
!newNote[task.id]?.trim()
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{loadingNotes[task.id] ? "Adding..." : "Add"}
|
{loadingNotes[task.id] ? t("common.saving") : t("common.add")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</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="bg-white rounded-lg p-6 w-full max-w-md mx-4">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h3 className="text-lg font-semibold text-gray-900">
|
<h3 className="text-lg font-semibold text-gray-900">
|
||||||
Edit Task:{" "}
|
{t("tasks.editTask")}:{" "}
|
||||||
{editingTask?.custom_task_name || editingTask?.task_name}
|
{editingTask?.custom_task_name || editingTask?.task_name}
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
<button
|
||||||
@@ -740,7 +742,7 @@ export default function ProjectTasksSection({ projectId }) {
|
|||||||
{/* Assignment */}
|
{/* Assignment */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
Assigned To
|
{t("tasks.assignedTo")}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={editTaskForm.assigned_to}
|
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"
|
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) => (
|
{users.map((user) => (
|
||||||
<option key={user.id} value={user.id}>
|
<option key={user.id} value={user.id}>
|
||||||
{user.name}
|
{user.name}
|
||||||
@@ -764,7 +766,7 @@ export default function ProjectTasksSection({ projectId }) {
|
|||||||
{/* Priority */}
|
{/* Priority */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
Priority
|
{t("tasks.priority")}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={editTaskForm.priority}
|
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"
|
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="">{t("common.selectOption")}</option>
|
||||||
<option value="low">Low</option>
|
<option value="low">{t("tasks.low")}</option>
|
||||||
<option value="normal">Normal</option>
|
<option value="normal">{t("tasks.normal")}</option>
|
||||||
<option value="high">High</option>
|
<option value="high">{t("tasks.high")}</option>
|
||||||
<option value="urgent">Urgent</option>
|
<option value="urgent">{t("tasks.urgent")}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Date Started */}
|
{/* Date Started */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
Date Started
|
{t("tasks.dateStarted")}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
@@ -805,7 +807,7 @@ export default function ProjectTasksSection({ projectId }) {
|
|||||||
{/* Status */}
|
{/* Status */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
Status
|
{t("tasks.status")}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={editTaskForm.status}
|
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"
|
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="">{t("common.selectOption")}</option>
|
||||||
<option value="pending">Pending</option>
|
<option value="pending">{t("taskStatus.pending")}</option>
|
||||||
<option value="in_progress">In Progress</option>
|
<option value="in_progress">{t("taskStatus.in_progress")}</option>
|
||||||
<option value="completed">Completed</option>
|
<option value="completed">{t("taskStatus.completed")}</option>
|
||||||
<option value="cancelled">Cancelled</option>
|
<option value="cancelled">{t("taskStatus.cancelled")}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-end gap-3 mt-6">
|
<div className="flex items-center justify-end gap-3 mt-6">
|
||||||
<Button variant="outline" onClick={handleCloseEditModal}>
|
<Button variant="outline" onClick={handleCloseEditModal}>
|
||||||
Cancel
|
{t("common.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="primary" onClick={handleUpdateTask}>
|
<Button variant="primary" onClick={handleUpdateTask}>
|
||||||
Update Task
|
{t("tasks.updateTask")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,13 +3,16 @@
|
|||||||
import { useState, useRef, useEffect } from "react";
|
import { useState, useRef, useEffect } from "react";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import Badge from "@/components/ui/Badge";
|
import Badge from "@/components/ui/Badge";
|
||||||
|
import { useTranslation } from "@/lib/i18n";
|
||||||
|
|
||||||
export default function TaskStatusDropdown({
|
export default function TaskStatusDropdown({
|
||||||
task,
|
task,
|
||||||
size = "sm",
|
size = "sm",
|
||||||
showDropdown = true,
|
showDropdown = true,
|
||||||
onStatusChange,
|
onStatusChange,
|
||||||
}) { const [status, setStatus] = useState(task.status);
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [status, setStatus] = useState(task.status);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [dropdownPosition, setDropdownPosition] = useState({ x: 0, y: 0, position: 'bottom' });
|
const [dropdownPosition, setDropdownPosition] = useState({ x: 0, y: 0, position: 'bottom' });
|
||||||
@@ -23,19 +26,19 @@ export default function TaskStatusDropdown({
|
|||||||
|
|
||||||
const statusConfig = {
|
const statusConfig = {
|
||||||
pending: {
|
pending: {
|
||||||
label: "Pending",
|
label: t("taskStatus.pending"),
|
||||||
variant: "warning",
|
variant: "warning",
|
||||||
},
|
},
|
||||||
in_progress: {
|
in_progress: {
|
||||||
label: "In Progress",
|
label: t("taskStatus.in_progress"),
|
||||||
variant: "primary",
|
variant: "primary",
|
||||||
},
|
},
|
||||||
completed: {
|
completed: {
|
||||||
label: "Completed",
|
label: t("taskStatus.completed"),
|
||||||
variant: "success",
|
variant: "success",
|
||||||
},
|
},
|
||||||
cancelled: {
|
cancelled: {
|
||||||
label: "Cancelled",
|
label: t("taskStatus.cancelled"),
|
||||||
variant: "danger",
|
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 Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { useSession, signOut } from "next-auth/react";
|
import { useSession, signOut } from "next-auth/react";
|
||||||
|
import { useTranslation } from "@/lib/i18n";
|
||||||
|
import LanguageSwitcher from "./LanguageSwitcher";
|
||||||
|
|
||||||
const Navigation = () => {
|
const Navigation = () => {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const { data: session, status } = useSession();
|
const { data: session, status } = useSession();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const isActive = (path) => {
|
const isActive = (path) => {
|
||||||
if (path === "/") return pathname === "/";
|
if (path === "/") return pathname === "/";
|
||||||
@@ -18,16 +21,16 @@ const Navigation = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const navItems = [
|
const navItems = [
|
||||||
{ href: "/", label: "Dashboard" },
|
{ href: "/", label: t('navigation.dashboard') },
|
||||||
{ href: "/projects", label: "Projects" },
|
{ href: "/projects", label: t('navigation.projects') },
|
||||||
{ href: "/tasks/templates", label: "Task Templates" },
|
{ href: "/tasks/templates", label: t('navigation.taskTemplates') },
|
||||||
{ href: "/project-tasks", label: "Project Tasks" },
|
{ href: "/project-tasks", label: t('navigation.projectTasks') },
|
||||||
{ href: "/contracts", label: "Contracts" },
|
{ href: "/contracts", label: t('navigation.contracts') },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Add admin-only items
|
// Add admin-only items
|
||||||
if (session?.user?.role === 'admin') {
|
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 () => {
|
const handleSignOut = async () => {
|
||||||
@@ -45,13 +48,13 @@ const Navigation = () => {
|
|||||||
<div className="flex items-center justify-between h-16">
|
<div className="flex items-center justify-between h-16">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Link href="/" className="text-xl font-bold text-gray-900">
|
<Link href="/" className="text-xl font-bold text-gray-900">
|
||||||
Project Panel
|
{t('navigation.projectPanel')}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
{status === "loading" ? (
|
{status === "loading" ? (
|
||||||
<div className="text-gray-500">Loading...</div>
|
<div className="text-gray-500">{t('navigation.loading')}</div>
|
||||||
) : session ? (
|
) : session ? (
|
||||||
<>
|
<>
|
||||||
<div className="flex space-x-8">
|
<div className="flex space-x-8">
|
||||||
@@ -71,10 +74,12 @@ const Navigation = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-4 ml-8 pl-8 border-l border-gray-200">
|
<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="flex items-center space-x-2">
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
<div className="font-medium text-gray-900">{session.user.name}</div>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -82,17 +87,20 @@ const Navigation = () => {
|
|||||||
onClick={handleSignOut}
|
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"
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Link
|
<>
|
||||||
href="/auth/signin"
|
<LanguageSwitcher />
|
||||||
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm font-medium transition-colors"
|
<Link
|
||||||
>
|
href="/auth/signin"
|
||||||
Sign In
|
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm font-medium transition-colors"
|
||||||
</Link>
|
>
|
||||||
|
{t('navigation.signIn')}
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
859
src/lib/i18n.js
Normal file
859
src/lib/i18n.js
Normal file
@@ -0,0 +1,859 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { createContext, useContext, useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
// Translation context
|
||||||
|
const TranslationContext = createContext();
|
||||||
|
|
||||||
|
// Translation data
|
||||||
|
const translations = {
|
||||||
|
pl: {
|
||||||
|
// Navigation
|
||||||
|
navigation: {
|
||||||
|
dashboard: "Panel główny",
|
||||||
|
projects: "Projekty",
|
||||||
|
taskTemplates: "Szablony zadań",
|
||||||
|
projectTasks: "Zadania projektów",
|
||||||
|
contracts: "Umowy",
|
||||||
|
userManagement: "Zarządzanie użytkownikami",
|
||||||
|
projectPanel: "Panel Projektów",
|
||||||
|
loading: "Ładowanie...",
|
||||||
|
signOut: "Wyloguj się",
|
||||||
|
signIn: "Zaloguj się"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Common UI elements
|
||||||
|
common: {
|
||||||
|
save: "Zapisz",
|
||||||
|
cancel: "Anuluj",
|
||||||
|
delete: "Usuń",
|
||||||
|
edit: "Edytuj",
|
||||||
|
add: "Dodaj",
|
||||||
|
create: "Utwórz",
|
||||||
|
update: "Aktualizuj",
|
||||||
|
search: "Szukaj",
|
||||||
|
filter: "Filtruj",
|
||||||
|
loading: "Ładowanie...",
|
||||||
|
saving: "Zapisywanie...",
|
||||||
|
creating: "Tworzenie...",
|
||||||
|
updating: "Aktualizowanie...",
|
||||||
|
yes: "Tak",
|
||||||
|
no: "Nie",
|
||||||
|
confirm: "Potwierdź",
|
||||||
|
close: "Zamknij",
|
||||||
|
back: "Wstecz",
|
||||||
|
next: "Dalej",
|
||||||
|
previous: "Poprzedni",
|
||||||
|
page: "Strona",
|
||||||
|
of: "z",
|
||||||
|
items: "pozycji",
|
||||||
|
all: "Wszystkie",
|
||||||
|
none: "Brak",
|
||||||
|
required: "wymagane",
|
||||||
|
optional: "opcjonalne",
|
||||||
|
selectOption: "Wybierz opcję",
|
||||||
|
selectLanguage: "Wybierz język",
|
||||||
|
noResults: "Brak wyników",
|
||||||
|
error: "Błąd",
|
||||||
|
success: "Sukces",
|
||||||
|
warning: "Ostrzeżenie",
|
||||||
|
info: "Informacja",
|
||||||
|
deleteConfirm: "Czy na pewno chcesz to usunąć?",
|
||||||
|
addNote: "Dodaj notatkę",
|
||||||
|
addNoteSuccess: "Notatka została dodana",
|
||||||
|
addNoteError: "Nie udało się zapisać notatki",
|
||||||
|
addNotePlaceholder: "Dodaj nową notatkę..."
|
||||||
|
},
|
||||||
|
|
||||||
|
// Dashboard
|
||||||
|
dashboard: {
|
||||||
|
title: "Panel główny",
|
||||||
|
subtitle: "Przegląd twoich projektów i zadań",
|
||||||
|
totalProjects: "Wszystkie projekty",
|
||||||
|
activeProjects: "Aktywne projekty",
|
||||||
|
completedProjects: "Ukończone projekty",
|
||||||
|
overdueProjects: "Przeterminowane projekty",
|
||||||
|
totalContracts: "Wszystkie umowy",
|
||||||
|
activeContracts: "Aktywne umowy",
|
||||||
|
pendingTasks: "Oczekujące zadania",
|
||||||
|
inProgressTasks: "Zadania w trakcie",
|
||||||
|
completedTasks: "Ukończone zadania",
|
||||||
|
overdueTasks: "Przeterminowane zadania",
|
||||||
|
recentProjects: "Najnowsze projekty",
|
||||||
|
recentTasks: "Najnowsze zadania",
|
||||||
|
upcomingDeadlines: "Nadchodzące terminy",
|
||||||
|
projectsThisWeek: "Projekty w tym tygodniu",
|
||||||
|
tasksThisWeek: "Zadania w tym tygodniu",
|
||||||
|
completionRate: "Wskaźnik ukończenia",
|
||||||
|
viewAll: "Zobacz wszystkie",
|
||||||
|
noRecentProjects: "Brak najnowszych projektów",
|
||||||
|
noRecentTasks: "Brak najnowszych zadań",
|
||||||
|
noUpcomingDeadlines: "Brak nadchodzących terminów"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Project statuses
|
||||||
|
projectStatus: {
|
||||||
|
registered: "Zarejestrowany",
|
||||||
|
in_progress_design: "W realizacji (projektowanie)",
|
||||||
|
in_progress_construction: "W realizacji (realizacja)",
|
||||||
|
fulfilled: "Zakończony",
|
||||||
|
unknown: "Nieznany"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Project types
|
||||||
|
projectType: {
|
||||||
|
design: "Projektowanie",
|
||||||
|
construction: "Realizacja",
|
||||||
|
"design+construction": "Projektowanie + Realizacja"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Task statuses
|
||||||
|
taskStatus: {
|
||||||
|
pending: "Oczekujące",
|
||||||
|
in_progress: "W trakcie",
|
||||||
|
completed: "Ukończone",
|
||||||
|
cancelled: "Anulowane",
|
||||||
|
on_hold: "Wstrzymane"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Projects
|
||||||
|
projects: {
|
||||||
|
title: "Projekty",
|
||||||
|
subtitle: "Zarządzaj swoimi projektami",
|
||||||
|
newProject: "Nowy projekt",
|
||||||
|
editProject: "Edytuj projekt",
|
||||||
|
deleteProject: "Usuń projekt",
|
||||||
|
projectName: "Nazwa projektu",
|
||||||
|
city: "Miasto",
|
||||||
|
address: "Adres",
|
||||||
|
plot: "Działka",
|
||||||
|
district: "Dzielnica",
|
||||||
|
unit: "Jednostka",
|
||||||
|
finishDate: "Data zakończenia",
|
||||||
|
contact: "Kontakt",
|
||||||
|
coordinates: "Współrzędne",
|
||||||
|
notes: "Notatki",
|
||||||
|
assignedTo: "Przypisane do",
|
||||||
|
unassigned: "Nieprzypisane",
|
||||||
|
createdBy: "Utworzone przez",
|
||||||
|
lastModified: "Ostatnia modyfikacja",
|
||||||
|
basicInformation: "Informacje podstawowe",
|
||||||
|
locationDetails: "Szczegóły lokalizacji",
|
||||||
|
projectDetails: "Szczegóły projektu",
|
||||||
|
additionalInfo: "Dodatkowe informacje",
|
||||||
|
searchPlaceholder: "Szukaj projektów po nazwie, mieście lub adresie...",
|
||||||
|
noProjects: "Brak projektów",
|
||||||
|
noProjectsMessage: "Rozpocznij od utworzenia swojego pierwszego projektu.",
|
||||||
|
deleteConfirm: "Czy na pewno chcesz usunąć ten projekt?",
|
||||||
|
deleteSuccess: "Projekt został pomyślnie usunięty",
|
||||||
|
createSuccess: "Projekt został pomyślnie utworzony",
|
||||||
|
updateSuccess: "Projekt został pomyślnie zaktualizowany",
|
||||||
|
enterProjectName: "Wprowadź nazwę projektu",
|
||||||
|
enterCity: "Wprowadź miasto",
|
||||||
|
enterAddress: "Wprowadź adres",
|
||||||
|
enterPlotNumber: "Wprowadź numer działki",
|
||||||
|
enterDistrict: "Wprowadź dzielnicę",
|
||||||
|
enterUnit: "Wprowadź jednostkę",
|
||||||
|
enterContactDetails: "Wprowadź dane kontaktowe",
|
||||||
|
coordinatesPlaceholder: "np. 49.622958,20.629562",
|
||||||
|
enterNotes: "Wprowadź notatki..."
|
||||||
|
},
|
||||||
|
|
||||||
|
// Contracts
|
||||||
|
contracts: {
|
||||||
|
title: "Umowy",
|
||||||
|
subtitle: "Zarządzaj swoimi umowami i kontraktami",
|
||||||
|
newContract: "Nowa umowa",
|
||||||
|
editContract: "Edytuj umowę",
|
||||||
|
deleteContract: "Usuń umowę",
|
||||||
|
contractNumber: "Numer umowy",
|
||||||
|
contractName: "Nazwa umowy",
|
||||||
|
customerContractNumber: "Numer umowy klienta",
|
||||||
|
customer: "Klient",
|
||||||
|
investor: "Inwestor",
|
||||||
|
dateSigned: "Data zawarcia",
|
||||||
|
finishDate: "Data zakończenia",
|
||||||
|
searchPlaceholder: "Szukaj umów po numerze, nazwie, kliencie lub inwestorze...",
|
||||||
|
noContracts: "Brak umów",
|
||||||
|
noContractsMessage: "Rozpocznij od utworzenia swojej pierwszej umowy.",
|
||||||
|
noMatchingContracts: "Brak pasujących umów",
|
||||||
|
changeSearchCriteria: "Spróbuj zmienić kryteria wyszukiwania lub filtry.",
|
||||||
|
all: "Wszystkie",
|
||||||
|
active: "Aktywne",
|
||||||
|
withoutEndDate: "W trakcie",
|
||||||
|
expired: "Przeterminowane",
|
||||||
|
createContract: "Utwórz umowę",
|
||||||
|
enterContractNumber: "Wprowadź numer umowy",
|
||||||
|
enterContractName: "Wprowadź nazwę umowy",
|
||||||
|
enterCustomerContractNumber: "Wprowadź numer umowy klienta",
|
||||||
|
enterCustomerName: "Wprowadź nazwę klienta",
|
||||||
|
enterInvestorName: "Wprowadź nazwę inwestora",
|
||||||
|
signedOn: "Zawarcie:",
|
||||||
|
finishOn: "Zakończenie:",
|
||||||
|
customerLabel: "Zleceniodawca:",
|
||||||
|
investorLabel: "Inwestor:"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Tasks
|
||||||
|
tasks: {
|
||||||
|
title: "Zadania",
|
||||||
|
task: "zadanie",
|
||||||
|
tasks: "zadania",
|
||||||
|
subtitle: "Zarządzaj zadaniami projektów",
|
||||||
|
newTask: "Nowe zadanie",
|
||||||
|
editTask: "Edytuj zadanie",
|
||||||
|
deleteTask: "Usuń zadanie",
|
||||||
|
taskName: "Nazwa zadania",
|
||||||
|
description: "Opis",
|
||||||
|
priority: "Priorytet",
|
||||||
|
assignedTo: "Przypisane do",
|
||||||
|
dueDate: "Termin wykonania",
|
||||||
|
status: "Status",
|
||||||
|
project: "Projekt",
|
||||||
|
template: "Szablon",
|
||||||
|
searchPlaceholder: "Szukaj zadań...",
|
||||||
|
noTasks: "Brak zadań",
|
||||||
|
noTasksMessage: "Brak zadań przypisanych do tego projektu.",
|
||||||
|
addTaskMessage: "Dodaj zadanie powyżej, aby rozpocząć.",
|
||||||
|
filterBy: "Filtruj według",
|
||||||
|
sortBy: "Sortuj według",
|
||||||
|
allTasks: "Wszystkie zadania",
|
||||||
|
myTasks: "Moje zadania",
|
||||||
|
overdue: "Przeterminowane",
|
||||||
|
dueSoon: "Niedługo termin",
|
||||||
|
high: "Wysoki",
|
||||||
|
normal: "Normalny",
|
||||||
|
medium: "Średni",
|
||||||
|
low: "Niski",
|
||||||
|
dateCreated: "Data utworzenia",
|
||||||
|
dateModified: "Data modyfikacji",
|
||||||
|
daysLeft: "dni pozostało",
|
||||||
|
daysOverdue: "dni przeterminowane",
|
||||||
|
dueToday: "Termin dzisiaj",
|
||||||
|
startTask: "Rozpocznij zadanie",
|
||||||
|
completeTask: "Zakończ zadanie",
|
||||||
|
comments: "Komentarze",
|
||||||
|
addComment: "Dodaj komentarz",
|
||||||
|
noComments: "Brak komentarzy",
|
||||||
|
maxWait: "Maksymalne oczekiwanie",
|
||||||
|
dateStarted: "Data rozpoczęcia",
|
||||||
|
actions: "Działania",
|
||||||
|
urgent: "Pilne",
|
||||||
|
updateTask: "Aktualizuj zadanie",
|
||||||
|
notStarted: "Nie rozpoczęte",
|
||||||
|
days: "dni"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Task Templates
|
||||||
|
taskTemplates: {
|
||||||
|
title: "Szablony zadań",
|
||||||
|
subtitle: "Zarządzaj szablonami zadań",
|
||||||
|
newTemplate: "Nowy szablon",
|
||||||
|
editTemplate: "Edytuj szablon",
|
||||||
|
deleteTemplate: "Usuń szablon",
|
||||||
|
templateName: "Nazwa szablonu",
|
||||||
|
templateDescription: "Opis szablonu",
|
||||||
|
defaultPriority: "Domyślny priorytet",
|
||||||
|
estimatedDuration: "Szacowany czas trwania",
|
||||||
|
category: "Kategoria",
|
||||||
|
searchPlaceholder: "Szukaj szablonów...",
|
||||||
|
noTemplates: "Brak szablonów",
|
||||||
|
noTemplatesMessage: "Rozpocznij od utworzenia swojego pierwszego szablonu zadania.",
|
||||||
|
useTemplate: "Użyj szablonu",
|
||||||
|
duplicateTemplate: "Duplikuj szablon"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Forms
|
||||||
|
forms: {
|
||||||
|
validation: {
|
||||||
|
required: "To pole jest wymagane",
|
||||||
|
email: "Wprowadź prawidłowy adres email",
|
||||||
|
minLength: "Minimalna długość: {min} znaków",
|
||||||
|
maxLength: "Maksymalna długość: {max} znaków",
|
||||||
|
invalidDate: "Nieprawidłowa data",
|
||||||
|
invalidCoordinates: "Nieprawidłowe współrzędne"
|
||||||
|
},
|
||||||
|
placeholders: {
|
||||||
|
enterText: "Wprowadź tekst...",
|
||||||
|
selectDate: "Wybierz datę",
|
||||||
|
selectOption: "Wybierz opcję",
|
||||||
|
searchResults: "Szukaj wyników..."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Sorting and filtering
|
||||||
|
sorting: {
|
||||||
|
sortBy: "Sortuj według",
|
||||||
|
orderBy: "Kolejność",
|
||||||
|
ascending: "Rosnąco",
|
||||||
|
descending: "Malejąco",
|
||||||
|
name: "Nazwa",
|
||||||
|
date: "Data",
|
||||||
|
status: "Status",
|
||||||
|
priority: "Priorytet",
|
||||||
|
dateCreated: "Data utworzenia",
|
||||||
|
dateModified: "Data modyfikacji",
|
||||||
|
startDate: "Data rozpoczęcia",
|
||||||
|
finishDate: "Data zakończenia"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Date formats
|
||||||
|
dates: {
|
||||||
|
today: "Dzisiaj",
|
||||||
|
yesterday: "Wczoraj",
|
||||||
|
tomorrow: "Jutro",
|
||||||
|
daysAgo: "{count} dni temu",
|
||||||
|
inDays: "za {count} dni",
|
||||||
|
invalidDate: "Nieprawidłowa data",
|
||||||
|
selectDate: "Wybierz datę"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Map layers
|
||||||
|
mapLayers: {
|
||||||
|
polishOrthophoto: "🛰️ Polska Ortofotomapa",
|
||||||
|
polishCadastral: "📋 Polskie Dane Katastralne",
|
||||||
|
polishSpatialPlanning: "🏗️ Polskie Planowanie Przestrzenne",
|
||||||
|
lpPortalRoads: "🛣️ LP-Portal Drogi",
|
||||||
|
lpPortalStreetNames: "🏷️ LP-Portal Nazwy Ulic",
|
||||||
|
lpPortalParcels: "📐 LP-Portal Działki",
|
||||||
|
lpPortalSurveyMarkers: "📍 LP-Portal Punkty Pomiarowe",
|
||||||
|
openStreetMap: "🗺️ OpenStreetMap",
|
||||||
|
googleSatellite: "🛰️ Google Satelita",
|
||||||
|
googleRoads: "🛣️ Google Drogi",
|
||||||
|
layerControl: "Kontrola warstw",
|
||||||
|
baseLayers: "Warstwy bazowe",
|
||||||
|
overlayLayers: "Warstwy nakładkowe",
|
||||||
|
opacity: "Przezroczystość"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Authentication
|
||||||
|
auth: {
|
||||||
|
signIn: "Zaloguj się",
|
||||||
|
signOut: "Wyloguj się",
|
||||||
|
email: "Email",
|
||||||
|
password: "Hasło",
|
||||||
|
rememberMe: "Zapamiętaj mnie",
|
||||||
|
forgotPassword: "Zapomniałeś hasła?",
|
||||||
|
signInWith: "Zaloguj się przez",
|
||||||
|
signUp: "Zarejestruj się",
|
||||||
|
createAccount: "Utwórz konto",
|
||||||
|
alreadyHaveAccount: "Masz już konto?",
|
||||||
|
dontHaveAccount: "Nie masz konta?",
|
||||||
|
signInError: "Błąd logowania",
|
||||||
|
signOutError: "Błąd wylogowania",
|
||||||
|
emailRequired: "Email jest wymagany",
|
||||||
|
passwordRequired: "Hasło jest wymagane",
|
||||||
|
invalidCredentials: "Nieprawidłowe dane logowania"
|
||||||
|
},
|
||||||
|
|
||||||
|
// User roles
|
||||||
|
userRoles: {
|
||||||
|
admin: "Administrator",
|
||||||
|
user: "Użytkownik",
|
||||||
|
manager: "Menedżer",
|
||||||
|
viewer: "Czytelnik"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Error messages
|
||||||
|
errors: {
|
||||||
|
generic: "Wystąpił błąd. Spróbuj ponownie.",
|
||||||
|
network: "Błąd połączenia sieciowego",
|
||||||
|
unauthorized: "Brak autoryzacji",
|
||||||
|
forbidden: "Brak uprawnień",
|
||||||
|
notFound: "Nie znaleziono",
|
||||||
|
validation: "Błąd walidacji danych",
|
||||||
|
server: "Błąd serwera",
|
||||||
|
timeout: "Przekroczono limit czasu",
|
||||||
|
unknown: "Nieznany błąd"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Success messages
|
||||||
|
success: {
|
||||||
|
saved: "Zapisano pomyślnie",
|
||||||
|
created: "Utworzono pomyślnie",
|
||||||
|
updated: "Zaktualizowano pomyślnie",
|
||||||
|
deleted: "Usunięto pomyślnie",
|
||||||
|
sent: "Wysłano pomyślnie",
|
||||||
|
completed: "Ukończono pomyślnie"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Admin section
|
||||||
|
admin: {
|
||||||
|
title: "Administracja",
|
||||||
|
subtitle: "Zarządzaj użytkownikami systemu i uprawnieniami",
|
||||||
|
userManagement: "Zarządzanie użytkownikami",
|
||||||
|
auditLogs: "Logi audytu",
|
||||||
|
systemSettings: "Ustawienia systemu",
|
||||||
|
users: "Użytkownicy",
|
||||||
|
newUser: "Nowy użytkownik",
|
||||||
|
editUser: "Edytuj użytkownika",
|
||||||
|
deleteUser: "Usuń użytkownika",
|
||||||
|
userName: "Nazwa użytkownika",
|
||||||
|
userEmail: "Email użytkownika",
|
||||||
|
userRole: "Rola użytkownika",
|
||||||
|
active: "Aktywny",
|
||||||
|
inactive: "Nieaktywny",
|
||||||
|
lastLogin: "Ostatnie logowanie",
|
||||||
|
createdAt: "Data utworzenia",
|
||||||
|
noUsers: "Brak użytkowników",
|
||||||
|
system: "System"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
en: {
|
||||||
|
// English translations (fallback)
|
||||||
|
navigation: {
|
||||||
|
dashboard: "Dashboard",
|
||||||
|
projects: "Projects",
|
||||||
|
taskTemplates: "Task Templates",
|
||||||
|
projectTasks: "Project Tasks",
|
||||||
|
contracts: "Contracts",
|
||||||
|
userManagement: "User Management",
|
||||||
|
projectPanel: "Project Panel",
|
||||||
|
loading: "Loading...",
|
||||||
|
signOut: "Sign Out",
|
||||||
|
signIn: "Sign In"
|
||||||
|
},
|
||||||
|
|
||||||
|
common: {
|
||||||
|
save: "Save",
|
||||||
|
cancel: "Cancel",
|
||||||
|
delete: "Delete",
|
||||||
|
edit: "Edit",
|
||||||
|
add: "Add",
|
||||||
|
create: "Create",
|
||||||
|
update: "Update",
|
||||||
|
search: "Search",
|
||||||
|
filter: "Filter",
|
||||||
|
loading: "Loading...",
|
||||||
|
saving: "Saving...",
|
||||||
|
creating: "Creating...",
|
||||||
|
updating: "Updating...",
|
||||||
|
yes: "Yes",
|
||||||
|
no: "No",
|
||||||
|
confirm: "Confirm",
|
||||||
|
close: "Close",
|
||||||
|
back: "Back",
|
||||||
|
next: "Next",
|
||||||
|
previous: "Previous",
|
||||||
|
page: "Page",
|
||||||
|
of: "of",
|
||||||
|
items: "items",
|
||||||
|
all: "All",
|
||||||
|
none: "None",
|
||||||
|
required: "required",
|
||||||
|
optional: "optional",
|
||||||
|
selectOption: "Select option",
|
||||||
|
selectLanguage: "Select language",
|
||||||
|
noResults: "No results",
|
||||||
|
error: "Error",
|
||||||
|
success: "Success",
|
||||||
|
warning: "Warning",
|
||||||
|
info: "Info",
|
||||||
|
deleteConfirm: "Are you sure you want to delete this?",
|
||||||
|
addNote: "Add Note",
|
||||||
|
addNoteSuccess: "Note added",
|
||||||
|
addNoteError: "Failed to save note",
|
||||||
|
addNotePlaceholder: "Add a new note..."
|
||||||
|
},
|
||||||
|
|
||||||
|
dashboard: {
|
||||||
|
title: "Dashboard",
|
||||||
|
subtitle: "Overview of your projects and tasks",
|
||||||
|
totalProjects: "Total Projects",
|
||||||
|
activeProjects: "Active Projects",
|
||||||
|
completedProjects: "Completed Projects",
|
||||||
|
overdueProjects: "Overdue Projects",
|
||||||
|
totalContracts: "Total Contracts",
|
||||||
|
activeContracts: "Active Contracts",
|
||||||
|
pendingTasks: "Pending Tasks",
|
||||||
|
inProgressTasks: "In Progress Tasks",
|
||||||
|
completedTasks: "Completed Tasks",
|
||||||
|
overdueTasks: "Overdue Tasks",
|
||||||
|
recentProjects: "Recent Projects",
|
||||||
|
recentTasks: "Recent Tasks",
|
||||||
|
upcomingDeadlines: "Upcoming Deadlines",
|
||||||
|
projectsThisWeek: "Projects This Week",
|
||||||
|
tasksThisWeek: "Tasks This Week",
|
||||||
|
completionRate: "Completion Rate",
|
||||||
|
viewAll: "View All",
|
||||||
|
noRecentProjects: "No recent projects",
|
||||||
|
noRecentTasks: "No recent tasks",
|
||||||
|
noUpcomingDeadlines: "No upcoming deadlines"
|
||||||
|
},
|
||||||
|
|
||||||
|
projectStatus: {
|
||||||
|
registered: "Registered",
|
||||||
|
in_progress_design: "In Progress (Design)",
|
||||||
|
in_progress_construction: "In Progress (Construction)",
|
||||||
|
fulfilled: "Completed",
|
||||||
|
unknown: "Unknown"
|
||||||
|
},
|
||||||
|
|
||||||
|
projectType: {
|
||||||
|
design: "Design",
|
||||||
|
construction: "Construction",
|
||||||
|
"design+construction": "Design + Construction"
|
||||||
|
},
|
||||||
|
|
||||||
|
taskStatus: {
|
||||||
|
pending: "Pending",
|
||||||
|
in_progress: "In Progress",
|
||||||
|
completed: "Completed",
|
||||||
|
cancelled: "Cancelled",
|
||||||
|
on_hold: "On Hold"
|
||||||
|
},
|
||||||
|
|
||||||
|
projects: {
|
||||||
|
title: "Projects",
|
||||||
|
subtitle: "Manage your projects",
|
||||||
|
newProject: "New Project",
|
||||||
|
editProject: "Edit Project",
|
||||||
|
deleteProject: "Delete Project",
|
||||||
|
projectName: "Project Name",
|
||||||
|
city: "City",
|
||||||
|
address: "Address",
|
||||||
|
plot: "Plot",
|
||||||
|
district: "District",
|
||||||
|
unit: "Unit",
|
||||||
|
finishDate: "Finish Date",
|
||||||
|
contact: "Contact",
|
||||||
|
coordinates: "Coordinates",
|
||||||
|
notes: "Notes",
|
||||||
|
assignedTo: "Assigned To",
|
||||||
|
unassigned: "Unassigned",
|
||||||
|
createdBy: "Created By",
|
||||||
|
lastModified: "Last Modified",
|
||||||
|
basicInformation: "Basic Information",
|
||||||
|
locationDetails: "Location Details",
|
||||||
|
projectDetails: "Project Details",
|
||||||
|
additionalInfo: "Additional Information",
|
||||||
|
searchPlaceholder: "Search projects by name, city or address...",
|
||||||
|
noProjects: "No projects",
|
||||||
|
noProjectsMessage: "Get started by creating your first project.",
|
||||||
|
deleteConfirm: "Are you sure you want to delete this project?",
|
||||||
|
deleteSuccess: "Project deleted successfully",
|
||||||
|
createSuccess: "Project created successfully",
|
||||||
|
updateSuccess: "Project updated successfully",
|
||||||
|
enterProjectName: "Enter project name",
|
||||||
|
enterCity: "Enter city",
|
||||||
|
enterAddress: "Enter address",
|
||||||
|
enterPlotNumber: "Enter plot number",
|
||||||
|
enterDistrict: "Enter district",
|
||||||
|
enterUnit: "Enter unit",
|
||||||
|
enterContactDetails: "Enter contact details",
|
||||||
|
coordinatesPlaceholder: "e.g., 49.622958,20.629562",
|
||||||
|
enterNotes: "Enter notes..."
|
||||||
|
},
|
||||||
|
|
||||||
|
contracts: {
|
||||||
|
title: "Contracts",
|
||||||
|
subtitle: "Manage your contracts and agreements",
|
||||||
|
newContract: "New Contract",
|
||||||
|
editContract: "Edit Contract",
|
||||||
|
deleteContract: "Delete Contract",
|
||||||
|
contractNumber: "Contract Number",
|
||||||
|
contractName: "Contract Name",
|
||||||
|
customerContractNumber: "Customer Contract Number",
|
||||||
|
customer: "Customer",
|
||||||
|
investor: "Investor",
|
||||||
|
dateSigned: "Date Signed",
|
||||||
|
finishDate: "Finish Date",
|
||||||
|
searchPlaceholder: "Search contracts by number, name, customer or investor...",
|
||||||
|
noContracts: "No contracts",
|
||||||
|
noContractsMessage: "Get started by creating your first contract.",
|
||||||
|
noMatchingContracts: "No matching contracts",
|
||||||
|
changeSearchCriteria: "Try changing your search criteria or filters.",
|
||||||
|
all: "All",
|
||||||
|
active: "Active",
|
||||||
|
withoutEndDate: "In Progress",
|
||||||
|
expired: "Expired",
|
||||||
|
createContract: "Create Contract",
|
||||||
|
enterContractNumber: "Enter contract number",
|
||||||
|
enterContractName: "Enter contract name",
|
||||||
|
enterCustomerContractNumber: "Enter customer contract number",
|
||||||
|
enterCustomerName: "Enter customer name",
|
||||||
|
enterInvestorName: "Enter investor name",
|
||||||
|
signedOn: "Signed:",
|
||||||
|
finishOn: "Finish:",
|
||||||
|
customerLabel: "Customer:",
|
||||||
|
investorLabel: "Investor:"
|
||||||
|
},
|
||||||
|
|
||||||
|
tasks: {
|
||||||
|
title: "Tasks",
|
||||||
|
task: "task",
|
||||||
|
tasks: "tasks",
|
||||||
|
subtitle: "Manage project tasks",
|
||||||
|
newTask: "New Task",
|
||||||
|
editTask: "Edit Task",
|
||||||
|
deleteTask: "Delete Task",
|
||||||
|
taskName: "Task Name",
|
||||||
|
description: "Description",
|
||||||
|
priority: "Priority",
|
||||||
|
assignedTo: "Assigned To",
|
||||||
|
dueDate: "Due Date",
|
||||||
|
status: "Status",
|
||||||
|
project: "Project",
|
||||||
|
template: "Template",
|
||||||
|
searchPlaceholder: "Search tasks...",
|
||||||
|
noTasks: "No tasks",
|
||||||
|
noTasksMessage: "No tasks assigned to this project yet.",
|
||||||
|
addTaskMessage: "Add a task above to get started.",
|
||||||
|
filterBy: "Filter by",
|
||||||
|
sortBy: "Sort by",
|
||||||
|
allTasks: "All Tasks",
|
||||||
|
myTasks: "My Tasks",
|
||||||
|
overdue: "Overdue",
|
||||||
|
dueSoon: "Due Soon",
|
||||||
|
high: "High",
|
||||||
|
normal: "Normal",
|
||||||
|
medium: "Medium",
|
||||||
|
low: "Low",
|
||||||
|
dateCreated: "Date Created",
|
||||||
|
dateModified: "Date Modified",
|
||||||
|
daysLeft: "days left",
|
||||||
|
daysOverdue: "days overdue",
|
||||||
|
dueToday: "Due Today",
|
||||||
|
startTask: "Start Task",
|
||||||
|
completeTask: "Complete Task",
|
||||||
|
comments: "Comments",
|
||||||
|
addComment: "Add Comment",
|
||||||
|
noComments: "No comments",
|
||||||
|
maxWait: "Max Wait",
|
||||||
|
dateStarted: "Date Started",
|
||||||
|
actions: "Actions",
|
||||||
|
urgent: "Urgent",
|
||||||
|
updateTask: "Update Task",
|
||||||
|
notStarted: "Not started",
|
||||||
|
days: "days"
|
||||||
|
},
|
||||||
|
|
||||||
|
taskTemplates: {
|
||||||
|
title: "Task Templates",
|
||||||
|
subtitle: "Manage task templates",
|
||||||
|
newTemplate: "New Template",
|
||||||
|
editTemplate: "Edit Template",
|
||||||
|
deleteTemplate: "Delete Template",
|
||||||
|
templateName: "Template Name",
|
||||||
|
templateDescription: "Template Description",
|
||||||
|
defaultPriority: "Default Priority",
|
||||||
|
estimatedDuration: "Estimated Duration",
|
||||||
|
category: "Category",
|
||||||
|
searchPlaceholder: "Search templates...",
|
||||||
|
noTemplates: "No templates",
|
||||||
|
noTemplatesMessage: "Get started by creating your first task template.",
|
||||||
|
useTemplate: "Use Template",
|
||||||
|
duplicateTemplate: "Duplicate Template"
|
||||||
|
},
|
||||||
|
|
||||||
|
forms: {
|
||||||
|
validation: {
|
||||||
|
required: "This field is required",
|
||||||
|
email: "Please enter a valid email address",
|
||||||
|
minLength: "Minimum length: {min} characters",
|
||||||
|
maxLength: "Maximum length: {max} characters",
|
||||||
|
invalidDate: "Invalid date",
|
||||||
|
invalidCoordinates: "Invalid coordinates"
|
||||||
|
},
|
||||||
|
placeholders: {
|
||||||
|
enterText: "Enter text...",
|
||||||
|
selectDate: "Select date",
|
||||||
|
selectOption: "Select option",
|
||||||
|
searchResults: "Search results..."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
sorting: {
|
||||||
|
sortBy: "Sort by",
|
||||||
|
orderBy: "Order by",
|
||||||
|
ascending: "Ascending",
|
||||||
|
descending: "Descending",
|
||||||
|
name: "Name",
|
||||||
|
date: "Date",
|
||||||
|
status: "Status",
|
||||||
|
priority: "Priority",
|
||||||
|
dateCreated: "Date Created",
|
||||||
|
dateModified: "Date Modified",
|
||||||
|
startDate: "Start Date",
|
||||||
|
finishDate: "Finish Date"
|
||||||
|
},
|
||||||
|
|
||||||
|
dates: {
|
||||||
|
today: "Today",
|
||||||
|
yesterday: "Yesterday",
|
||||||
|
tomorrow: "Tomorrow",
|
||||||
|
daysAgo: "{count} days ago",
|
||||||
|
inDays: "in {count} days",
|
||||||
|
invalidDate: "Invalid date",
|
||||||
|
selectDate: "Select date"
|
||||||
|
},
|
||||||
|
|
||||||
|
mapLayers: {
|
||||||
|
polishOrthophoto: "🛰️ Polish Orthophoto",
|
||||||
|
polishCadastral: "📋 Polish Cadastral Data",
|
||||||
|
polishSpatialPlanning: "🏗️ Polish Spatial Planning",
|
||||||
|
lpPortalRoads: "🛣️ LP-Portal Roads",
|
||||||
|
lpPortalStreetNames: "🏷️ LP-Portal Street Names",
|
||||||
|
lpPortalParcels: "📐 LP-Portal Parcels",
|
||||||
|
lpPortalSurveyMarkers: "📍 LP-Portal Survey Markers",
|
||||||
|
openStreetMap: "🗺️ OpenStreetMap",
|
||||||
|
googleSatellite: "🛰️ Google Satellite",
|
||||||
|
googleRoads: "🛣️ Google Roads",
|
||||||
|
layerControl: "Layer Control",
|
||||||
|
baseLayers: "Base Layers",
|
||||||
|
overlayLayers: "Overlay Layers",
|
||||||
|
opacity: "Opacity"
|
||||||
|
},
|
||||||
|
|
||||||
|
auth: {
|
||||||
|
signIn: "Sign In",
|
||||||
|
signOut: "Sign Out",
|
||||||
|
email: "Email",
|
||||||
|
password: "Password",
|
||||||
|
rememberMe: "Remember Me",
|
||||||
|
forgotPassword: "Forgot Password?",
|
||||||
|
signInWith: "Sign in with",
|
||||||
|
signUp: "Sign Up",
|
||||||
|
createAccount: "Create Account",
|
||||||
|
alreadyHaveAccount: "Already have an account?",
|
||||||
|
dontHaveAccount: "Don't have an account?",
|
||||||
|
signInError: "Sign in error",
|
||||||
|
signOutError: "Sign out error",
|
||||||
|
emailRequired: "Email is required",
|
||||||
|
passwordRequired: "Password is required",
|
||||||
|
invalidCredentials: "Invalid credentials"
|
||||||
|
},
|
||||||
|
|
||||||
|
userRoles: {
|
||||||
|
admin: "Administrator",
|
||||||
|
user: "User",
|
||||||
|
manager: "Manager",
|
||||||
|
viewer: "Viewer"
|
||||||
|
},
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
generic: "An error occurred. Please try again.",
|
||||||
|
network: "Network connection error",
|
||||||
|
unauthorized: "Unauthorized",
|
||||||
|
forbidden: "Forbidden",
|
||||||
|
notFound: "Not found",
|
||||||
|
validation: "Validation error",
|
||||||
|
server: "Server error",
|
||||||
|
timeout: "Request timeout",
|
||||||
|
unknown: "Unknown error"
|
||||||
|
},
|
||||||
|
|
||||||
|
success: {
|
||||||
|
saved: "Saved successfully",
|
||||||
|
created: "Created successfully",
|
||||||
|
updated: "Updated successfully",
|
||||||
|
deleted: "Deleted successfully",
|
||||||
|
sent: "Sent successfully",
|
||||||
|
completed: "Completed successfully"
|
||||||
|
},
|
||||||
|
|
||||||
|
admin: {
|
||||||
|
title: "Administration",
|
||||||
|
userManagement: "User Management",
|
||||||
|
auditLogs: "Audit Logs",
|
||||||
|
systemSettings: "System Settings",
|
||||||
|
users: "Users",
|
||||||
|
newUser: "New User",
|
||||||
|
editUser: "Edit User",
|
||||||
|
deleteUser: "Delete User",
|
||||||
|
userName: "User Name",
|
||||||
|
userEmail: "User Email",
|
||||||
|
userRole: "User Role",
|
||||||
|
active: "Active",
|
||||||
|
inactive: "Inactive",
|
||||||
|
lastLogin: "Last Login",
|
||||||
|
createdAt: "Created At",
|
||||||
|
noUsers: "No users",
|
||||||
|
system: "System"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Translation provider component
|
||||||
|
export function TranslationProvider({ children, initialLanguage = 'pl' }) {
|
||||||
|
const [language, setLanguage] = useState(initialLanguage);
|
||||||
|
|
||||||
|
// Load language from localStorage on mount
|
||||||
|
useEffect(() => {
|
||||||
|
const savedLanguage = localStorage.getItem('app-language');
|
||||||
|
if (savedLanguage && translations[savedLanguage]) {
|
||||||
|
setLanguage(savedLanguage);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Save language to localStorage when changed
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem('app-language', language);
|
||||||
|
}, [language]);
|
||||||
|
|
||||||
|
const changeLanguage = (newLanguage) => {
|
||||||
|
if (translations[newLanguage]) {
|
||||||
|
setLanguage(newLanguage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const t = (key, replacements = {}) => {
|
||||||
|
const keys = key.split('.');
|
||||||
|
let value = translations[language];
|
||||||
|
|
||||||
|
for (const k of keys) {
|
||||||
|
if (value && typeof value === 'object' && k in value) {
|
||||||
|
value = value[k];
|
||||||
|
} else {
|
||||||
|
// Fallback to English if key not found in current language
|
||||||
|
value = translations.en;
|
||||||
|
for (const fallbackKey of keys) {
|
||||||
|
if (value && typeof value === 'object' && fallbackKey in value) {
|
||||||
|
value = value[fallbackKey];
|
||||||
|
} else {
|
||||||
|
console.warn(`Translation key not found: ${key} (language: ${language})`);
|
||||||
|
return key; // Return the key itself as fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
console.warn(`Translation value is not a string: ${key} (language: ${language})`);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace placeholders like {count}, {min}, {max}, etc.
|
||||||
|
let result = value;
|
||||||
|
Object.keys(replacements).forEach(placeholder => {
|
||||||
|
result = result.replace(new RegExp(`{${placeholder}}`, 'g'), replacements[placeholder]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TranslationContext.Provider value={{
|
||||||
|
t,
|
||||||
|
language,
|
||||||
|
changeLanguage,
|
||||||
|
availableLanguages: Object.keys(translations)
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</TranslationContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook to use translations
|
||||||
|
export function useTranslation() {
|
||||||
|
const context = useContext(TranslationContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useTranslation must be used within a TranslationProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export translations for direct access if needed
|
||||||
|
export { translations };
|
||||||
@@ -34,9 +34,9 @@ export const formatProjectType = (type) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getDeadlineText = (daysRemaining) => {
|
export const getDeadlineText = (daysRemaining) => {
|
||||||
if (daysRemaining === 0) return "Due Today";
|
if (daysRemaining === 0) return "Termin dzisiaj";
|
||||||
if (daysRemaining > 0) return `${daysRemaining} days left`;
|
if (daysRemaining > 0) return `${daysRemaining} dni pozostało`;
|
||||||
return `${Math.abs(daysRemaining)} days overdue`;
|
return `${Math.abs(daysRemaining)} dni przeterminowane`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatDate = (date, options = {}) => {
|
export const formatDate = (date, options = {}) => {
|
||||||
@@ -46,7 +46,7 @@ export const formatDate = (date, options = {}) => {
|
|||||||
const dateObj = typeof date === "string" ? new Date(date) : date;
|
const dateObj = typeof date === "string" ? new Date(date) : date;
|
||||||
|
|
||||||
if (isNaN(dateObj.getTime())) {
|
if (isNaN(dateObj.getTime())) {
|
||||||
return "Invalid date";
|
return "Nieprawidłowa data";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default to DD.MM.YYYY format
|
// Default to DD.MM.YYYY format
|
||||||
@@ -63,7 +63,7 @@ export const formatDate = (date, options = {}) => {
|
|||||||
return `${day}.${month}.${year}`;
|
return `${day}.${month}.${year}`;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error formatting date:", error);
|
console.error("Error formatting date:", error);
|
||||||
return "Invalid date";
|
return "Nieprawidłowa data";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user