feat: Implement dark mode support across components and UI elements
- Added dark mode styles to TaskStatusDropdown, TaskStatusDropdownDebug, and TaskStatusDropdownSimple components. - Introduced ThemeProvider and useTheme hook for managing theme state. - Updated Button, Card, Input, Loading, Navigation, PageContainer, PageHeader, ProjectCalendarWidget, ProjectMap, SearchBar, States, Tooltip, and other UI components to support dark mode. - Created ThemeToggle component for switching between light and dark modes. - Enhanced i18n translations for settings related to theme and language preferences. - Configured Tailwind CSS to support dark mode with class-based toggling.
This commit is contained in:
@@ -5,6 +5,91 @@
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
|
||||
/* Surface colors */
|
||||
--surface-primary: #ffffff;
|
||||
--surface-secondary: #f9fafb;
|
||||
--surface-tertiary: #f3f4f6;
|
||||
--surface-modal: #ffffff;
|
||||
--surface-card: #ffffff;
|
||||
--surface-hover: #f9fafb;
|
||||
--surface-active: #f3f4f6;
|
||||
|
||||
/* Text colors */
|
||||
--text-primary: #111827;
|
||||
--text-secondary: #6b7280;
|
||||
--text-tertiary: #9ca3af;
|
||||
--text-inverse: #ffffff;
|
||||
--text-muted: #6b7280;
|
||||
|
||||
/* Border colors */
|
||||
--border-default: #d1d5db;
|
||||
--border-hover: #9ca3af;
|
||||
--border-focus: #3b82f6;
|
||||
--border-divider: #e5e7eb;
|
||||
|
||||
/* Interactive colors */
|
||||
--interactive-primary: #3b82f6;
|
||||
--interactive-primary-hover: #2563eb;
|
||||
--interactive-secondary: #6b7280;
|
||||
--interactive-secondary-hover: #4b5563;
|
||||
--interactive-danger: #ef4444;
|
||||
--interactive-danger-hover: #dc2626;
|
||||
|
||||
/* Status colors */
|
||||
--status-success: #10b981;
|
||||
--status-warning: #f59e0b;
|
||||
--status-error: #ef4444;
|
||||
--status-info: #3b82f6;
|
||||
|
||||
/* Shadow colors */
|
||||
--shadow-default: rgba(0, 0, 0, 0.1);
|
||||
--shadow-hover: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
|
||||
/* Surface colors */
|
||||
--surface-primary: #1f2937;
|
||||
--surface-secondary: #374151;
|
||||
--surface-tertiary: #4b5563;
|
||||
--surface-modal: #1f2937;
|
||||
--surface-card: #374151;
|
||||
--surface-hover: #4b5563;
|
||||
--surface-active: #6b7280;
|
||||
|
||||
/* Text colors */
|
||||
--text-primary: #f9fafb;
|
||||
--text-secondary: #d1d5db;
|
||||
--text-tertiary: #9ca3af;
|
||||
--text-inverse: #111827;
|
||||
--text-muted: #9ca3af;
|
||||
|
||||
/* Border colors */
|
||||
--border-default: #4b5563;
|
||||
--border-hover: #6b7280;
|
||||
--border-focus: #60a5fa;
|
||||
--border-divider: #374151;
|
||||
|
||||
/* Interactive colors */
|
||||
--interactive-primary: #3b82f6;
|
||||
--interactive-primary-hover: #60a5fa;
|
||||
--interactive-secondary: #6b7280;
|
||||
--interactive-secondary-hover: #9ca3af;
|
||||
--interactive-danger: #ef4444;
|
||||
--interactive-danger-hover: #f87171;
|
||||
|
||||
/* Status colors */
|
||||
--status-success: #10b981;
|
||||
--status-warning: #f59e0b;
|
||||
--status-error: #ef4444;
|
||||
--status-info: #3b82f6;
|
||||
|
||||
/* Shadow colors */
|
||||
--shadow-default: rgba(0, 0, 0, 0.3);
|
||||
--shadow-hover: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
/* @media (prefers-color-scheme: dark) {
|
||||
@@ -29,6 +114,18 @@ body {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track:window-inactive {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
.dark ::-webkit-scrollbar-track {
|
||||
background: #374151;
|
||||
}
|
||||
|
||||
.dark ::-webkit-scrollbar-track:window-inactive {
|
||||
background: #374151;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 4px;
|
||||
@@ -38,6 +135,14 @@ body {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
|
||||
.dark ::-webkit-scrollbar-thumb {
|
||||
background: #6b7280;
|
||||
}
|
||||
|
||||
.dark ::-webkit-scrollbar-thumb:hover {
|
||||
background: #9ca3af;
|
||||
}
|
||||
|
||||
/* Focus styles */
|
||||
.focus-ring {
|
||||
@apply focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2;
|
||||
|
||||
@@ -3,6 +3,7 @@ import "./globals.css";
|
||||
import Navigation from "@/components/ui/Navigation";
|
||||
import { AuthProvider } from "@/components/auth/AuthProvider";
|
||||
import { TranslationProvider } from "@/lib/i18n";
|
||||
import { ThemeProvider } from "@/components/ThemeProvider";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
@@ -23,14 +24,16 @@ export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="pl">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased bg-background text-foreground`}
|
||||
>
|
||||
<TranslationProvider initialLanguage="pl">
|
||||
<AuthProvider>
|
||||
<Navigation />
|
||||
<main>{children}</main>
|
||||
</AuthProvider>
|
||||
</TranslationProvider>
|
||||
<ThemeProvider>
|
||||
<TranslationProvider initialLanguage="pl">
|
||||
<AuthProvider>
|
||||
<Navigation />
|
||||
<main>{children}</main>
|
||||
</AuthProvider>
|
||||
</TranslationProvider>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -12,8 +12,8 @@ import { formatProjectStatus } from "@/lib/utils";
|
||||
// Loading component that can access translations
|
||||
function MapLoadingComponent({ t }) {
|
||||
return (
|
||||
<div className="w-full h-96 bg-gray-100 animate-pulse rounded-lg flex items-center justify-center">
|
||||
<span className="text-gray-500">{t ? t('map.loadingMap') : 'Loading map...'}</span>
|
||||
<div className="w-full h-96 bg-gray-100 dark:bg-gray-700 animate-pulse rounded-lg flex items-center justify-center">
|
||||
<span className="text-gray-500 dark:text-gray-400">{t ? t('map.loadingMap') : 'Loading map...'}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -410,11 +410,11 @@ function ProjectsMapPageContent() {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="fixed inset-0 bg-gray-50 flex items-center justify-center">
|
||||
<div className="fixed inset-0 bg-gray-50 dark:bg-gray-900 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="w-12 h-12 mx-auto mb-4 border-4 border-blue-200 border-t-blue-600 rounded-full animate-spin"></div>
|
||||
<p className="text-gray-600 font-medium">{t('map.loadingProjectsMap')}</p>
|
||||
<p className="text-sm text-gray-500 mt-2">
|
||||
<div className="w-12 h-12 mx-auto mb-4 border-4 border-blue-200 dark:border-blue-800 border-t-blue-600 dark:border-t-blue-400 rounded-full animate-spin"></div>
|
||||
<p className="text-gray-600 dark:text-gray-300 font-medium">{t('map.loadingProjectsMap')}</p>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mt-2">
|
||||
{t('map.preparingMap')}
|
||||
</p>
|
||||
</div>
|
||||
@@ -422,7 +422,7 @@ function ProjectsMapPageContent() {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="fixed inset-0 bg-gray-50 overflow-hidden">
|
||||
<div className="fixed inset-0 bg-gray-50 dark:bg-gray-900 overflow-hidden">
|
||||
{/* Floating Header - Left Side */}
|
||||
<div className="absolute top-4 left-4 z-[1000]">
|
||||
{/* Title Box */}
|
||||
@@ -989,10 +989,10 @@ function ProjectsMapPageContent() {
|
||||
export default function ProjectsMapPage() {
|
||||
return (
|
||||
<Suspense fallback={
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto mb-4"></div>
|
||||
<p className="text-gray-600">Loading map...</p>
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 dark:border-blue-400 mx-auto mb-4"></div>
|
||||
<p className="text-gray-600 dark:text-gray-300">Loading map...</p>
|
||||
</div>
|
||||
</div>
|
||||
}>
|
||||
|
||||
@@ -254,13 +254,13 @@ export default function ProjectListPage() {
|
||||
<div className="px-4 pb-4 border-t border-gray-100">
|
||||
<div className="flex flex-col space-y-4 md:flex-row md:flex-wrap md:gap-4 md:space-y-0 md:items-center pt-4">
|
||||
<div className="flex flex-col space-y-2 md:flex-row md:items-center md:space-y-0 md:space-x-2">
|
||||
<label className="text-sm font-medium text-gray-700 md:text-xs md:whitespace-nowrap">
|
||||
<label className="text-sm font-medium text-gray-700 dark:text-gray-300 md:text-xs md:whitespace-nowrap">
|
||||
{t('common.status') || 'Status'}:
|
||||
</label>
|
||||
<select
|
||||
value={filters.status}
|
||||
onChange={(e) => handleFilterChange('status', e.target.value)}
|
||||
className="px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 md:px-3 md:py-1 md:text-sm"
|
||||
className="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 md:px-3 md:py-1 md:text-sm"
|
||||
>
|
||||
<option value="all">{t('common.all')}</option>
|
||||
<option value="not_finished">{t('projects.notFinished') || 'Nie zakończone'}</option>
|
||||
@@ -272,13 +272,13 @@ export default function ProjectListPage() {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-2 md:flex-row md:items-center md:space-y-0 md:space-x-2">
|
||||
<label className="text-sm font-medium text-gray-700 md:text-xs md:whitespace-nowrap">
|
||||
<label className="text-sm font-medium text-gray-700 dark:text-gray-300 md:text-xs md:whitespace-nowrap">
|
||||
{t('common.type') || 'Typ'}:
|
||||
</label>
|
||||
<select
|
||||
value={filters.type}
|
||||
onChange={(e) => handleFilterChange('type', e.target.value)}
|
||||
className="px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 md:px-3 md:py-1 md:text-sm"
|
||||
className="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 md:px-3 md:py-1 md:text-sm"
|
||||
>
|
||||
<option value="all">{t('common.all')}</option>
|
||||
<option value="design">{t('projectType.design')}</option>
|
||||
@@ -288,13 +288,13 @@ export default function ProjectListPage() {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-2 md:flex-row md:items-center md:space-y-0 md:space-x-2">
|
||||
<label className="text-sm font-medium text-gray-700 md:text-xs md:whitespace-nowrap">
|
||||
<label className="text-sm font-medium text-gray-700 dark:text-gray-300 md:text-xs md:whitespace-nowrap">
|
||||
{t('contracts.customer') || 'Klient'}:
|
||||
</label>
|
||||
<select
|
||||
value={filters.customer}
|
||||
onChange={(e) => handleFilterChange('customer', e.target.value)}
|
||||
className="px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 md:px-3 md:py-1 md:text-sm"
|
||||
className="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 md:px-3 md:py-1 md:text-sm"
|
||||
>
|
||||
<option value="all">{t('common.all')}</option>
|
||||
{customers.map((customer) => (
|
||||
@@ -313,13 +313,13 @@ export default function ProjectListPage() {
|
||||
<div className="p-4">
|
||||
<div className="flex flex-col space-y-4 md:flex-row md:flex-wrap md:gap-4 md:space-y-0 md:items-center">
|
||||
<div className="flex flex-col space-y-2 md:flex-row md:items-center md:space-y-0 md:space-x-2">
|
||||
<label className="text-sm font-medium text-gray-700 md:text-xs md:whitespace-nowrap">
|
||||
<label className="text-sm font-medium text-gray-700 dark:text-gray-300 md:text-xs md:whitespace-nowrap">
|
||||
{t('common.status') || 'Status'}:
|
||||
</label>
|
||||
<select
|
||||
value={filters.status}
|
||||
onChange={(e) => handleFilterChange('status', e.target.value)}
|
||||
className="px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 md:px-3 md:py-1 md:text-sm"
|
||||
className="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 md:px-3 md:py-1 md:text-sm"
|
||||
>
|
||||
<option value="all">{t('common.all')}</option>
|
||||
<option value="not_finished">{t('projects.notFinished') || 'Nie zakończone'}</option>
|
||||
@@ -331,13 +331,13 @@ export default function ProjectListPage() {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-2 md:flex-row md:items-center md:space-y-0 md:space-x-2">
|
||||
<label className="text-sm font-medium text-gray-700 md:text-xs md:whitespace-nowrap">
|
||||
<label className="text-sm font-medium text-gray-700 dark:text-gray-300 md:text-xs md:whitespace-nowrap">
|
||||
{t('common.type') || 'Typ'}:
|
||||
</label>
|
||||
<select
|
||||
value={filters.type}
|
||||
onChange={(e) => handleFilterChange('type', e.target.value)}
|
||||
className="px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 md:px-3 md:py-1 md:text-sm"
|
||||
className="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 md:px-3 md:py-1 md:text-sm"
|
||||
>
|
||||
<option value="all">{t('common.all')}</option>
|
||||
<option value="design">{t('projectType.design')}</option>
|
||||
@@ -347,13 +347,13 @@ export default function ProjectListPage() {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-2 md:flex-row md:items-center md:space-y-0 md:space-x-2">
|
||||
<label className="text-sm font-medium text-gray-700 md:text-xs md:whitespace-nowrap">
|
||||
<label className="text-sm font-medium text-gray-700 dark:text-gray-300 md:text-xs md:whitespace-nowrap">
|
||||
{t('contracts.customer') || 'Klient'}:
|
||||
</label>
|
||||
<select
|
||||
value={filters.customer}
|
||||
onChange={(e) => handleFilterChange('customer', e.target.value)}
|
||||
className="px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 md:px-3 md:py-1 md:text-sm"
|
||||
className="px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 md:px-3 md:py-1 md:text-sm"
|
||||
>
|
||||
<option value="all">{t('common.all')}</option>
|
||||
{customers.map((customer) => (
|
||||
@@ -437,40 +437,40 @@ export default function ProjectListPage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="bg-white rounded-lg shadow overflow-hidden">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden">
|
||||
{/* Mobile scroll container */}
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full min-w-[600px] table-fixed">
|
||||
<thead>
|
||||
<tr className="bg-gray-100 border-b">
|
||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-20 md:w-24">
|
||||
<tr className="bg-gray-100 dark:bg-gray-700 border-b dark:border-gray-600">
|
||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 dark:text-gray-300 w-20 md:w-24">
|
||||
Nr.
|
||||
</th>
|
||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-[200px] md:w-[250px]">
|
||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 dark:text-gray-300 w-[200px] md:w-[250px]">
|
||||
{t('projects.projectName')}
|
||||
</th>
|
||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-16 md:w-20 hidden sm:table-cell">
|
||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 dark:text-gray-300 w-16 md:w-20 hidden sm:table-cell">
|
||||
WP
|
||||
</th>
|
||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-14 md:w-16 hidden md:table-cell">
|
||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 dark:text-gray-300 w-14 md:w-16 hidden md:table-cell">
|
||||
{t('projects.city')}
|
||||
</th>
|
||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-20 md:w-24 hidden lg:table-cell">
|
||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 dark:text-gray-300 w-20 md:w-24 hidden lg:table-cell">
|
||||
{t('projects.address')}
|
||||
</th>
|
||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-14 md:w-16 hidden sm:table-cell">
|
||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 dark:text-gray-300 w-14 md:w-16 hidden sm:table-cell">
|
||||
{t('projects.plot')}
|
||||
</th>
|
||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-18 md:w-20 hidden md:table-cell">
|
||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 dark:text-gray-300 w-18 md:w-20 hidden md:table-cell">
|
||||
{t('projects.finishDate')}
|
||||
</th>
|
||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-10">
|
||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 dark:text-gray-300 w-10">
|
||||
{t('common.type') || 'Typ'}
|
||||
</th>
|
||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-10">
|
||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 dark:text-gray-300 w-10">
|
||||
{t('common.status') || 'Status'}
|
||||
</th>
|
||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 w-14 md:w-16">
|
||||
<th className="text-left px-2 py-3 font-semibold text-xs text-gray-700 dark:text-gray-300 w-14 md:w-16">
|
||||
{t('common.actions') || 'Akcje'}
|
||||
</th>
|
||||
</tr>
|
||||
@@ -479,8 +479,8 @@ export default function ProjectListPage() {
|
||||
{filteredProjects.map((project, index) => (
|
||||
<tr
|
||||
key={project.project_id}
|
||||
className={`border-b hover:bg-gray-50 transition-colors ${
|
||||
index % 2 === 0 ? "bg-white" : "bg-gray-25"
|
||||
className={`border-b dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors ${
|
||||
index % 2 === 0 ? "bg-white dark:bg-gray-800" : "bg-gray-25 dark:bg-gray-750"
|
||||
}`}
|
||||
>
|
||||
<td className="px-1 py-3">
|
||||
@@ -505,38 +505,38 @@ export default function ProjectListPage() {
|
||||
</Link>
|
||||
</td>
|
||||
<td
|
||||
className="px-2 py-3 text-xs text-gray-600 truncate hidden sm:table-cell"
|
||||
className="px-2 py-3 text-xs text-gray-600 dark:text-gray-400 truncate hidden sm:table-cell"
|
||||
title={project.wp}
|
||||
>
|
||||
{project.wp || "N/A"}
|
||||
</td>
|
||||
<td
|
||||
className="px-2 py-3 text-xs text-gray-600 truncate hidden md:table-cell"
|
||||
className="px-2 py-3 text-xs text-gray-600 dark:text-gray-400 truncate hidden md:table-cell"
|
||||
title={project.city}
|
||||
>
|
||||
{project.city || "N/A"}
|
||||
</td>
|
||||
<td
|
||||
className="px-2 py-3 text-xs text-gray-600 truncate hidden lg:table-cell"
|
||||
className="px-2 py-3 text-xs text-gray-600 dark:text-gray-400 truncate hidden lg:table-cell"
|
||||
title={project.address}
|
||||
>
|
||||
{project.address || "N/A"}
|
||||
</td>
|
||||
<td
|
||||
className="px-2 py-3 text-xs text-gray-600 truncate hidden sm:table-cell"
|
||||
className="px-2 py-3 text-xs text-gray-600 dark:text-gray-400 truncate hidden sm:table-cell"
|
||||
title={project.plot}
|
||||
>
|
||||
{project.plot || "N/A"}
|
||||
</td>
|
||||
<td
|
||||
className="px-2 py-3 text-xs text-gray-600 truncate hidden md:table-cell"
|
||||
className="px-2 py-3 text-xs text-gray-600 dark:text-gray-400 truncate hidden md:table-cell"
|
||||
title={project.finish_date}
|
||||
>
|
||||
{project.finish_date
|
||||
? formatDate(project.finish_date)
|
||||
: "N/A"}
|
||||
</td>
|
||||
<td className="px-2 py-3 text-xs text-gray-600 text-center font-semibold">
|
||||
<td className="px-2 py-3 text-xs text-gray-600 dark:text-gray-400 text-center font-semibold">
|
||||
{project.project_type === "design"
|
||||
? "P"
|
||||
: project.project_type === "construction"
|
||||
|
||||
64
src/app/settings/page.js
Normal file
64
src/app/settings/page.js
Normal file
@@ -0,0 +1,64 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslation } from "@/lib/i18n";
|
||||
import PageContainer from "@/components/ui/PageContainer";
|
||||
import PageHeader from "@/components/ui/PageHeader";
|
||||
import { Card, CardHeader, CardContent } from "@/components/ui/Card";
|
||||
import ThemeToggle from "@/components/ui/ThemeToggle";
|
||||
import LanguageSwitcher from "@/components/ui/LanguageSwitcher";
|
||||
|
||||
export default function SettingsPage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageHeader title={t('settings.title') || 'Settings'} />
|
||||
|
||||
<div className="max-w-2xl mx-auto space-y-6">
|
||||
{/* Appearance Settings */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{t('settings.appearance') || 'Appearance'}
|
||||
</h2>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{t('settings.theme') || 'Theme'}
|
||||
</label>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{t('settings.themeDescription') || 'Choose your preferred theme'}
|
||||
</p>
|
||||
</div>
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Language Settings */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{t('settings.language') || 'Language'}
|
||||
</h2>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{t('settings.language') || 'Language'}
|
||||
</label>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{t('settings.languageDescription') || 'Select your preferred language'}
|
||||
</p>
|
||||
</div>
|
||||
<LanguageSwitcher />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user