feat: update ProjectListPage layout and button styles for improved UI/UX
This commit is contained in:
@@ -183,73 +183,12 @@ export default function ProjectListPage() {
|
|||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<PageHeader title={t('projects.title')} description={t('projects.subtitle')}>
|
<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">
|
<div className="flex items-center gap-2 sm:gap-3">
|
||||||
<Link href="/projects/map" className="w-full sm:w-auto">
|
{/* Primary Action - New Project */}
|
||||||
<Button variant="outline" size="lg" className="w-full">
|
<Link href="/projects/new" className="flex-shrink-0">
|
||||||
|
<Button variant="primary" size="lg">
|
||||||
<svg
|
<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"
|
|
||||||
>
|
|
||||||
<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"
|
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
@@ -261,9 +200,55 @@ export default function ProjectListPage() {
|
|||||||
d="M12 4v16m8-8H4"
|
d="M12 4v16m8-8H4"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
{t('projects.newProject')}
|
<span className="hidden sm:inline">{t('projects.newProject')}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</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>
|
</div>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
@@ -380,6 +365,34 @@ export default function ProjectListPage() {
|
|||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -387,15 +400,17 @@ export default function ProjectListPage() {
|
|||||||
{/* Desktop always visible content */}
|
{/* Desktop always visible content */}
|
||||||
<div className="hidden md:block">
|
<div className="hidden md:block">
|
||||||
<div className="p-4">
|
<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-3">
|
||||||
<div className="flex flex-col space-y-2 md:flex-row md:items-center md:space-y-0 md:space-x-2">
|
{/* Filters row */}
|
||||||
<label className="text-sm font-medium text-gray-700 dark:text-gray-300 md:text-xs md:whitespace-nowrap">
|
<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'}:
|
{t('common.status') || 'Status'}:
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={filters.status}
|
value={filters.status}
|
||||||
onChange={(e) => handleFilterChange('status', e.target.value)}
|
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="all">{t('common.all')}</option>
|
||||||
<option value="not_finished">{t('projects.notFinished') || 'Nie zakończone'}</option>
|
<option value="not_finished">{t('projects.notFinished') || 'Nie zakończone'}</option>
|
||||||
@@ -406,14 +421,14 @@ export default function ProjectListPage() {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col space-y-2 md:flex-row md:items-center md:space-y-0 md:space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<label className="text-sm font-medium text-gray-700 dark:text-gray-300 md:text-xs md:whitespace-nowrap">
|
<label className="text-xs font-medium text-gray-700 dark:text-gray-300 whitespace-nowrap">
|
||||||
{t('common.type') || 'Typ'}:
|
{t('common.type') || 'Typ'}:
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={filters.type}
|
value={filters.type}
|
||||||
onChange={(e) => handleFilterChange('type', e.target.value)}
|
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="all">{t('common.all')}</option>
|
||||||
<option value="design">{t('projectType.design')}</option>
|
<option value="design">{t('projectType.design')}</option>
|
||||||
@@ -422,14 +437,14 @@ export default function ProjectListPage() {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col space-y-2 md:flex-row md:items-center md:space-y-0 md:space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<label className="text-sm font-medium text-gray-700 dark:text-gray-300 md:text-xs md:whitespace-nowrap">
|
<label className="text-xs font-medium text-gray-700 dark:text-gray-300 whitespace-nowrap">
|
||||||
{t('contracts.customer') || 'Klient'}:
|
{t('contracts.customer') || 'Klient'}:
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={filters.customer}
|
value={filters.customer}
|
||||||
onChange={(e) => handleFilterChange('customer', e.target.value)}
|
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>
|
<option value="all">{t('common.all')}</option>
|
||||||
{customers.map((customer) => (
|
{customers.map((customer) => (
|
||||||
@@ -440,19 +455,64 @@ export default function ProjectListPage() {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</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
|
<Button
|
||||||
variant="outline"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={clearAllFilters}
|
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'}
|
{t('common.clearAllFilters') || 'Wyczyść wszystkie filtry'}
|
||||||
</Button>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const buttonSizes = {
|
|||||||
sm: "px-3 py-1.5 text-sm",
|
sm: "px-3 py-1.5 text-sm",
|
||||||
md: "px-4 py-2 text-sm",
|
md: "px-4 py-2 text-sm",
|
||||||
lg: "px-6 py-3 text-base",
|
lg: "px-6 py-3 text-base",
|
||||||
|
icon: "p-2",
|
||||||
};
|
};
|
||||||
|
|
||||||
const Button = forwardRef(
|
const Button = forwardRef(
|
||||||
|
|||||||
Reference in New Issue
Block a user