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:
2025-09-25 08:58:03 +02:00
parent 96333ecced
commit fd87b66b06
33 changed files with 582 additions and 259 deletions

View File

@@ -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;

View File

@@ -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>
);

View File

@@ -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>
}>

View File

@@ -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
View 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>
);
}

View File

@@ -318,43 +318,43 @@ export default function AuditLogViewer() {
)}
{/* Audit Logs Table */}
<div className="bg-white rounded-lg shadow overflow-hidden">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-600">
<thead className="bg-gray-50 dark:bg-gray-700">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Timestamp
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
User
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Action
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Resource
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
IP Address
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
Details
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-600">
{logs.map((log) => (
<tr key={log.id} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<tr key={log.id} className="hover:bg-gray-50 dark:hover:bg-gray-700">
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
{formatTimestamp(log.timestamp)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
<div>
<div className="font-medium">
{log.user_name || "Anonymous"}
</div>
<div className="text-gray-500">{log.user_email}</div>
<div className="text-gray-500 dark:text-gray-400">{log.user_email}</div>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm">
@@ -364,26 +364,26 @@ export default function AuditLogViewer() {
{log.action.replace(/_/g, " ").toUpperCase()}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
<div>
<div className="font-medium">
{log.resource_type || "N/A"}
</div>
<div className="text-gray-500">
<div className="text-gray-500 dark:text-gray-400">
ID: {log.resource_id || "N/A"}
</div>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
{log.ip_address || "Unknown"}
</td>
<td className="px-6 py-4 text-sm text-gray-500">
<td className="px-6 py-4 text-sm text-gray-500 dark:text-gray-400">
{log.details && (
<details className="cursor-pointer">
<summary className="text-blue-600 hover:text-blue-800">
<summary className="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300">
View Details
</summary>
<pre className="mt-2 text-xs bg-gray-100 p-2 rounded overflow-auto max-w-md">
<pre className="mt-2 text-xs bg-gray-100 dark:bg-gray-700 p-2 rounded overflow-auto max-w-md">
{JSON.stringify(log.details, null, 2)}
</pre>
</details>
@@ -396,7 +396,7 @@ export default function AuditLogViewer() {
</div>
{logs.length === 0 && !loading && (
<div className="text-center py-8 text-gray-500">
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
No audit logs found matching your criteria.
</div>
)}

View File

@@ -87,14 +87,14 @@ export default function FileUploadModal({
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 w-full max-w-md mx-4">
<div className="bg-surface-modal rounded-lg p-6 w-full max-w-md mx-4">
<div className="flex items-center justify-between mb-6">
<h3 className="text-lg font-semibold text-gray-900">
<h3 className="text-lg font-semibold text-text-primary">
{t('contracts.uploadDocumentTitle')}
</h3>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600"
className="text-text-tertiary hover:text-text-secondary"
disabled={uploading}
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -106,7 +106,7 @@ export default function FileUploadModal({
<div className="space-y-4">
{/* Description Input */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
<label className="block text-sm font-medium text-text-primary mb-2">
{t('contracts.descriptionOptional')}
</label>
<input
@@ -114,7 +114,7 @@ export default function FileUploadModal({
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder={t('contracts.descriptionPlaceholder')}
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-border-default rounded-md shadow-sm bg-surface-primary text-text-primary focus:outline-none focus:ring-2 focus:ring-interactive-primary focus:border-border-focus"
disabled={uploading}
/>
</div>
@@ -123,8 +123,8 @@ export default function FileUploadModal({
<div
className={`relative border-2 border-dashed rounded-lg p-8 text-center transition-colors ${
dragActive
? "border-blue-400 bg-blue-50"
: "border-gray-300 hover:border-gray-400"
? "border-interactive-primary bg-surface-hover"
: "border-border-default hover:border-border-hover"
} ${uploading ? "opacity-50 pointer-events-none" : ""}`}
onDragEnter={handleDrag}
onDragLeave={handleDrag}
@@ -146,17 +146,17 @@ export default function FileUploadModal({
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
<span className="text-sm text-gray-600">{t('contracts.uploading')}</span>
<span className="text-sm text-text-secondary">{t('contracts.uploading')}</span>
</div>
) : (
<div className="flex flex-col items-center">
<svg className="w-12 h-12 text-gray-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="w-12 h-12 text-text-tertiary mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg>
<span className="text-sm font-medium text-gray-900 mb-2">
<span className="text-sm font-medium text-text-primary mb-2">
{t('contracts.dropFilesHere')}
</span>
<span className="text-xs text-gray-500 mb-4">
<span className="text-xs text-text-secondary mb-4">
{t('contracts.supportedFiles')}
</span>
<Button

View File

@@ -154,14 +154,14 @@ export default function ProjectStatusDropdown({
</button>{" "}
{/* Status Options Dropdown */}
{isOpen && (
<div className="absolute top-full left-0 mt-1 bg-white border border-gray-200 rounded-md shadow-lg z-[9999] min-w-[140px]">
<div className="absolute top-full left-0 mt-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-md shadow-lg z-[9999] min-w-[140px]">
{Object.entries(statusConfig).map(([statusKey, config]) => (
<button
key={statusKey}
onClick={() => {
handleChange(statusKey);
}}
className="w-full text-left px-3 py-2 hover:bg-gray-50 transition-colors first:rounded-t-md last:rounded-b-md"
className="w-full text-left px-3 py-2 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors first:rounded-t-md last:rounded-b-md"
>
<Badge variant={config.variant} size="sm">
{config.label}

View File

@@ -111,8 +111,8 @@ export default function ProjectStatusDropdownDebug({
{/* Simple visible dropdown for debugging */}
{isOpen && (
<div className="absolute top-full left-0 mt-1 bg-white border-2 border-red-500 rounded-md shadow-lg z-[9999] min-w-[140px]">
<div className="bg-yellow-100 p-2 text-xs text-center border-b">
<div className="absolute top-full left-0 mt-1 bg-white dark:bg-gray-800 border-2 border-red-500 rounded-md shadow-lg z-[9999] min-w-[140px]">
<div className="bg-yellow-100 dark:bg-yellow-900 p-2 text-xs text-center border-b dark:border-gray-600">
DEBUG: Project Status Dropdown is visible
</div>
{Object.entries(statusConfig).map(([statusKey, config]) => (
@@ -122,7 +122,7 @@ export default function ProjectStatusDropdownDebug({
console.log("Project Status Option clicked:", statusKey);
handleChange(statusKey);
}}
className="w-full text-left px-3 py-2 hover:bg-gray-50 transition-colors first:rounded-t-md last:rounded-b-md"
className="w-full text-left px-3 py-2 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors first:rounded-t-md last:rounded-b-md"
>
<Badge variant={config.variant} size="sm">
{config.label}

View File

@@ -114,8 +114,8 @@ export default function ProjectStatusDropdownSimple({
{/* Simple dropdown for debugging */}
{isOpen && (
<div className="absolute top-full left-0 mt-1 bg-white border-2 border-red-500 rounded-md shadow-lg z-[9999] min-w-[140px]">
<div className="bg-yellow-100 p-2 text-xs text-center border-b">
<div className="absolute top-full left-0 mt-1 bg-white dark:bg-gray-800 border-2 border-red-500 rounded-md shadow-lg z-[9999] min-w-[140px]">
<div className="bg-yellow-100 dark:bg-yellow-900 p-2 text-xs text-center border-b dark:border-gray-600">
DEBUG: ProjectStatus Dropdown is visible
</div>
{Object.entries(statusConfig).map(([statusKey, config]) => (
@@ -125,7 +125,7 @@ export default function ProjectStatusDropdownSimple({
console.log("ProjectStatus Option clicked:", statusKey);
handleChange(statusKey);
}}
className="w-full text-left px-3 py-2 hover:bg-gray-50 transition-colors first:rounded-t-md last-rounded-b-md"
className="w-full text-left px-3 py-2 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors first:rounded-t-md last-rounded-b-md"
>
<Badge variant={config.variant} size="sm">
{config.label}

View File

@@ -331,7 +331,7 @@ export default function ProjectTasksDashboard() {
</CardHeader>
<CardContent className="space-y-3">
{tasks.length === 0 ? (
<div className="text-center py-8 text-gray-500">
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
<p className="text-sm">No tasks in this category</p>
</div>
) : (

View File

@@ -278,10 +278,10 @@ export default function ProjectTasksList() {
return "high";
};
const TaskRow = ({ task, showTimeLeft = false, showMaxWait = true }) => (
<tr className="hover:bg-gray-50 border-b border-gray-200">
<tr className="hover:bg-gray-50 dark:hover:bg-gray-700 border-b border-gray-200 dark:border-gray-600">
<td className="px-4 py-3">
<div className="flex items-center gap-2">
<span className="font-medium text-gray-900">{task.task_name}</span>
<span className="font-medium text-gray-900 dark:text-gray-100">{task.task_name}</span>
<Badge variant={getPriorityVariant(task.priority)} size="sm">
{t(`tasks.${task.priority}`)}
</Badge>
@@ -291,25 +291,25 @@ export default function ProjectTasksList() {
{" "}
<Link
href={`/projects/${task.project_id}`}
className="text-blue-600 hover:text-blue-800 font-medium"
className="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 font-medium"
>
{task.project_name}
</Link>
</td>
<td className="px-4 py-3 text-sm text-gray-600">{task.city || "N/A"}</td>
<td className="px-4 py-3 text-sm text-gray-600">
<td className="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">{task.city || "N/A"}</td>
<td className="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
{task.address || "N/A"}
</td>
<td className="px-4 py-3 text-sm text-gray-600">
<td className="px-4 py-3 text-sm text-gray-600 dark:text-gray-400">
{task.assigned_to_name ? (
<div>
<div className="font-medium">{task.assigned_to_name}</div>
<div className="font-medium text-gray-900 dark:text-gray-100">{task.assigned_to_name}</div>
{/* <div className="text-xs text-gray-500">
{task.assigned_to_email}
</div> */}
</div>
) : (
<span className="text-gray-400 italic">{t("projects.unassigned")}</span>
<span className="text-gray-400 dark:text-gray-500 italic">{t("projects.unassigned")}</span>
)}
</td>
{showTimeLeft && (
@@ -341,7 +341,7 @@ export default function ProjectTasksList() {
</div>
</td>
)}
<td className="px-4 py-3 text-sm text-gray-500">
<td className="px-4 py-3 text-sm text-gray-500 dark:text-gray-400">
{task.status === "completed" && task.date_completed ? (
<div>
<div>
@@ -386,7 +386,7 @@ export default function ProjectTasksList() {
)}
</td>
{showMaxWait && (
<td className="px-4 py-3 text-sm text-gray-500">
<td className="px-4 py-3 text-sm text-gray-500 dark:text-gray-400">
{task.max_wait_days} {t("tasks.days")}
</td>
)}
@@ -416,38 +416,38 @@ export default function ProjectTasksList() {
return (
<div className="overflow-x-auto">
<table className="w-full bg-white rounded-lg shadow-sm">
<thead className="bg-gray-50">
<table className="w-full bg-white dark:bg-gray-800 rounded-lg shadow-sm">
<thead className="bg-gray-50 dark:bg-gray-700">
<tr>
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700">
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700 dark:text-gray-300">
{t("tasks.taskName")}
</th>{" "}
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700">
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700 dark:text-gray-300">
{t("tasks.project")}
</th>
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700">
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700 dark:text-gray-300">
{t("projects.city")}
</th>
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700">
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700 dark:text-gray-300">
{t("projects.address")}
</th>
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700">
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700 dark:text-gray-300">
{t("tasks.assignedTo")}
</th>
{showTimeLeft && (
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700">
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700 dark:text-gray-300">
{t("tasks.daysLeft")}
</th>
)}
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700">
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700 dark:text-gray-300">
{t("tasks.dateCreated")}
</th>
{showMaxWait && (
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700">
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700 dark:text-gray-300">
{t("tasks.maxWait")}
</th>
)}{" "}
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700">
<th className="px-4 py-3 text-left text-sm font-medium text-gray-700 dark:text-gray-300">
{t("tasks.actions")}
</th>
</tr>
@@ -459,7 +459,7 @@ export default function ProjectTasksList() {
<tr key={`group-${groupName}`}>
<td
colSpan={colSpan}
className="px-4 py-2 bg-gray-100 font-medium text-gray-800 text-sm"
className="px-4 py-2 bg-gray-100 dark:bg-gray-700 font-medium text-gray-800 dark:text-gray-200 text-sm"
>
{groupName} ({groupTasks.length} {t("tasks.tasks")})
</td>
@@ -478,7 +478,7 @@ export default function ProjectTasksList() {
</tbody>
</table>
{filteredTasks.length === 0 && (
<div className="text-center py-8 text-gray-500">
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
<p>{t("tasks.noTasks")}</p>
</div>
)}

View File

@@ -465,42 +465,42 @@ export default function ProjectTasksSection({ projectId }) {
) : (
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-gray-50 border-b border-gray-200">
<thead className="bg-gray-50 dark:bg-gray-700 border-b border-gray-200 dark:border-gray-600">
<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 dark:text-gray-300 uppercase tracking-wider">
{t("tasks.taskName")}
</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 dark:text-gray-300 uppercase tracking-wider">
{t("tasks.priority")}
</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 dark:text-gray-300 uppercase tracking-wider">
{t("tasks.maxWait")}
</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 dark:text-gray-300 uppercase tracking-wider">
{t("tasks.dateStarted")}
</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 dark:text-gray-300 uppercase tracking-wider">
{t("tasks.status")}
</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 dark:text-gray-300 uppercase tracking-wider w-48">
{t("tasks.actions")}
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-600">
{projectTasks.map((task) => (
<React.Fragment key={task.id}>
{/* Main task row */}
<tr className="hover:bg-gray-50 transition-colors">
<tr className="hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
<td className="px-4 py-4">
<div className="flex items-center gap-2">
<h4 className="text-sm font-medium text-gray-900">
<h4 className="text-sm font-medium text-gray-900 dark:text-gray-100">
{task.task_name}
</h4>
{task.description && (
<button
onClick={() => toggleDescription(task.id)}
className="text-gray-400 hover:text-gray-600 transition-colors"
className="text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
title="Toggle description"
>
<svg
@@ -532,10 +532,10 @@ export default function ProjectTasksSection({ projectId }) {
{t(`tasks.${task.priority}`)}
</Badge>
</td>
<td className="px-4 py-4 text-sm text-gray-600">
<td className="px-4 py-4 text-sm text-gray-600 dark:text-gray-400">
{task.max_wait_days} {t("tasks.days")}
</td>{" "}
<td className="px-4 py-4 text-sm text-gray-600">
<td className="px-4 py-4 text-sm text-gray-600 dark:text-gray-400">
{task.date_started
? formatDate(task.date_started)
: t("tasks.notStarted")}

View File

@@ -140,13 +140,13 @@ export default function TaskCommentsModal({ task, isOpen, onClose }) {
className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-[9999]"
onClick={(e) => e.target === e.currentTarget && onClose()}
>
<div className="bg-white rounded-lg w-full max-w-4xl mx-4 max-h-[90vh] flex flex-col">
<div className="bg-white dark:bg-gray-800 rounded-lg w-full max-w-4xl mx-4 max-h-[90vh] flex flex-col">
{/* Header */}
<div className="p-6 border-b">
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-start justify-between mb-4">
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<h3 className="text-xl font-semibold text-gray-900">
<h3 className="text-xl font-semibold text-gray-900 dark:text-gray-100">
{task?.task_name}
</h3>
<Badge variant={getPriorityVariant(task?.priority)} size="sm">
@@ -156,13 +156,13 @@ export default function TaskCommentsModal({ task, isOpen, onClose }) {
{t(`taskStatus.${task?.status}`)}
</Badge>
</div>
<p className="text-sm text-gray-600 mb-3">
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3">
{t("tasks.project")}: <span className="font-medium">{task?.project_name}</span>
</p>
</div>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 text-xl font-semibold ml-4"
className="text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 text-xl font-semibold ml-4"
onKeyDown={handleKeyDown}
>
×
@@ -170,38 +170,38 @@ export default function TaskCommentsModal({ task, isOpen, onClose }) {
</div>
{/* Task Details Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-4 bg-gray-50 rounded-lg">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
{/* Location Information */}
{(task?.city || task?.address) && (
<div className="space-y-1">
<h4 className="text-xs font-medium text-gray-500 uppercase tracking-wide">{t("projects.locationDetails")}</h4>
<h4 className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">{t("projects.locationDetails")}</h4>
{task?.city && (
<p className="text-sm text-gray-900">{task.city}</p>
<p className="text-sm text-gray-900 dark:text-gray-100">{task.city}</p>
)}
{task?.address && (
<p className="text-xs text-gray-600">{task.address}</p>
<p className="text-xs text-gray-600 dark:text-gray-400">{task.address}</p>
)}
</div>
)}
{/* Assignment Information */}
<div className="space-y-1">
<h4 className="text-xs font-medium text-gray-500 uppercase tracking-wide">{t("tasks.assignedTo")}</h4>
<h4 className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">{t("tasks.assignedTo")}</h4>
{task?.assigned_to_name ? (
<div>
<p className="text-sm text-gray-900">{task.assigned_to_name}</p>
<p className="text-xs text-gray-600">{task.assigned_to_email}</p>
<p className="text-sm text-gray-900 dark:text-gray-100">{task.assigned_to_name}</p>
<p className="text-xs text-gray-600 dark:text-gray-400">{task.assigned_to_email}</p>
</div>
) : (
<p className="text-sm text-gray-500 italic">{t("projects.unassigned")}</p>
<p className="text-sm text-gray-500 dark:text-gray-400 italic">{t("projects.unassigned")}</p>
)}
</div>
{/* Task Timing */}
<div className="space-y-1">
<h4 className="text-xs font-medium text-gray-500 uppercase tracking-wide">{t("tasks.dateCreated")}</h4>
<h4 className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">{t("tasks.dateCreated")}</h4>
{task?.max_wait_days && (
<p className="text-xs text-gray-600">{t("tasks.maxWait")}: {task.max_wait_days} {t("tasks.days")}</p>
<p className="text-xs text-gray-600 dark:text-gray-400">{t("tasks.maxWait")}: {task.max_wait_days} {t("tasks.days")}</p>
)}
{(() => {
if (task?.status === "completed" && task?.date_completed) {
@@ -236,8 +236,8 @@ export default function TaskCommentsModal({ task, isOpen, onClose }) {
{/* Task Description */}
{task?.description && (
<div className="space-y-1 md:col-span-2 lg:col-span-3">
<h4 className="text-xs font-medium text-gray-500 uppercase tracking-wide">{t("tasks.description")}</h4>
<p className="text-sm text-gray-900 leading-relaxed">{task.description}</p>
<h4 className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">{t("tasks.description")}</h4>
<p className="text-sm text-gray-900 dark:text-gray-100 leading-relaxed">{task.description}</p>
</div>
)}
</div>
@@ -248,15 +248,15 @@ export default function TaskCommentsModal({ task, isOpen, onClose }) {
{loading ? (
<div className="text-center py-8">
<div className="animate-pulse space-y-4">
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
<div className="h-4 bg-gray-200 rounded w-1/2"></div>
<div className="h-4 bg-gray-200 rounded w-2/3"></div>
<div className="h-4 bg-gray-200 dark:bg-gray-600 rounded w-3/4"></div>
<div className="h-4 bg-gray-200 dark:bg-gray-600 rounded w-1/2"></div>
<div className="h-4 bg-gray-200 dark:bg-gray-600 rounded w-2/3"></div>
</div>
</div>
) : (
<div className="space-y-4">
<div className="flex items-center gap-2 mb-4">
<h5 className="text-lg font-medium text-gray-900">
<h5 className="text-lg font-medium text-gray-900 dark:text-gray-100">
{t("tasks.comments")}
</h5>
<Badge variant="secondary" size="sm">
@@ -265,8 +265,8 @@ export default function TaskCommentsModal({ task, isOpen, onClose }) {
</div>
{notes.length === 0 ? (
<div className="text-center py-12 text-gray-500">
<div className="w-16 h-16 mx-auto mb-4 bg-gray-100 rounded-full flex items-center justify-center">
<div className="text-center py-12 text-gray-500 dark:text-gray-400">
<div className="w-16 h-16 mx-auto mb-4 bg-gray-100 dark:bg-gray-700 rounded-full flex items-center justify-center">
<span className="text-2xl">💬</span>
</div>
<p className="text-lg font-medium mb-1">{t("tasks.noComments")}</p>
@@ -279,37 +279,37 @@ export default function TaskCommentsModal({ task, isOpen, onClose }) {
key={note.note_id}
className={`p-4 rounded-lg border flex justify-between items-start transition-colors ${
note.is_system
? "bg-blue-50 border-blue-200 hover:bg-blue-100"
: "bg-white border-gray-200 hover:bg-gray-50 shadow-sm"
? "bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-700 hover:bg-blue-100 dark:hover:bg-blue-900/30"
: "bg-white dark:bg-gray-700 border-gray-200 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600 shadow-sm"
}`}
>
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
{note.is_system ? (
<span className="px-2 py-1 text-xs bg-blue-100 text-blue-700 rounded-full font-medium flex items-center gap-1">
<span className="px-2 py-1 text-xs bg-blue-100 dark:bg-blue-800 text-blue-700 dark:text-blue-300 rounded-full font-medium flex items-center gap-1">
<span>🤖</span>
{t("admin.system")}
</span>
) : (
<span className="px-2 py-1 text-xs bg-gray-100 text-gray-700 rounded-full font-medium flex items-center gap-1">
<span className="px-2 py-1 text-xs bg-gray-100 dark:bg-gray-600 text-gray-700 dark:text-gray-300 rounded-full font-medium flex items-center gap-1">
<span>👤</span>
{note.created_by_name || t("userRoles.user")}
</span>
)}
<span className="text-xs text-gray-500 font-medium">
<span className="text-xs text-gray-500 dark:text-gray-400 font-medium">
{formatDate(note.note_date, {
includeTime: true,
})}
</span>
</div>
<p className="text-sm text-gray-800 leading-relaxed">
<p className="text-sm text-gray-800 dark:text-gray-200 leading-relaxed">
{note.note}
</p>
</div>
{!note.is_system && (
<button
onClick={() => handleDeleteNote(note.note_id)}
className="ml-3 p-1 text-red-400 hover:text-red-600 hover:bg-red-50 rounded transition-colors"
className="ml-3 p-1 text-red-400 hover:text-red-600 dark:text-red-500 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 rounded transition-colors"
title={t("common.delete")}
>
<span className="text-sm">🗑</span>
@@ -324,11 +324,11 @@ export default function TaskCommentsModal({ task, isOpen, onClose }) {
</div>
{/* Footer - Add new comment */}
<div className="p-6 border-t bg-gradient-to-r from-gray-50 to-gray-100">
<div className="p-6 border-t border-gray-200 dark:border-gray-700 bg-gradient-to-r from-gray-50 to-gray-100 dark:from-gray-700 dark:to-gray-600">
<div className="space-y-4">
<div className="flex items-center gap-2">
<span className="text-lg">💬</span>
<label className="text-sm font-medium text-gray-700">
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">
{t("tasks.addComment")}
</label>
</div>
@@ -336,12 +336,12 @@ export default function TaskCommentsModal({ task, isOpen, onClose }) {
value={newNote}
onChange={(e) => setNewNote(e.target.value)}
placeholder={t("common.addNotePlaceholder")}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none shadow-sm"
className="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none shadow-sm"
rows={3}
onKeyDown={handleKeyDown}
/>
<div className="flex items-center justify-between">
<p className="text-xs text-gray-500 flex items-center gap-1">
<p className="text-xs text-gray-500 dark:text-gray-400 flex items-center gap-1">
<span></span>
Naciśnij Ctrl+Enter aby wysłać lub Escape aby zamknąć
</p>

View File

@@ -151,8 +151,8 @@ export default function TaskStatusDropdown({
</button>{" "}
{/* Simple dropdown for debugging */}
{isOpen && (
<div className="absolute top-full left-0 mt-1 bg-white border-2 border-red-500 rounded-md shadow-lg z-[9999] min-w-[120px]">
<div className="bg-yellow-100 p-2 text-xs text-center border-b">
<div className="absolute top-full left-0 mt-1 bg-white dark:bg-gray-800 border-2 border-red-500 rounded-md shadow-lg z-[9999] min-w-[120px]">
<div className="bg-yellow-100 dark:bg-yellow-900 p-2 text-xs text-center border-b dark:border-gray-600">
DEBUG: TaskStatus Dropdown is visible
</div>
{Object.entries(statusConfig).map(([statusKey, config]) => (
@@ -162,7 +162,7 @@ export default function TaskStatusDropdown({
console.log("TaskStatus Option clicked:", statusKey);
handleChange(statusKey);
}}
className="w-full text-left px-3 py-2 hover:bg-gray-50 transition-colors first:rounded-t-md last:rounded-b-md"
className="w-full text-left px-3 py-2 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors first:rounded-t-md last:rounded-b-md"
>
<Badge variant={config.variant} size="sm">
{config.label}

View File

@@ -117,8 +117,8 @@ export default function TaskStatusDropdownDebug({
{/* Simple visible dropdown for debugging */}
{isOpen && (
<div className="absolute top-full left-0 mt-1 bg-white border-2 border-red-500 rounded-md shadow-lg z-[9999] min-w-[120px]">
<div className="bg-yellow-100 p-2 text-xs text-center border-b">
<div className="absolute top-full left-0 mt-1 bg-white dark:bg-gray-800 border-2 border-red-500 rounded-md shadow-lg z-[9999] min-w-[120px]">
<div className="bg-yellow-100 dark:bg-yellow-900 p-2 text-xs text-center border-b dark:border-gray-600">
DEBUG: Dropdown is visible
</div>
{Object.entries(statusConfig).map(([statusKey, config]) => (
@@ -128,7 +128,7 @@ export default function TaskStatusDropdownDebug({
console.log("Option clicked:", statusKey);
handleChange(statusKey);
}}
className="w-full text-left px-3 py-2 hover:bg-gray-50 transition-colors first:rounded-t-md last:rounded-b-md"
className="w-full text-left px-3 py-2 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors first:rounded-t-md last:rounded-b-md"
>
<Badge variant={config.variant} size="sm">
{config.label}

View File

@@ -171,7 +171,7 @@ export default function TaskStatusDropdown({
return createPortal(
<div
ref={dropdownRef}
className="fixed bg-white border border-gray-200 rounded-md shadow-lg z-[9999] min-w-[140px]"
className="fixed bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-md shadow-lg z-[9999] min-w-[140px]"
style={{
left: `${dropdownPosition.x}px`,
top: `${dropdownPosition.y}px`,
@@ -182,7 +182,7 @@ export default function TaskStatusDropdown({
onClick={() => {
handleChange(statusKey);
}}
className="w-full text-left px-3 py-2 hover:bg-gray-50 transition-colors first:rounded-t-md last:rounded-b-md"
className="w-full text-left px-3 py-2 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors first:rounded-t-md last:rounded-b-md"
>
<Badge variant={config.variant} size="sm">
{config.label}

View File

@@ -0,0 +1,47 @@
"use client";
import { createContext, useContext, useEffect, useState } from 'react';
const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [isDark, setIsDark] = useState(false);
useEffect(() => {
// Check for saved theme preference or default to light mode
const savedTheme = localStorage.getItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (savedTheme === 'dark' || (!savedTheme && prefersDark)) {
setIsDark(true);
document.documentElement.classList.add('dark');
}
}, []);
const toggleTheme = () => {
const newIsDark = !isDark;
setIsDark(newIsDark);
if (newIsDark) {
document.documentElement.classList.add('dark');
localStorage.setItem('theme', 'dark');
} else {
document.documentElement.classList.remove('dark');
localStorage.setItem('theme', 'light');
}
};
return (
<ThemeContext.Provider value={{ isDark, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
}

View File

@@ -3,13 +3,13 @@
import { forwardRef } from "react";
const buttonVariants = {
primary: "bg-blue-600 hover:bg-blue-700 text-white",
primary: "bg-blue-600 hover:bg-blue-700 text-white dark:bg-blue-700 dark:hover:bg-blue-800",
secondary:
"bg-gray-100 hover:bg-gray-200 text-gray-900 border border-gray-300",
danger: "bg-red-600 hover:bg-red-700 text-white",
success: "bg-green-600 hover:bg-green-700 text-white",
outline: "border border-blue-600 text-blue-600 hover:bg-blue-50",
ghost: "text-gray-600 hover:text-gray-900 hover:bg-gray-50",
"bg-gray-100 hover:bg-gray-200 text-gray-900 border border-gray-300 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-100 dark:border-gray-600",
danger: "bg-red-600 hover:bg-red-700 text-white dark:bg-red-700 dark:hover:bg-red-800",
success: "bg-green-600 hover:bg-green-700 text-white dark:bg-green-700 dark:hover:bg-green-800",
outline: "border border-blue-600 text-blue-600 hover:bg-blue-50 dark:border-blue-400 dark:text-blue-400 dark:hover:bg-blue-900/20",
ghost: "text-gray-600 hover:text-gray-900 hover:bg-gray-50 dark:text-gray-400 dark:hover:text-gray-100 dark:hover:bg-gray-800",
};
const buttonSizes = {

View File

@@ -7,7 +7,7 @@ const Card = forwardRef(({ children, className = "", ...props }, ref) => {
<div
ref={ref}
className={`
bg-white rounded-lg border border-gray-200 shadow-sm
bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm
${className}
`}
{...props}
@@ -21,7 +21,7 @@ Card.displayName = "Card";
const CardHeader = ({ children, className = "" }) => {
return (
<div className={`px-6 py-4 border-b border-gray-200 ${className}`}>
<div className={`px-6 py-4 border-b border-gray-200 dark:border-gray-700 ${className}`}>
{children}
</div>
);
@@ -33,7 +33,7 @@ const CardContent = ({ children, className = "" }) => {
const CardFooter = ({ children, className = "" }) => {
return (
<div className={`px-6 py-4 border-t border-gray-200 ${className}`}>
<div className={`px-6 py-4 border-t border-gray-200 dark:border-gray-700 ${className}`}>
{children}
</div>
);

View File

@@ -7,7 +7,7 @@ const Input = forwardRef(
return (
<div className="space-y-1">
{label && (
<label className="block text-sm font-medium text-gray-700">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{label}
</label>
)}
@@ -15,9 +15,11 @@ const Input = forwardRef(
ref={ref}
type={type}
className={`
w-full px-3 py-2 border border-gray-300 rounded-lg
w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100
placeholder-gray-500 dark:placeholder-gray-400
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500
disabled:bg-gray-50 disabled:text-gray-500
disabled:bg-gray-50 dark:disabled:bg-gray-700 disabled:text-gray-500 dark:disabled:text-gray-400
${
error
? "border-red-300 focus:ring-red-500 focus:border-red-500"
@@ -27,7 +29,7 @@ const Input = forwardRef(
`}
{...props}
/>
{error && <p className="text-sm text-red-600">{error}</p>}
{error && <p className="text-sm text-red-600 dark:text-red-400">{error}</p>}
</div>
);
}
@@ -40,16 +42,17 @@ const Select = forwardRef(
return (
<div className="space-y-1">
{label && (
<label className="block text-sm font-medium text-gray-700">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{label}
</label>
)}
<select
ref={ref}
className={`
w-full px-3 py-2 border border-gray-300 rounded-lg
w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500
disabled:bg-gray-50 disabled:text-gray-500
disabled:bg-gray-50 dark:disabled:bg-gray-700 disabled:text-gray-500 dark:disabled:text-gray-400
${
error
? "border-red-300 focus:ring-red-500 focus:border-red-500"
@@ -61,7 +64,7 @@ const Select = forwardRef(
>
{children}
</select>
{error && <p className="text-sm text-red-600">{error}</p>}
{error && <p className="text-sm text-red-600 dark:text-red-400">{error}</p>}
</div>
);
}
@@ -74,16 +77,18 @@ const Textarea = forwardRef(
return (
<div className="space-y-1">
{label && (
<label className="block text-sm font-medium text-gray-700">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
{label}
</label>
)}
<textarea
ref={ref}
className={`
w-full px-3 py-2 border border-gray-300 rounded-lg
w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100
placeholder-gray-500 dark:placeholder-gray-400
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500
disabled:bg-gray-50 disabled:text-gray-500
disabled:bg-gray-50 dark:disabled:bg-gray-700 disabled:text-gray-500 dark:disabled:text-gray-400
${
error
? "border-red-300 focus:ring-red-500 focus:border-red-500"
@@ -93,7 +98,7 @@ const Textarea = forwardRef(
`}
{...props}
/>
{error && <p className="text-sm text-red-600">{error}</p>}
{error && <p className="text-sm text-red-600 dark:text-red-400">{error}</p>}
</div>
);
}

View File

@@ -10,18 +10,18 @@ const LoadingSpinner = ({ size = "md", className = "" }) => {
return (
<div
className={`animate-spin rounded-full border-2 border-gray-300 border-t-blue-600 ${sizes[size]} ${className}`}
className={`animate-spin rounded-full border-2 border-gray-300 dark:border-gray-600 border-t-blue-600 dark:border-t-blue-500 ${sizes[size]} ${className}`}
></div>
);
};
const LoadingCard = ({ className = "" }) => (
<div className={`animate-pulse ${className}`}>
<div className="bg-white rounded-lg border border-gray-200 shadow-sm p-6">
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm p-6">
<div className="space-y-3">
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
<div className="h-3 bg-gray-200 rounded w-1/2"></div>
<div className="h-3 bg-gray-200 rounded w-2/3"></div>
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-3/4"></div>
<div className="h-3 bg-gray-200 dark:bg-gray-700 rounded w-1/2"></div>
<div className="h-3 bg-gray-200 dark:bg-gray-700 rounded w-2/3"></div>
</div>
</div>
</div>

View File

@@ -4,7 +4,6 @@ import Link from "next/link";
import { usePathname } from "next/navigation";
import { useSession, signOut } from "next-auth/react";
import { useTranslation } from "@/lib/i18n";
import LanguageSwitcher from "./LanguageSwitcher";
import { useState } from "react";
const Navigation = () => {
@@ -42,12 +41,12 @@ const Navigation = () => {
}
return (
<nav className="bg-white border-b border-gray-200 shadow-sm">
<nav className="bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700 shadow-sm">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
{/* Logo/Brand */}
<div className="flex items-center">
<Link href="/projects" className="text-xl font-bold text-gray-900 hover:text-blue-600 transition-colors">
<Link href="/projects" className="text-xl font-bold text-gray-900 dark:text-white hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
{t('navigation.projectPanel')}
</Link>
</div>
@@ -64,8 +63,8 @@ const Navigation = () => {
href={item.href}
className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
isActive(item.href)
? "bg-blue-50 text-blue-700 border border-blue-200"
: "text-gray-600 hover:text-gray-900 hover:bg-gray-50"
? "bg-blue-50 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 border border-blue-200 dark:border-blue-700"
: "text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-gray-800"
}`}
>
{item.label}
@@ -83,12 +82,12 @@ const Navigation = () => {
<>
{/* User Info */}
<div className="hidden md:flex items-center space-x-3">
<div className="text-right">
<div className="text-sm font-medium text-gray-900">{session.user.name}</div>
<Link href="/settings" className="text-right hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md px-2 py-1 transition-colors">
<div className="text-sm font-medium text-gray-900 dark:text-white">{session.user.name}</div>
{/* <div className="text-xs text-gray-500 capitalize">
{t(`userRoles.${session.user.role}`) || session.user.role?.replace('_', ' ')}
</div> */}
</div>
</Link>
{/* <div className="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center">
<span className="text-gray-600 font-medium text-sm">
{session.user.name?.charAt(0).toUpperCase()}
@@ -99,17 +98,16 @@ const Navigation = () => {
{/* Sign Out Button */}
<button
onClick={handleSignOut}
className="hidden md:block bg-gray-100 hover:bg-gray-200 text-gray-700 px-4 py-2 rounded-md text-sm font-medium transition-colors"
className="hidden md:block bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 px-4 py-2 rounded-md text-sm font-medium transition-colors"
>
{t('navigation.signOut')}
</button>
</>
) : (
<>
<LanguageSwitcher />
<Link
href="/auth/signin"
className="bg-white hover:bg-gray-50 text-blue-600 px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200 shadow-md"
className="bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 text-blue-600 dark:text-blue-400 px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200 shadow-md border border-gray-200 dark:border-gray-600"
>
{t('navigation.signIn')}
</Link>
@@ -120,7 +118,7 @@ const Navigation = () => {
{session && (
<button
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
className="md:hidden p-2 rounded-md text-gray-600 hover:text-gray-900 hover:bg-gray-100 transition-colors"
className="md:hidden p-2 rounded-md text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
{isMobileMenuOpen ? (
@@ -136,7 +134,7 @@ const Navigation = () => {
{/* Mobile Navigation Menu */}
{session && isMobileMenuOpen && (
<div className="md:hidden mt-4 pb-4 border-t border-gray-200">
<div className="md:hidden mt-4 pb-4 border-t border-gray-200 dark:border-gray-700">
<div className="flex flex-col space-y-2 pt-4">
{navItems.map((item) => (
<Link
@@ -145,8 +143,8 @@ const Navigation = () => {
onClick={() => setIsMobileMenuOpen(false)}
className={`px-4 py-3 rounded-md text-sm font-medium transition-colors ${
isActive(item.href)
? "bg-blue-50 text-blue-700 border border-blue-200"
: "text-gray-600 hover:text-gray-900 hover:bg-gray-50"
? "bg-blue-50 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 border border-blue-200 dark:border-blue-700"
: "text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-gray-800"
}`}
>
{item.label}
@@ -154,29 +152,31 @@ const Navigation = () => {
))}
{/* Mobile User Info */}
<div className="flex items-center justify-between px-4 py-3 border-t border-gray-200 mt-4">
<div className="flex items-center space-x-3">
<div className="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center">
<span className="text-gray-600 font-medium text-sm">
<div className="flex items-center justify-between px-4 py-3 border-t border-gray-200 dark:border-gray-700 mt-4">
<Link href="/settings" className="flex items-center space-x-3 hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md px-2 py-1 transition-colors flex-1">
<div className="w-8 h-8 bg-gray-100 dark:bg-gray-800 rounded-full flex items-center justify-center">
<span className="text-gray-600 dark:text-gray-300 font-medium text-sm">
{session.user.name?.charAt(0).toUpperCase()}
</span>
</div>
<div>
<div className="text-sm font-medium text-gray-900">{session.user.name}</div>
<div className="text-sm font-medium text-gray-900 dark:text-white">{session.user.name}</div>
{/* <div className="text-xs text-gray-500 capitalize">
{t(`userRoles.${session.user.role}`) || session.user.role?.replace('_', ' ')}
</div> */}
</div>
</Link>
<div className="flex items-center space-x-2">
<button
onClick={() => {
setIsMobileMenuOpen(false);
handleSignOut();
}}
className="bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 px-3 py-1 rounded text-sm transition-colors"
>
{t('navigation.signOut')}
</button>
</div>
<button
onClick={() => {
setIsMobileMenuOpen(false);
handleSignOut();
}}
className="bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1 rounded text-sm transition-colors"
>
{t('navigation.signOut')}
</button>
</div>
</div>
</div>

View File

@@ -2,7 +2,7 @@
const PageContainer = ({ children, className = "" }) => {
return (
<div className={`min-h-screen bg-gray-50 ${className}`}>
<div className={`min-h-screen bg-gray-50 dark:bg-gray-900 ${className}`}>
<div className="max-w-6xl mx-auto p-6">{children}</div>
</div>
);

View File

@@ -4,8 +4,8 @@ const PageHeader = ({ title, description, children, action, className = "" }) =>
return (
<div className={`flex justify-between items-start mb-8 ${className}`}>
<div className="flex-1">
<h1 className="text-3xl font-bold text-gray-900">{title}</h1>
{description && <p className="text-gray-600 mt-1">{description}</p>}
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">{title}</h1>
{description && <p className="text-gray-600 dark:text-gray-400 mt-1">{description}</p>}
</div>
{(children || action) && (
<div className="ml-6 flex-shrink-0">{action || children}</div>

View File

@@ -277,7 +277,7 @@ export default function ProjectCalendarWidget({
</div>
</CardHeader>
<CardContent>
<div className="text-center py-8 text-gray-500">
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
Brak aktywnych projektów z terminami
</div>
</CardContent>

View File

@@ -7,8 +7,8 @@ import dynamic from "next/dynamic";
const DynamicMap = dynamic(() => import("./LeafletMap"), {
ssr: false,
loading: () => (
<div className="w-full h-64 bg-gray-100 animate-pulse rounded-lg flex items-center justify-center">
<span className="text-gray-500">Loading map...</span>
<div className="w-full h-64 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">Loading map...</span>
</div>
),
});
@@ -52,15 +52,15 @@ export default function ProjectMap({
return (
<div className="space-y-2">
<div className="flex items-center justify-between">
<h3 className="text-sm font-medium text-gray-700">
<h3 className="text-sm font-medium text-gray-700 dark:text-gray-300">
Lokalizacja projektu
</h3>
<div className="text-xs text-gray-500">No coordinates available</div>
<div className="text-xs text-gray-500 dark:text-gray-400">No coordinates available</div>
</div>
<div
className={`w-full ${mapHeight} rounded-lg bg-gray-100 border border-gray-200 flex items-center justify-center`}
className={`w-full ${mapHeight} rounded-lg bg-gray-100 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 flex items-center justify-center`}
>
<div className="text-center text-gray-500">
<div className="text-center text-gray-500 dark:text-gray-400">
<svg
className="w-8 h-8 mx-auto mb-2"
fill="currentColor"
@@ -86,23 +86,23 @@ export default function ProjectMap({
<div className="space-y-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<h3 className="text-sm font-medium text-gray-700">
<h3 className="text-sm font-medium text-gray-700 dark:text-gray-300">
Lokalizacja projektu
</h3>
<div
className="w-3 h-3 rounded-full border border-white shadow-sm"
className="w-3 h-3 rounded-full border border-white dark:border-gray-800 shadow-sm"
style={{ backgroundColor: statusInfo.color }}
title={`Status: ${statusInfo.label}`}
></div>
</div>
{showLayerControl && (
<div className="text-xs text-gray-500">
<div className="text-xs text-gray-500 dark:text-gray-400">
{/* Use the layer control (📚) to switch map views */}
</div>
)}
</div>
<div
className={`w-full ${mapHeight} rounded-lg border border-gray-200`}
className={`w-full ${mapHeight} rounded-lg border border-gray-200 dark:border-gray-600`}
>
<DynamicMap
center={[coords.lat, coords.lng]}
@@ -113,8 +113,8 @@ export default function ProjectMap({
color: statusInfo.color,
popup: (
<div className="min-w-48">
<div className="mb-2 pb-2 border-b border-gray-200">
<h4 className="font-semibold text-gray-900 mb-1">
<div className="mb-2 pb-2 border-b border-gray-200 dark:border-gray-600">
<h4 className="font-semibold text-gray-900 dark:text-gray-100 mb-1">
{projectName || "Project Location"}
</h4>
<span
@@ -124,10 +124,10 @@ export default function ProjectMap({
{statusInfo.label}
</span>
</div>
<div className="text-sm text-gray-600">
<div className="text-sm text-gray-600 dark:text-gray-400">
<div className="flex items-center gap-2">
<svg
className="w-4 h-4 text-gray-400"
className="w-4 h-4 text-gray-400 dark:text-gray-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"

View File

@@ -14,13 +14,13 @@ const SearchBar = ({
}) => {
return (
<div
className={`bg-white rounded-lg shadow-sm border border-gray-200 p-4 mb-6 ${className}`}
className={`bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-4 mb-6 ${className}`}
>
<div className="flex items-center space-x-4">
<div className="flex-1 relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg
className="h-5 w-5 text-gray-400"
className="h-5 w-5 text-gray-400 dark:text-gray-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
@@ -38,7 +38,7 @@ const SearchBar = ({
placeholder={placeholder}
value={searchTerm}
onChange={onSearchChange}
className="pl-10 pr-10 w-full border-gray-300 focus:border-blue-500 focus:ring-blue-500"
className="pl-10 pr-10 w-full border-gray-300 dark:border-gray-600 focus:border-blue-500 focus:ring-blue-500"
/>
{searchTerm && (
<button
@@ -48,7 +48,7 @@ const SearchBar = ({
className="absolute inset-y-0 right-0 pr-3 flex items-center"
>
<svg
className="h-5 w-5 text-gray-400 hover:text-gray-600 transition-colors"
className="h-5 w-5 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
@@ -67,12 +67,12 @@ const SearchBar = ({
</div>
{searchTerm && resultsCount !== null && (
<div className="mt-3 pt-3 border-t border-gray-100">
<p className="text-sm text-gray-600">
<div className="mt-3 pt-3 border-t border-gray-100 dark:border-gray-700">
<p className="text-sm text-gray-600 dark:text-gray-400">
Found{" "}
<span className="font-medium text-gray-900">{resultsCount}</span>{" "}
<span className="font-medium text-gray-900 dark:text-gray-100">{resultsCount}</span>{" "}
{resultsText} matching
<span className="font-medium text-blue-600">
<span className="font-medium text-blue-600 dark:text-blue-400">
{" "}
&quot;{searchTerm}&quot;
</span>

View File

@@ -13,7 +13,7 @@ const LoadingSpinner = ({ size = "md" }) => {
return (
<div className="flex items-center justify-center">
<div
className={`${sizeClasses[size]} animate-spin rounded-full border-2 border-gray-300 border-t-blue-600`}
className={`${sizeClasses[size]} animate-spin rounded-full border-2 border-gray-300 dark:border-gray-600 border-t-blue-600 dark:border-t-blue-500`}
></div>
</div>
);
@@ -24,7 +24,7 @@ const LoadingState = ({ message = "Loading...", className = "" }) => {
<div className={`flex items-center justify-center py-12 ${className}`}>
<div className="text-center">
<LoadingSpinner size="lg" />
<p className="text-gray-600 mt-4">{message}</p>
<p className="text-gray-600 dark:text-gray-400 mt-4">{message}</p>
</div>
</div>
);
@@ -42,7 +42,7 @@ const EmptyState = ({
return (
<Card className={className}>
<CardContent className="text-center py-12">
<div className="text-gray-400 mb-4">
<div className="text-gray-400 dark:text-gray-500 mb-4">
{icon || (
<svg
className="w-16 h-16 mx-auto"
@@ -57,8 +57,8 @@ const EmptyState = ({
</svg>
)}
</div>
<h3 className="text-lg font-medium text-gray-900 mb-2">{title}</h3>
{description && <p className="text-gray-500 mb-6">{description}</p>}
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">{title}</h3>
{description && <p className="text-gray-500 dark:text-gray-400 mb-6">{description}</p>}
{actionLabel && (actionHref || onAction) && (
<Button
variant="primary"
@@ -83,7 +83,7 @@ const ErrorState = ({
return (
<Card className={className}>
<CardContent className="text-center py-12">
<div className="text-red-400 mb-4">
<div className="text-red-400 dark:text-red-500 mb-4">
<svg
className="w-16 h-16 mx-auto"
fill="none"
@@ -98,8 +98,8 @@ const ErrorState = ({
/>
</svg>
</div>
<h3 className="text-lg font-medium text-gray-900 mb-2">{title}</h3>
<p className="text-gray-500 mb-6">{description}</p>
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">{title}</h3>
<p className="text-gray-500 dark:text-gray-400 mb-6">{description}</p>
{onRetry && (
<Button variant="primary" onClick={onRetry}>
Try Again

View File

@@ -0,0 +1,27 @@
"use client";
import { useTheme } from "@/components/ThemeProvider";
export default function ThemeToggle() {
const { isDark, toggleTheme } = useTheme();
return (
<button
onClick={toggleTheme}
className="p-2 rounded-md text-gray-600 hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-gray-100 dark:hover:bg-gray-800 transition-colors"
title={isDark ? "Switch to light mode" : "Switch to dark mode"}
>
{isDark ? (
// Sun icon for light mode
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
) : (
// Moon icon for dark mode
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
</svg>
)}
</button>
);
}

View File

@@ -67,7 +67,7 @@ export default function Tooltip({ children, content, className = "" }) {
const tooltip = isVisible && (
<div
ref={tooltipRef}
className={`fixed z-50 px-3 py-2 text-sm bg-gray-900 text-white rounded-lg shadow-lg border max-w-sm ${className}`}
className={`fixed z-50 px-3 py-2 text-sm bg-gray-900 dark:bg-gray-700 text-white rounded-lg shadow-lg border border-gray-700 dark:border-gray-600 max-w-sm ${className}`}
style={{
top: position.top,
left: position.left,
@@ -75,7 +75,7 @@ export default function Tooltip({ children, content, className = "" }) {
>
{content}
{/* Arrow pointing down */}
<div className="absolute top-full left-1/2 transform -translate-x-1/2 border-4 border-transparent border-t-gray-900"></div>
<div className="absolute top-full left-1/2 transform -translate-x-1/2 border-4 border-transparent border-t-gray-900 dark:border-t-gray-700"></div>
</div>
);

View File

@@ -526,6 +526,16 @@ const translations = {
createdAt: "Data utworzenia",
noUsers: "Brak użytkowników",
system: "System"
},
// Settings
settings: {
title: "Ustawienia",
appearance: "Wygląd",
theme: "Motyw",
themeDescription: "Wybierz preferowany motyw",
language: "Język",
languageDescription: "Wybierz preferowany język"
}
},
@@ -984,6 +994,16 @@ const translations = {
createdAt: "Created At",
noUsers: "No users",
system: "System"
},
// Settings
settings: {
title: "Settings",
appearance: "Appearance",
theme: "Theme",
themeDescription: "Choose your preferred theme",
language: "Language",
languageDescription: "Select your preferred language"
}
}
};

View File

@@ -5,11 +5,63 @@ export default {
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
darkMode: 'class',
theme: {
extend: {
colors: {
// Base colors using CSS variables
background: "var(--background)",
foreground: "var(--foreground)",
// Surface colors
surface: {
primary: "var(--surface-primary)",
secondary: "var(--surface-secondary)",
tertiary: "var(--surface-tertiary)",
modal: "var(--surface-modal)",
card: "var(--surface-card)",
hover: "var(--surface-hover)",
active: "var(--surface-active)",
},
// Text colors
text: {
primary: "var(--text-primary)",
secondary: "var(--text-secondary)",
tertiary: "var(--text-tertiary)",
inverse: "var(--text-inverse)",
muted: "var(--text-muted)",
},
// Border colors
border: {
default: "var(--border-default)",
hover: "var(--border-hover)",
focus: "var(--border-focus)",
divider: "var(--border-divider)",
},
// Interactive colors
interactive: {
primary: "var(--interactive-primary)",
"primary-hover": "var(--interactive-primary-hover)",
secondary: "var(--interactive-secondary)",
"secondary-hover": "var(--interactive-secondary-hover)",
danger: "var(--interactive-danger)",
"danger-hover": "var(--interactive-danger-hover)",
},
// Status colors
status: {
success: "var(--status-success)",
warning: "var(--status-warning)",
error: "var(--status-error)",
info: "var(--status-info)",
},
},
boxShadow: {
'custom': 'var(--shadow-default)',
'custom-hover': 'var(--shadow-hover)',
},
},
},