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:
@@ -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} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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'>
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
56
src/components/organisms/YearGifts/index.tsx
Normal file
56
src/components/organisms/YearGifts/index.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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} />
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
|
||||
2
src/lib/db/types.d.ts
vendored
2
src/lib/db/types.d.ts
vendored
@@ -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;
|
||||
|
||||
|
||||
2
src/types/global.d.ts
vendored
2
src/types/global.d.ts
vendored
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user