Den is the private cloud vault for your reminders, calendars and to-dos.

feat: working on new ui components

hayden.moe 7e9297f2 fe28f984

verified
Changed files
+219 -1
src
Den.Client.Web
src
components
routes
(app)
_home
calendar
+100
src/Den.Client.Web/src/components/views/calendar/calendar-view.tsx
··· 1 + import { cn } from "@/lib/utils"; 2 + import { useState } from "react"; 3 + 4 + export type CalendarEvent = { 5 + id: string; 6 + title: string; 7 + date: Date; 8 + }; 9 + 10 + export type CalendarProps = { 11 + events: CalendarEvent[]; 12 + onEventClick: (event: CalendarEvent) => void; 13 + }; 14 + 15 + export const CalendarView = ({ events, onEventClick }: CalendarProps) => { 16 + const [currentDate, setCurrentDate] = useState(new Date()) 17 + 18 + const year = currentDate.getFullYear() 19 + const month = currentDate.getMonth() 20 + 21 + const firstDayOfMonth = new Date(year, month, 1) 22 + const lastDayOfMonth = new Date(year, month + 1, 0) 23 + const startingDayOfWeek = firstDayOfMonth.getDay() 24 + const daysInMonth = lastDayOfMonth.getDate() 25 + 26 + const previousMonth = () => { 27 + setCurrentDate(new Date(year, month - 1, 1)) 28 + } 29 + 30 + const nextMonth = () => { 31 + setCurrentDate(new Date(year, month + 1, 1)) 32 + } 33 + 34 + const getEventsForDay = (day: number) => { 35 + return events.filter((event) => { 36 + const eventDate = new Date(event.date) 37 + return eventDate.getDate() === day && eventDate.getMonth() === month && eventDate.getFullYear() === year 38 + }) 39 + } 40 + 41 + const isToday = (day: number) => { 42 + const today = new Date() 43 + return today.getDate() === day && today.getMonth() === month && today.getFullYear() === year 44 + } 45 + 46 + // Create array of day cells including empty cells for alignment 47 + const calendarDays = [] 48 + 49 + // Add empty cells for days before the first day of month 50 + for (let i = 0; i < startingDayOfWeek; i++) { 51 + calendarDays.push(null) 52 + } 53 + 54 + // Add cells for each day of the month 55 + for (let day = 1; day <= daysInMonth; day++) { 56 + calendarDays.push(day) 57 + } 58 + 59 + return ( 60 + <div className="grid grid-cols-7 gap-2"> 61 + {calendarDays.map((day, index) => { 62 + if (day === null) { 63 + return <div key={`empty-${index}`} className="aspect-square" /> 64 + } 65 + 66 + const dayEvents = getEventsForDay(day); 67 + const isTodayDate = isToday(day); 68 + 69 + return ( 70 + <div 71 + key={day} 72 + className={cn( 73 + "aspect-square border rounded-lg p-2 hover:border-primary/50 transition-colors", 74 + isTodayDate && "bg-primary/5 border-primary" 75 + )} 76 + > 77 + <div className="h-full flex flex-col"> 78 + <span className={cn("text-sm font-medium mb-1", isTodayDate && "text-primary font-semibold")}>{day}</span> 79 + 80 + <div className="flex-1 flex flex-col gap-1 overflow-hidden"> 81 + {dayEvents.slice(0, 3).map((event) => ( 82 + <button 83 + key={event.id} 84 + onClick={() => onEventClick(event)} 85 + className={cn( 86 + "text-left text-xs px-2 py-1 rounded truncate transition-all hover:scale-105", 87 + )} 88 + > 89 + {event.title} 90 + </button> 91 + ))} 92 + </div> 93 + </div> 94 + 95 + </div> 96 + ); 97 + })} 98 + </div> 99 + ); 100 + };
+24
src/Den.Client.Web/src/components/views/calendar/event-card.tsx
··· 1 + import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; 2 + import type { CalendarEvent } from "./calendar-view"; 3 + 4 + export const EventCard = ({ event }: { event: CalendarEvent; }) => { 5 + return ( 6 + <Card> 7 + <CardHeader> 8 + <CardTitle>{event.title}</CardTitle> 9 + <CardDescription>{event.date.toLocaleString()}</CardDescription> 10 + </CardHeader> 11 + <CardContent> 12 + <dl className="grid grid-cols-1 gap-4"> 13 + <div className="flex flex-col gap-2"> 14 + <dt className="font-medium">Location</dt> 15 + <dd>123 Flower St, Springfield</dd> 16 + </div> 17 + 18 + <dt className="font-medium">Notes</dt> 19 + <dd>Don't forget to bring the flowers!</dd> 20 + </dl> 21 + </CardContent> 22 + </Card> 23 + ); 24 + }
+27 -1
src/Den.Client.Web/src/routeTree.gen.ts
··· 12 12 import { Route as authAuthRouteImport } from './routes/(auth)/_auth' 13 13 import { Route as appHomeRouteImport } from './routes/(app)/_home' 14 14 import { Route as appHomeIndexRouteImport } from './routes/(app)/_home.index' 15 + import { Route as appHomeCalendarIndexRouteImport } from './routes/(app)/_home/calendar/index' 15 16 import { Route as appHomeBudgetsIndexRouteImport } from './routes/(app)/_home/budgets/index' 16 17 import { Route as authAuthAuthSignupRouteImport } from './routes/(auth)/_auth.auth.signup' 17 18 import { Route as authAuthAuthLoginRouteImport } from './routes/(auth)/_auth.auth.login' ··· 28 29 const appHomeIndexRoute = appHomeIndexRouteImport.update({ 29 30 id: '/', 30 31 path: '/', 32 + getParentRoute: () => appHomeRoute, 33 + } as any) 34 + const appHomeCalendarIndexRoute = appHomeCalendarIndexRouteImport.update({ 35 + id: '/calendar/', 36 + path: '/calendar/', 31 37 getParentRoute: () => appHomeRoute, 32 38 } as any) 33 39 const appHomeBudgetsIndexRoute = appHomeBudgetsIndexRouteImport.update({ ··· 57 63 '/auth/login': typeof authAuthAuthLoginRoute 58 64 '/auth/signup': typeof authAuthAuthSignupRoute 59 65 '/budgets': typeof appHomeBudgetsIndexRoute 66 + '/calendar': typeof appHomeCalendarIndexRoute 60 67 } 61 68 export interface FileRoutesByTo { 62 69 '/': typeof appHomeIndexRoute ··· 64 71 '/auth/login': typeof authAuthAuthLoginRoute 65 72 '/auth/signup': typeof authAuthAuthSignupRoute 66 73 '/budgets': typeof appHomeBudgetsIndexRoute 74 + '/calendar': typeof appHomeCalendarIndexRoute 67 75 } 68 76 export interface FileRoutesById { 69 77 __root__: typeof rootRouteImport ··· 74 82 '/(auth)/_auth/auth/login': typeof authAuthAuthLoginRoute 75 83 '/(auth)/_auth/auth/signup': typeof authAuthAuthSignupRoute 76 84 '/(app)/_home/budgets/': typeof appHomeBudgetsIndexRoute 85 + '/(app)/_home/calendar/': typeof appHomeCalendarIndexRoute 77 86 } 78 87 export interface FileRouteTypes { 79 88 fileRoutesByFullPath: FileRoutesByFullPath ··· 83 92 | '/auth/login' 84 93 | '/auth/signup' 85 94 | '/budgets' 95 + | '/calendar' 86 96 fileRoutesByTo: FileRoutesByTo 87 - to: '/' | '/budgets/create' | '/auth/login' | '/auth/signup' | '/budgets' 97 + to: 98 + | '/' 99 + | '/budgets/create' 100 + | '/auth/login' 101 + | '/auth/signup' 102 + | '/budgets' 103 + | '/calendar' 88 104 id: 89 105 | '__root__' 90 106 | '/(app)/_home' ··· 94 110 | '/(auth)/_auth/auth/login' 95 111 | '/(auth)/_auth/auth/signup' 96 112 | '/(app)/_home/budgets/' 113 + | '/(app)/_home/calendar/' 97 114 fileRoutesById: FileRoutesById 98 115 } 99 116 export interface RootRouteChildren { ··· 124 141 preLoaderRoute: typeof appHomeIndexRouteImport 125 142 parentRoute: typeof appHomeRoute 126 143 } 144 + '/(app)/_home/calendar/': { 145 + id: '/(app)/_home/calendar/' 146 + path: '/calendar' 147 + fullPath: '/calendar' 148 + preLoaderRoute: typeof appHomeCalendarIndexRouteImport 149 + parentRoute: typeof appHomeRoute 150 + } 127 151 '/(app)/_home/budgets/': { 128 152 id: '/(app)/_home/budgets/' 129 153 path: '/budgets' ··· 159 183 appHomeIndexRoute: typeof appHomeIndexRoute 160 184 appHomeBudgetsCreateRoute: typeof appHomeBudgetsCreateRoute 161 185 appHomeBudgetsIndexRoute: typeof appHomeBudgetsIndexRoute 186 + appHomeCalendarIndexRoute: typeof appHomeCalendarIndexRoute 162 187 } 163 188 164 189 const appHomeRouteChildren: appHomeRouteChildren = { 165 190 appHomeIndexRoute: appHomeIndexRoute, 166 191 appHomeBudgetsCreateRoute: appHomeBudgetsCreateRoute, 167 192 appHomeBudgetsIndexRoute: appHomeBudgetsIndexRoute, 193 + appHomeCalendarIndexRoute: appHomeCalendarIndexRoute, 168 194 } 169 195 170 196 const appHomeRouteWithChildren =
+68
src/Den.Client.Web/src/routes/(app)/_home/calendar/index.tsx
··· 1 + import { Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbSeparator, BreadcrumbPage } from '@/components/ui/breadcrumb'; 2 + import { SidebarTrigger } from '@/components/ui/sidebar'; 3 + import { Separator } from '@/components/ui/separator'; 4 + import { createFileRoute, Link } from '@tanstack/react-router' 5 + import { useTranslation } from 'react-i18next'; 6 + import { CalendarView, type CalendarEvent } from '@/components/views/calendar/calendar-view'; 7 + import { EventCard } from '@/components/views/calendar/event-card'; 8 + import { useState } from 'react'; 9 + import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty'; 10 + import { CalendarIcon } from 'lucide-react'; 11 + 12 + export const Route = createFileRoute('/(app)/_home/calendar/')({ 13 + component: () => { 14 + const [events, _setEvents] = useState<CalendarEvent[]>([ 15 + { id: 'abc123', title: 'Flower Delivery', date: new Date() }, 16 + { id: 'def456', title: 'Meeting with Bob', date: new Date() }, 17 + ]); 18 + const { t } = useTranslation(); 19 + 20 + return ( 21 + <> 22 + <header className="flex h-16 shrink-0 items-center gap-2 border-b px-4"> 23 + <SidebarTrigger /> 24 + <Separator orientation="vertical" className="data-[orientation=vertical]:h-4" /> 25 + <Breadcrumb> 26 + <BreadcrumbList> 27 + <BreadcrumbItem className="hidden md:block"> 28 + <BreadcrumbLink asChild> 29 + <Link to="/budgets">{t('menu.budgeting.title')}</Link> 30 + </BreadcrumbLink> 31 + </BreadcrumbItem> 32 + <BreadcrumbSeparator /> 33 + <BreadcrumbItem> 34 + <BreadcrumbPage> 35 + {t('menu.budgeting.allBudgets')} 36 + </BreadcrumbPage> 37 + </BreadcrumbItem> 38 + </BreadcrumbList> 39 + </Breadcrumb> 40 + </header> 41 + 42 + <div className="flex flex-1 flex-col gap-4 p-4"> 43 + <div className="grid flex-1 grid-cols-1 lg:grid-cols-3 gap-4"> 44 + <div className="flex flex-col gap-6"> 45 + {events.length === 0 && ( 46 + <Empty> 47 + <EmptyHeader> 48 + <EmptyMedia variant="icon"> 49 + <CalendarIcon /> 50 + </EmptyMedia> 51 + <EmptyTitle>No Events</EmptyTitle> 52 + <EmptyDescription> 53 + You have no events scheduled. Your events will appear here. 54 + </EmptyDescription> 55 + </EmptyHeader> 56 + </Empty> 57 + )} 58 + {events.map((ev, idx) => <EventCard key={idx} event={ev} />)} 59 + </div> 60 + <div className="lg:col-span-2"> 61 + <CalendarView events={events} onEventClick={() => {}} /> 62 + </div> 63 + </div> 64 + </div> 65 + </> 66 + ); 67 + } 68 + });