Refactor Home component to display gifts for the current year using YearGifts, update YearOverview to accept a custom heading, and enhance YearList button text for clarity. Introduce new gift status 'ready' across relevant components and adjust GiftCard styling for better status representation.

This commit is contained in:
Norbert Maciaszek
2025-11-15 14:32:46 +01:00
parent 2a7ee1edf6
commit 3d6cd486e1
12 changed files with 89 additions and 22 deletions

View File

@@ -1,20 +1,21 @@
import { YearOverview } from '@/components/organisms/YearOverview'; import { YearOverview } from '@/components/organisms/YearOverview';
import { YearList } from '@/components/atoms/YearList'; import { YearList } from '@/components/atoms/YearList';
import { DB } from '@/lib/db'; import { DB } from '@/lib/db';
import { GiftCard } from '@/components/molecules/GiftCard';
import { Heading } from '@/components/atoms/Heading'; import { Heading } from '@/components/atoms/Heading';
import { YearGifts } from '@/components/organisms/YearGifts';
export const dynamic = 'force-dynamic';
export default async function Home() { export default async function Home() {
const recentGifts = await DB.getRecentGifts(); const year = new Date().getFullYear();
const gifts = await DB.getGifts(year);
return ( return (
<> <>
<YearOverview /> <YearOverview heading='Podsumowanie bieżącego roku' />
<YearList /> <YearList />
<Heading>Ostatnie prezenty</Heading> <Heading>Prezenty tego roku</Heading>
{recentGifts.map((gift) => ( <YearGifts gifts={gifts} />
<GiftCard key={gift.id} {...gift} />
))}
</> </>
); );
} }

View File

@@ -8,7 +8,7 @@ export const YearList = async () => {
<div className='mb-6 flex flex-col sm:flex-row gap-4 items-start sm:items-center justify-between'> <div className='mb-6 flex flex-col sm:flex-row gap-4 items-start sm:items-center justify-between'>
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<Button variant='secondary' href='/'> <Button variant='secondary' href='/'>
Wszystkie lata Prezenty tego roku
</Button> </Button>
{years.map((year) => ( {years.map((year) => (
<Button key={year.id} href={`/year/${year.year}`} variant='secondary'> <Button key={year.id} href={`/year/${year.year}`} variant='secondary'>

View File

@@ -26,9 +26,10 @@ export const GiftCard: FC<Gift & Props> = ({ hideDetails = false, editable = fal
const bgByStatus = { const bgByStatus = {
planned: 'bg-gray-100', planned: 'bg-gray-100',
decided: 'bg-blue-100', decided: 'bg-red-300',
bought: 'bg-green-100', bought: 'bg-yellow-200',
wrapped: 'bg-purple-100', ready: 'bg-green-100',
wrapped: 'bg-green-300',
}; };
const handleDelete = async () => { const handleDelete = async () => {

View File

@@ -16,7 +16,7 @@ type Props = {
link: string; link: string;
cost: number; cost: number;
imageUrl: string; imageUrl: string;
status: 'planned' | 'decided' | 'bought' | 'wrapped'; status: Gift['status'];
year: string; year: string;
person: string; person: string;
}) => Promise<void>; }) => Promise<void>;
@@ -148,6 +148,7 @@ export const GiftModal: FC<Props> = ({ isOpen, onClose, gift, yearId, personId,
<option value='planned'>{formatStatus('planned')} </option> <option value='planned'>{formatStatus('planned')} </option>
<option value='decided'>{formatStatus('decided')}</option> <option value='decided'>{formatStatus('decided')}</option>
<option value='bought'>{formatStatus('bought')}</option> <option value='bought'>{formatStatus('bought')}</option>
<option value='ready'>{formatStatus('ready')}</option>
<option value='wrapped'>{formatStatus('wrapped')}</option> <option value='wrapped'>{formatStatus('wrapped')}</option>
</select> </select>
</div> </div>

View File

@@ -24,7 +24,7 @@ export const PersonCardEdit: FC<Props> = ({ person }) => {
return ( return (
<> <>
<div className='flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4'> <div className='flex flex-row items-center justify-center gap-2'>
<Button variant='primary' onClick={() => setIsOpen(true)}> <Button variant='primary' onClick={() => setIsOpen(true)}>
Edytuj Edytuj
</Button> </Button>

View File

@@ -10,7 +10,7 @@ export const PersonCard: FC<Person> = (person) => {
return ( return (
<div className='bg-white rounded-2xl shadow-sm hover:shadow-md p-4 md:p-6 transition-all duration-200 ease-in-out mt-6'> <div className='bg-white rounded-2xl shadow-sm hover:shadow-md p-4 md:p-6 transition-all duration-200 ease-in-out mt-6'>
<div className='flex flex-col sm:items-center sm:justify-between gap-4 pb-4 mb-6 border-b border-gray-200'> <div className='flex flex-col items-center sm:justify-between gap-4 pb-4 mb-6 border-b border-gray-200'>
<Heading size='large' spacing='none'> <Heading size='large' spacing='none'>
{name} {name}
</Heading> </Heading>

View File

@@ -0,0 +1,56 @@
'use client';
import { GiftCard } from '@/components/molecules/GiftCard';
import { FC, useState } from 'react';
type Props = {
gifts: Gift[];
};
export const YearGifts: FC<Props> = ({ gifts }) => {
const [filter, setFilter] = useState<Gift['status'] | 'all'>('all');
const [sort, setSort] = useState<keyof Pick<Gift, 'title' | 'cost' | 'person' | 'status'>>('title');
let renderGifts = gifts;
// sort
if (sort === 'cost') {
renderGifts = renderGifts.sort((a, b) => b.cost - a.cost);
} else if (sort === 'person') {
renderGifts = renderGifts.sort((a, b) => a.expand.person.name.localeCompare(b.expand.person.name));
} else {
renderGifts = renderGifts.sort((a, b) => (a[sort] < b[sort] ? -1 : 1));
}
if (filter !== 'all') {
renderGifts = renderGifts.filter((gift) => gift.status === filter);
}
return (
<section className=''>
<div className='flex flex-row items-center justify-between gap-4'>
<select
className='px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all'
value={filter}
onChange={(e) => setFilter(e.target.value as Gift['status'] | 'all')}>
<option value='all'>Wszystkie</option>
<option value='planned'>Planowane</option>
<option value='decided'>Do kupienia</option>
<option value='bought'>Kupione</option>
<option value='wrapped'>Gotowe</option>
</select>
<select
className='px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all'
value={sort}
onChange={(e) => setSort(e.target.value as keyof Pick<Gift, 'title' | 'cost' | 'person' | 'status'>)}>
<option value='title'>Tytuł</option>
<option value='cost'>Koszt</option>
<option value='person'>Osoba</option>
<option value='status'>Status</option>
</select>
</div>
{renderGifts.map((gift) => (
<GiftCard key={gift.id} {...gift} />
))}
</section>
);
};

View File

@@ -5,10 +5,11 @@ import { FC } from 'react';
import { Heading } from '@/components/atoms/Heading'; import { Heading } from '@/components/atoms/Heading';
type Props = { type Props = {
heading?: string;
year?: number; year?: number;
}; };
export const YearOverview: FC<Props> = async ({ year }) => { export const YearOverview: FC<Props> = async ({ year, heading }) => {
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();
const data = await DB.getYear(year ?? currentYear); const data = await DB.getYear(year ?? currentYear);
@@ -36,7 +37,7 @@ export const YearOverview: FC<Props> = async ({ year }) => {
return ( return (
<div className='mb-6 md:mb-8'> <div className='mb-6 md:mb-8'>
<Heading size='large'>Podsumowanie roku {data.year}</Heading> <Heading size='large'>{heading ?? `Podsumowanie roku ${data.year}`}</Heading>
<div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4'> <div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4'>
<StatsCard title='Ilość prezentów' value={gifts.length} /> <StatsCard title='Ilość prezentów' value={gifts.length} />
<StatsCard title='Planowane prezenty' value={planned.length} /> <StatsCard title='Planowane prezenty' value={planned.length} />

View File

@@ -6,6 +6,8 @@ export const formatStatus = (status: Gift['status']) => {
return 'Do kupienia'; return 'Do kupienia';
case 'bought': case 'bought':
return 'Kupione'; return 'Kupione';
case 'ready':
return 'Do spakowania';
case 'wrapped': case 'wrapped':
return 'Gotowe'; return 'Gotowe';
} }

View File

@@ -46,7 +46,7 @@ export const DB = {
link: string; link: string;
cost: number; cost: number;
imageUrl: string; imageUrl: string;
status: 'planned' | 'decided' | 'bought' | 'wrapped'; status: Gift['status'];
year: string; year: string;
person: string; person: string;
}): Promise<Gift> => { }): Promise<Gift> => {
@@ -71,7 +71,7 @@ export const DB = {
link: string; link: string;
cost: number; cost: number;
imageUrl: string; imageUrl: string;
status: 'planned' | 'decided' | 'bought' | 'wrapped'; status: Gift['status'];
year: string; year: string;
person: string; person: string;
}, },
@@ -88,9 +88,14 @@ export const DB = {
'gifts-': id, 'gifts-': id,
}); });
}, },
getGifts: async (yearId: string): Promise<Gift[]> => { getGifts: async (year: number): Promise<Gift[]> => {
const yearRecord = await pb.collection('gifts_year').getFirstListItem(`year = ${year}`);
if (!yearRecord) {
return [];
}
return await pb.collection('gifts_items').getFullList({ return await pb.collection('gifts_items').getFullList({
filter: `year = "${yearId}"`, filter: `year = "${yearRecord.id}"`,
expand: 'year,person', expand: 'year,person',
fields: '*,expand.year.year,expand.person.name,expand.year.id,expand.person.id', fields: '*,expand.year.year,expand.person.name,expand.year.id,expand.person.id',
}); });

View File

@@ -25,7 +25,7 @@ namespace DB {
link: string; link: string;
cost: number; cost: number;
imageUrl: string; imageUrl: string;
status: 'planned' | 'decided' | 'bought' | 'wrapped'; status: 'planned' | 'decided' | 'bought' | 'ready' | 'wrapped';
created: Date; created: Date;
updated: Date; updated: Date;

View File

@@ -42,7 +42,7 @@ type Gift = {
link: string; link: string;
cost: number; cost: number;
imageUrl: string; imageUrl: string;
status: 'planned' | 'decided' | 'bought' | 'wrapped'; status: 'planned' | 'decided' | 'bought' | 'ready' | 'wrapped';
created: Date; created: Date;
updated: Date; updated: Date;