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

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='flex items-center gap-2'>
<Button variant='secondary' href='/'>
Wszystkie lata
Prezenty tego roku
</Button>
{years.map((year) => (
<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 = {
planned: 'bg-gray-100',
decided: 'bg-blue-100',
bought: 'bg-green-100',
wrapped: 'bg-purple-100',
decided: 'bg-red-300',
bought: 'bg-yellow-200',
ready: 'bg-green-100',
wrapped: 'bg-green-300',
};
const handleDelete = async () => {

View File

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

View File

@@ -24,7 +24,7 @@ export const PersonCardEdit: FC<Props> = ({ person }) => {
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)}>
Edytuj
</Button>

View File

@@ -10,7 +10,7 @@ export const PersonCard: FC<Person> = (person) => {
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='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'>
{name}
</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';
type Props = {
heading?: string;
year?: number;
};
export const YearOverview: FC<Props> = async ({ year }) => {
export const YearOverview: FC<Props> = async ({ year, heading }) => {
const currentYear = new Date().getFullYear();
const data = await DB.getYear(year ?? currentYear);
@@ -36,7 +37,7 @@ export const YearOverview: FC<Props> = async ({ year }) => {
return (
<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'>
<StatsCard title='Ilość prezentów' value={gifts.length} />
<StatsCard title='Planowane prezenty' value={planned.length} />

View File

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

View File

@@ -46,7 +46,7 @@ export const DB = {
link: string;
cost: number;
imageUrl: string;
status: 'planned' | 'decided' | 'bought' | 'wrapped';
status: Gift['status'];
year: string;
person: string;
}): Promise<Gift> => {
@@ -71,7 +71,7 @@ export const DB = {
link: string;
cost: number;
imageUrl: string;
status: 'planned' | 'decided' | 'bought' | 'wrapped';
status: Gift['status'];
year: string;
person: string;
},
@@ -88,9 +88,14 @@ export const DB = {
'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({
filter: `year = "${yearId}"`,
filter: `year = "${yearRecord.id}"`,
expand: 'year,person',
fields: '*,expand.year.year,expand.person.name,expand.year.id,expand.person.id',
});

View File

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

View File

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