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

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