feat: update ProjectListPage layout and button styles for improved UI/UX

This commit is contained in:
2025-10-09 20:56:55 +02:00
parent ac5fedb61a
commit ec5b60d478
2 changed files with 192 additions and 131 deletions

View File

@@ -183,73 +183,12 @@ export default function ProjectListPage() {
return (
<PageContainer>
<PageHeader title={t('projects.title')} description={t('projects.subtitle')}>
<div className="flex flex-col space-y-2 sm:flex-row sm:space-y-0 sm:space-x-2 sm:gap-2">
<Link href="/projects/map" className="w-full sm:w-auto">
<Button variant="outline" size="lg" className="w-full">
<div className="flex items-center gap-2 sm:gap-3">
{/* Primary Action - New Project */}
<Link href="/projects/new" className="flex-shrink-0">
<Button variant="primary" size="lg">
<svg
className="w-5 h-5 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7"
/>
</svg>
{t('projects.mapView') || 'Widok mapy'}
</Button>
</Link>
{session?.user && (
<Button
variant={filters.mine ? "primary" : "outline"}
size="lg"
className="w-full sm:w-auto"
onClick={() => handleFilterChange('mine', !filters.mine)}
>
<svg
className="w-5 h-5 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
/>
</svg>
{t('projects.mine') || 'Moje'}
</Button>
)}
<Button
variant="outline"
size="lg"
className="w-full sm:w-auto"
onClick={handleExportExcel}
>
<svg
className="w-5 h-5 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
/>
</svg>
{t('projects.exportExcel') || 'Export to Excel'}
</Button>
<Link href="/projects/new" className="w-full sm:w-auto">
<Button variant="primary" size="lg" className="w-full">
<svg
className="w-5 h-5 mr-2"
className="w-5 h-5 sm:mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
@@ -261,9 +200,55 @@ export default function ProjectListPage() {
d="M12 4v16m8-8H4"
/>
</svg>
{t('projects.newProject')}
<span className="hidden sm:inline">{t('projects.newProject')}</span>
</Button>
</Link>
{/* Spacer */}
<div className="flex-1"></div>
{/* Utility Actions - Icon Buttons */}
<div className="flex items-center gap-2">
<Link href="/projects/map" title={t('projects.mapView') || 'Widok mapy'}>
<Button variant="ghost" size="icon" className="h-10 w-10">
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7"
/>
</svg>
</Button>
</Link>
<Button
variant="ghost"
size="icon"
className="h-10 w-10"
onClick={handleExportExcel}
title={t('projects.exportExcel') || 'Export to Excel'}
>
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
/>
</svg>
</Button>
</div>
</div>
</PageHeader>
@@ -380,6 +365,34 @@ export default function ProjectListPage() {
))}
</select>
</div>
{session?.user && (
<button
onClick={() => handleFilterChange('mine', !filters.mine)}
className={`
inline-flex items-center space-x-2 px-3 py-1.5 rounded-full text-sm font-medium transition-all
${filters.mine
? 'bg-blue-100 text-blue-700 border-2 border-blue-300 dark:bg-blue-900/30 dark:text-blue-300 dark:border-blue-700'
: 'bg-gray-100 text-gray-700 border-2 border-gray-200 hover:border-gray-300 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-700 dark:hover:border-gray-600'
}
`}
>
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
/>
</svg>
<span>{t('projects.mine') || 'Tylko moje'}</span>
</button>
)}
</div>
</div>
</div>
@@ -387,15 +400,17 @@ export default function ProjectListPage() {
{/* Desktop always visible content */}
<div className="hidden md:block">
<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 dark:text-gray-300 md:text-xs md:whitespace-nowrap">
<div className="flex flex-col space-y-3">
{/* Filters row */}
<div className="flex flex-wrap gap-4 items-center">
<div className="flex items-center space-x-2">
<label className="text-xs font-medium text-gray-700 dark:text-gray-300 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 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"
className="px-3 py-1 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"
>
<option value="all">{t('common.all')}</option>
<option value="not_finished">{t('projects.notFinished') || 'Nie zakończone'}</option>
@@ -406,14 +421,14 @@ export default function ProjectListPage() {
</select>
</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 dark:text-gray-300 md:text-xs md:whitespace-nowrap">
<div className="flex items-center space-x-2">
<label className="text-xs font-medium text-gray-700 dark:text-gray-300 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 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"
className="px-3 py-1 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"
>
<option value="all">{t('common.all')}</option>
<option value="design">{t('projectType.design')}</option>
@@ -422,14 +437,14 @@ export default function ProjectListPage() {
</select>
</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 dark:text-gray-300 md:text-xs md:whitespace-nowrap">
<div className="flex items-center space-x-2">
<label className="text-xs font-medium text-gray-700 dark:text-gray-300 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 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"
className="px-3 py-1 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"
>
<option value="all">{t('common.all')}</option>
{customers.map((customer) => (
@@ -440,19 +455,64 @@ export default function ProjectListPage() {
</select>
</div>
{(filters.status !== 'all' || filters.type !== 'all' || filters.customer !== 'all' || searchTerm) && (
{session?.user && (
<button
onClick={() => handleFilterChange('mine', !filters.mine)}
className={`
inline-flex items-center space-x-2 px-3 py-1.5 rounded-full text-sm font-medium transition-all
${filters.mine
? 'bg-blue-100 text-blue-700 border-2 border-blue-300 dark:bg-blue-900/30 dark:text-blue-300 dark:border-blue-700'
: 'bg-gray-100 text-gray-700 border-2 border-gray-200 hover:border-gray-300 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-700 dark:hover:border-gray-600'
}
`}
>
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
/>
</svg>
<span>{t('projects.mine') || 'Tylko moje'}</span>
</button>
)}
</div>
{/* Results and clear button row */}
<div className="flex items-center justify-between pt-2 border-t border-gray-100">
<div className="text-sm text-gray-500">
{t('projects.showingResults', { shown: filteredProjects.length, total: projects.length }) || `Wyświetlono ${filteredProjects.length} z ${projects.length} projektów`}
</div>
{(filters.status !== 'all' || filters.type !== 'all' || filters.customer !== 'all' || filters.mine || searchTerm) && (
<Button
variant="outline"
variant="ghost"
size="sm"
onClick={clearAllFilters}
className="text-xs self-start md:self-auto"
className="text-xs"
>
<svg
className="w-4 h-4 mr-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
{t('common.clearAllFilters') || 'Wyczyść wszystkie filtry'}
</Button>
)}
<div className="text-sm text-gray-500 md:ml-auto md:text-right">
{t('projects.showingResults', { shown: filteredProjects.length, total: projects.length }) || `Wyświetlono ${filteredProjects.length} z ${projects.length} projektów`}
</div>
</div>
</div>

View File

@@ -16,6 +16,7 @@ const buttonSizes = {
sm: "px-3 py-1.5 text-sm",
md: "px-4 py-2 text-sm",
lg: "px-6 py-3 text-base",
icon: "p-2",
};
const Button = forwardRef(