diff --git a/src/app/[locale]/dashboard/rental-history/page.tsx b/src/app/[locale]/dashboard/rental-history/page.tsx new file mode 100644 index 0000000..a753792 --- /dev/null +++ b/src/app/[locale]/dashboard/rental-history/page.tsx @@ -0,0 +1,115 @@ + +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { History, ToyBrick } from "lucide-react"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; +import Image from "next/image"; +import type { RentalHistoryEntry } from "@/types"; +import { mockRentalHistory } from "@/lib/mockData"; +import { getI18n } from "@/locales/server"; +import type { Locale } from "@/locales/server"; +import { Badge } from "@/components/ui/badge"; +import { format } from 'date-fns'; + +const currentUserId = 'user1'; // Assume this is the logged-in user's ID + +export default async function RentalHistoryPage({ params }: { params: { locale: Locale } }) { + const t = await getI18n(); + const locale = params.locale; + + // Filter rental history for the current user + const userRentalHistory = mockRentalHistory.filter(entry => entry.userId === currentUserId); + + return ( +
+
+

{t('dashboard.rental_history.title')}

+

{t('dashboard.rental_history.description')}

+
+ + {userRentalHistory.length === 0 ? ( + + + + {t('dashboard.rental_history.no_history_title')} + {t('dashboard.rental_history.no_history_description')} + + + + + + + + ) : ( +
+ {userRentalHistory.map(entry => ( + + ))} +
+ )} +
+ ); +} + +interface RentalHistoryItemCardProps { + item: RentalHistoryEntry; + t: (key: string, params?: Record) => string; + locale: Locale; +} + +function RentalHistoryItemCard({ item, t, locale }: RentalHistoryItemCardProps) { + const placeholderHint = item.dataAiHint || item.toy.category.toLowerCase() || "toy"; + const formattedStartDate = format(new Date(item.rentalStartDate), 'PP'); + const formattedEndDate = format(new Date(item.rentalEndDate), 'PP'); + + const getStatusTextKey = (status: RentalHistoryEntry['status']) => { + if (status === 'Completed') return 'rental_history_card.status_completed'; + if (status === 'Returned') return 'rental_history_card.status_returned'; + return ''; + } + + return ( + +
+
+ {item.toy.name} +
+
+
+ {item.toy.name} + {t(getStatusTextKey(item.status))} +
+

+ {t('rental_history_card.rented_from')}: {item.toy.ownerName} +

+

+ {t('rental_history_card.rental_period')}: {formattedStartDate} - {formattedEndDate} +

+

+ {t('rental_history_card.cost')}: ${item.totalCost.toFixed(2)} +

+

+ {t('rental_history_card.status')}: {t(getStatusTextKey(item.status))} +

+ +
+ + + +
+
+
+
+ ); +} + diff --git a/src/components/layout/DashboardSidebar.tsx b/src/components/layout/DashboardSidebar.tsx index c68273b..8fb00e1 100644 --- a/src/components/layout/DashboardSidebar.tsx +++ b/src/components/layout/DashboardSidebar.tsx @@ -2,7 +2,7 @@ import Link from 'next/link'; import { usePathname, useRouter } from 'next/navigation'; -import { Home, ToyBrick, PlusCircle, ListOrdered, Settings, ShoppingBag, LogOutIcon } from 'lucide-react'; // Changed LogOut to LogOutIcon +import { Home, ToyBrick, PlusCircle, ListOrdered, Settings, ShoppingBag, LogOutIcon, History } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { cn } from '@/lib/utils'; import { Separator } from '@/components/ui/separator'; @@ -22,6 +22,7 @@ export default function DashboardSidebar() { { href: '/dashboard/my-toys/add', label: t('dashboard.sidebar.add_new_toy'), icon: PlusCircle }, { href: '/dashboard/rentals', label: t('dashboard.sidebar.my_rentals'), icon: ShoppingBag }, { href: '/dashboard/requests', label: t('dashboard.sidebar.rental_requests'), icon: ListOrdered }, + { href: '/dashboard/rental-history', label: t('dashboard.sidebar.rental_history'), icon: History }, ]; const accountNavItems = [ @@ -30,12 +31,11 @@ export default function DashboardSidebar() { const handleLogout = () => { localStorage.removeItem('isToyShareAuthenticated'); - toast({ description: "You have been logged out." }); // Translate if needed - router.push(`/${locale}/`); // Redirect to localized home page + toast({ description: "You have been logged out." }); + router.push(`/${locale}/`); }; const NavLink = ({ href, label, icon: Icon }: typeof sidebarNavItems[0] & {icon: React.ElementType}) => { - // For active link comparison, remove locale prefix from pathname const cleanPathname = pathname.startsWith(`/${locale}`) ? pathname.substring(`/${locale}`.length) || '/' : pathname; @@ -46,7 +46,7 @@ export default function DashboardSidebar() { const isActive = cleanPathname === cleanHref || (cleanHref !== '/dashboard' && cleanPathname.startsWith(cleanHref)); return ( - + diff --git a/src/lib/mockData.ts b/src/lib/mockData.ts index e38c8e3..9a659f1 100644 --- a/src/lib/mockData.ts +++ b/src/lib/mockData.ts @@ -1,4 +1,4 @@ -import type { Toy } from '@/types'; +import type { Toy, RentalHistoryEntry } from '@/types'; export const mockToys: Toy[] = [ { @@ -47,7 +47,7 @@ export const mockToys: Toy[] = [ category: 'Plush Toys', images: ['https://placehold.co/600x400.png?text=Teddy+Bear'], availability: { monday: true, tuesday: true, wednesday: true, thursday: true, friday: true, saturday: true, sunday: true }, - ownerName: 'David Copperfield', + ownerName: 'Alice Wonderland', // Changed owner for variety, was David Copperfield ownerId: 'user1', pricePerDay: 3, location: 'Springfield Gardens', @@ -60,7 +60,7 @@ export const mockToys: Toy[] = [ category: 'Musical', images: ['https://placehold.co/600x400.png?text=Kids+Guitar'], availability: { monday: true, tuesday: false, wednesday: true, thursday: false, friday: true, saturday: true, sunday: true }, - ownerName: 'Eve Adamson', + ownerName: 'Bob The Builder', // Changed owner, was Eve Adamson ownerId: 'user2', pricePerDay: 10, location: 'Willow Creek', @@ -73,7 +73,7 @@ export const mockToys: Toy[] = [ category: 'Outdoor', images: ['https://placehold.co/600x400.png?text=Sports+Kit'], availability: { monday: true, tuesday: true, wednesday: true, thursday: true, friday: true, saturday: true, sunday: true }, - ownerName: 'Frank Castle', + ownerName: 'Carol Danvers', // Changed owner, was Frank Castle ownerId: 'user3', pricePerDay: 6, location: 'Metro City', @@ -81,13 +81,47 @@ export const mockToys: Toy[] = [ } ]; -// Add dataAiHint to mockToys where Toy might have it. +export const mockRentalHistory: RentalHistoryEntry[] = [ + { + id: 'hist1', + userId: 'user1', + toy: mockToys[2], // Interactive Learning Tablet from Carol Danvers (user3) + rentalStartDate: '2024-05-01', + rentalEndDate: '2024-05-07', + totalCost: mockToys[2].pricePerDay! * 7, + status: 'Completed', + dataAiHint: mockToys[2].category.toLowerCase(), + }, + { + id: 'hist2', + userId: 'user1', + toy: mockToys[5], // Outdoor Sports Kit from Carol Danvers (user3) + rentalStartDate: '2024-06-10', + rentalEndDate: '2024-06-15', + totalCost: mockToys[5].pricePerDay! * 5, + status: 'Returned', + dataAiHint: mockToys[5].category.toLowerCase(), + }, + { + id: 'hist3', + userId: 'user2', // Different user + toy: mockToys[0], // Building Blocks from Alice Wonderland (user1) + rentalStartDate: '2024-07-01', + rentalEndDate: '2024-07-10', + totalCost: mockToys[0].pricePerDay! * 10, + status: 'Completed', + dataAiHint: mockToys[0].category.toLowerCase(), + } +]; + mockToys.forEach(toy => { - if ('dataAiHint' in toy && toy.images.length > 0) { - // This is a bit of a hack since the Toy interface doesn't have dataAiHint - // and images don't store this attribute directly. - // In a real scenario, this would be part of the image object or derived. - // For now, we'll assume the first image can have this hint if the toy object does. - // This is primarily for satisfying the placeholder image hint requirement in the prompt. + if (!toy.dataAiHint) { + toy.dataAiHint = toy.category.toLowerCase().split(' ')[0]; + } +}); + +mockRentalHistory.forEach(entry => { + if (!entry.dataAiHint) { + entry.dataAiHint = entry.toy.category.toLowerCase().split(' ')[0]; } }); diff --git a/src/locales/en.ts b/src/locales/en.ts index 50c05e6..24f5721 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -53,6 +53,7 @@ export default { 'dashboard.sidebar.add_new_toy': 'Add New Toy', 'dashboard.sidebar.my_rentals': 'My Rentals', 'dashboard.sidebar.rental_requests': 'Rental Requests', + 'dashboard.sidebar.rental_history': 'Rental History', 'dashboard.sidebar.account': 'Account', 'dashboard.sidebar.profile_settings': 'Profile Settings', 'dashboard.sidebar.logout': 'Logout', @@ -216,4 +217,17 @@ export default { 'admin.toys.table_header_actions': 'Actions', 'admin.toys.edit_button': 'Edit Toy', 'admin.toys.no_toys_found': 'No toys found.', + + 'dashboard.rental_history.title': 'My Rental History', + 'dashboard.rental_history.description': 'View your past toy rentals.', + 'dashboard.rental_history.no_history_title': 'No Rental History Yet', + 'dashboard.rental_history.no_history_description': 'Once you rent and return toys, they will appear here.', + 'dashboard.rental_history.browse_toys_button': 'Browse Toys to Rent', + 'rental_history_card.rented_from': 'Rented from', + 'rental_history_card.rental_period': 'Rental Period', + 'rental_history_card.status': 'Status', + 'rental_history_card.cost': 'Total Cost', + 'rental_history_card.view_toy_button': 'View Toy', + 'rental_history_card.status_completed': 'Completed', + 'rental_history_card.status_returned': 'Returned', } as const; diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts index 0bab20f..abb187b 100644 --- a/src/locales/zh-TW.ts +++ b/src/locales/zh-TW.ts @@ -53,6 +53,7 @@ export default { 'dashboard.sidebar.add_new_toy': '新增玩具', 'dashboard.sidebar.my_rentals': '我的租借', 'dashboard.sidebar.rental_requests': '租借請求', + 'dashboard.sidebar.rental_history': '租借歷史', 'dashboard.sidebar.account': '帳戶', 'dashboard.sidebar.profile_settings': '個人資料設定', 'dashboard.sidebar.logout': '登出', @@ -141,7 +142,7 @@ export default { 'dashboard.requests.description': '管理您玩具的租借請求。', 'dashboard.requests.no_requests_title': '沒有租借請求', 'dashboard.requests.no_requests_description': '您目前沒有任何待處理的玩具租借請求。', - 'dashboard.requests.no_requests_content': '當有人請求租借您的玩具時,請求將會出現在這裡。', + 'dashboard.requests.no_requests_content': '当有人请求租用您的玩具时,它会显示在此处。', 'dashboard.requests.requested_by': '請求者', 'dashboard.requests.dates': '日期', 'dashboard.requests.message': '訊息', @@ -216,4 +217,17 @@ export default { 'admin.toys.table_header_actions': '操作', 'admin.toys.edit_button': '編輯玩具', 'admin.toys.no_toys_found': '找不到玩具。', + + 'dashboard.rental_history.title': '我的租借歷史', + 'dashboard.rental_history.description': '查看您過去的玩具租借記錄。', + 'dashboard.rental_history.no_history_title': '尚無租借歷史', + 'dashboard.rental_history.no_history_description': '當您租借並歸還玩具後,它們將會出現在這裡。', + 'dashboard.rental_history.browse_toys_button': '瀏覽可租借的玩具', + 'rental_history_card.rented_from': '租借自', + 'rental_history_card.rental_period': '租借期間', + 'rental_history_card.status': '狀態', + 'rental_history_card.cost': '總費用', + 'rental_history_card.view_toy_button': '查看玩具', + 'rental_history_card.status_completed': '已完成', + 'rental_history_card.status_returned': '已歸還', } as const; diff --git a/src/types/index.ts b/src/types/index.ts index 85522d9..b0e2c6f 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -18,6 +18,7 @@ export interface Toy { ownerId: string; pricePerDay?: number; // Optional daily rental price location?: string; // Optional, e.g., "City, State" or "Neighborhood" + dataAiHint?: string; // For placeholder image keyword hint } export interface User { @@ -33,3 +34,14 @@ export interface DailyAvailability { isAvailable: boolean; bookedBy?: string; // User ID of the renter if booked } + +export interface RentalHistoryEntry { + id: string; + userId: string; // ID of the user who rented + toy: Toy; + rentalStartDate: string; // ISO date string + rentalEndDate: string; // ISO date string + totalCost: number; + status: 'Completed' | 'Returned'; // Example statuses + dataAiHint?: string; +}