Based on the codebase context, the calendar system in QCX consists of several interconnected components. Here are all the calendar implementations:
Core Components
CalendarNotepad Component
The main UI component CalendarNotepad provides a date-based note-taking interface with location tagging capabilities 1 . It features:
- Date navigation with a 7-day range selector
- Note creation via
⌘+Enter keyboard shortcut - Location tagging using current map position
- Fly-to navigation when clicking location icons
- Integration with
TimezoneClock component
Server Actions
The calendar system uses two server actions in lib/actions/calendar.ts2 :
getNotes(date, chatId) - Fetches notes for a specific date and user, optionally filtered by chat sessionsaveNote(noteData) - Inserts or updates notes, and creates a chat message for AI context when a note has a chatId
Database Schema
The calendarNotes table is defined in lib/db/schema.ts3 , storing:
- User and chat associations
- Date, content, and timestamps
- Location tags (GeoJSON), user tags, and map feature IDs
Type Definitions
Type definitions in lib/types/index.ts define the CalendarNote and NewCalendarNote interfaces 4 .
Integration Points
State Management
The CalendarToggleProvider in components/calendar-toggle-context.tsx manages calendar visibility state 5 .
UI Integration
- Header: Desktop calendar toggle button in
components/header.tsx6 - Mobile: Mobile calendar button in
components/mobile-icons-bar.tsx7 - Chat Layout: Renders
CalendarNotepad when calendar is open in both mobile and desktop layouts in components/chat.tsx89
Notes
The calendar system is tightly integrated with the map context, allowing users to tag notes with geographic locations and navigate to those locations. Notes can be associated with specific chat sessions to provide AI context.
Wiki pages you might want to explore:
Citations
File: components/calendar-notepad.tsx (L18-180)
exportfunctionCalendarNotepad({ chatId }: CalendarNotepadProps){const{ mapData, setMapData }=useMapData()const[selectedDate,setSelectedDate]=useState(newDate())const[notes,setNotes]=useState<CalendarNote[]>([])const[noteContent,setNoteContent]=useState("")const[dateOffset,setDateOffset]=useState(0)const[taggedLocation,setTaggedLocation]=useState<any|null>(null)useEffect(()=>{constfetchNotes=async()=>{constfetchedNotes=awaitgetNotes(selectedDate,chatId??null)setNotes(fetchedNotes)}fetchNotes()},[selectedDate,chatId])constgenerateDateRange=(offset: number)=>{constdates=[]consttoday=newDate()for(leti=0;i<7;i++){constdate=newDate(today)date.setDate(today.getDate()+offset+i)dates.push(date)}returndates}constdateRange=generateDateRange(dateOffset)constisSameDay=(date1: Date,date2: Date)=>{return(date1.getDate()===date2.getDate()&&date1.getMonth()===date2.getMonth()&&date1.getFullYear()===date2.getFullYear())}consthandleAddNote=async(e: React.KeyboardEvent<HTMLTextAreaElement>)=>{if(e.key==="Enter"&&(e.metaKey||e.ctrlKey)){if(!noteContent.trim())returnconstnewNote: NewCalendarNote={date: selectedDate,content: noteContent,chatId: chatId??null,userId: '',// This will be set on the serverlocationTags: taggedLocation,userTags: null,mapFeatureId: null,}constsavedNote=awaitsaveNote(newNote)if(savedNote){setNotes([savedNote, ...notes])setNoteContent("")setTaggedLocation(null)}}}consthandleTagLocation=()=>{if(mapData.targetPosition){setTaggedLocation({type: 'Point',coordinates: mapData.targetPosition});setNoteContent(prev=>`${prev} #location`);}};consthandleFlyTo=(location: any)=>{if(location&&location.coordinates){setMapData(prev=>({ ...prev,targetPosition: location.coordinates}));}};return(<divdata-testid="calendar-notepad"className="bg-card text-card-foreground shadow-lg rounded-lg p-4 max-w-2xl mx-auto my-4 border"><divclassName="flex items-center justify-between mb-4"><buttononClick={()=>setDateOffset(dateOffset-7)}className="p-2 text-muted-foreground hover:text-foreground"><ChevronLeftclassName="h-5 w-5"/></button><divclassName="flex space-x-2 overflow-x-auto">{dateRange.map((date)=>(<buttonkey={date.toISOString()}onClick={()=>setSelectedDate(date)}className={cn("flex flex-col items-center p-2 rounded-md transition-colors",isSameDay(date,selectedDate)
? "bg-primary text-primary-foreground"
: "hover:bg-accent")}><spanclassName="text-sm font-medium">{date.toLocaleDateString(undefined,{day: "numeric"})}</span><spanclassName="text-xs text-muted-foreground">{date.toLocaleDateString(undefined,{month: "short"})}</span></button>))}</div><buttononClick={()=>setDateOffset(dateOffset+7)}className="p-2 text-muted-foreground hover:text-foreground"><ChevronRightclassName="h-5 w-5"/></button></div><divclassName="mb-4"><divclassName="relative"><textareavalue={noteContent}onChange={(e)=>setNoteContent(e.target.value)}onKeyDown={handleAddNote}placeholder="Add note... (⌘+Enter to save, @mention, #location)"className="w-full p-2 bg-input rounded-md border focus:ring-ring focus:ring-2 focus:outline-none pr-10"rows={3}/><buttononClick={handleTagLocation}className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"><MapPinclassName="h-5 w-5"/></button></div></div><divclassName="space-y-4">{notes.length>0 ? (notes.map((note)=>(<divkey={note.id}className="p-3 bg-muted rounded-md"><divclassName="flex justify-between items-start"><div><pclassName="text-xs text-muted-foreground mb-1">{newDate(note.createdAt).toLocaleTimeString([],{hour: '2-digit',minute: '2-digit'})}</p><pclassName="text-sm whitespace-pre-wrap break-words">{note.content}</p></div>{note.locationTags&&(<buttononClick={()=>handleFlyTo(note.locationTags)}className="text-muted-foreground hover:text-foreground ml-2"><MapPinclassName="h-5 w-5"/></button>)}</div></div>))) : (<pclassName="text-center text-muted-foreground text-sm py-4">Nonotesforthisday.</p>)}</div><TimezoneClock/></div>)}File: lib/actions/calendar.ts (L1-112)
'use server'import{and,desc,eq,isNull,sql}from'drizzle-orm'import{db}from'@/lib/db'import{calendarNotes}from'@/lib/db/schema'import{getCurrentUserIdOnServer}from'@/lib/auth/get-current-user'importtype{CalendarNote,NewCalendarNote}from'@/lib/types'import{createMessage,NewMessage}from'./chat-db'/** * Retrieves notes for a specific date and chat session. * @param date - The date to fetch notes for. * @param chatId - The ID of the chat session. * @returns A promise that resolves to an array of notes. */exportasyncfunctiongetNotes(date: Date,chatId: string|null): Promise<CalendarNote[]>{constuserId=awaitgetCurrentUserIdOnServer()if(!userId){console.error('getNotes: User not authenticated')return[]}// Normalize date to the start of the day for consistent queryingconststartDate=newDate(date)startDate.setHours(0,0,0,0)constendDate=newDate(date)endDate.setHours(23,59,59,999)try{constwhereConditions=[eq(calendarNotes.userId,userId),and(sql`${calendarNotes.date} >= ${startDate}`,sql`${calendarNotes.date} <= ${endDate}`)];if(chatId){whereConditions.push(eq(calendarNotes.chatId,chatId));}else{whereConditions.push(isNull(calendarNotes.chatId));}constnotes=awaitdb.select().from(calendarNotes).where(and(...whereConditions)).orderBy(desc(calendarNotes.createdAt)).execute()returnnotes;}catch(error){console.error('Error fetching notes:',error)return[]}}/** * Saves a new note or updates an existing one. * @param noteData - The note data to save. * @returns A promise that resolves to the saved note or null if an error occurs. */exportasyncfunctionsaveNote(noteData: NewCalendarNote|CalendarNote): Promise<CalendarNote|null>{constuserId=awaitgetCurrentUserIdOnServer();if(!userId){console.error('saveNote: User not authenticated');returnnull;}if('id'innoteData){// Update existing notetry{const[updatedNote]=awaitdb.update(calendarNotes).set({ ...noteData,updatedAt: newDate()}).where(and(eq(calendarNotes.id,noteData.id),eq(calendarNotes.userId,userId))).returning();returnupdatedNote;}catch(error){console.error('Error updating note:',error);returnnull;}}else{// Create new notetry{const[newNote]=awaitdb.insert(calendarNotes).values({ ...noteData, userId }).returning();if(newNote&&newNote.chatId){constcalendarContextMessage: NewMessage={chatId: newNote.chatId,userId: userId,role: 'data',content: JSON.stringify({type: 'calendar_note',note: newNote,}),};awaitcreateMessage(calendarContextMessage);}returnnewNote;}catch(error){console.error('Error creating note:',error);returnnull;}}}File: lib/db/schema.ts (L92-103)
exportconstcalendarNotes=pgTable('calendar_notes',{id: uuid('id').primaryKey().defaultRandom(),userId: uuid('user_id').notNull().references(()=>users.id,{onDelete: 'cascade'}),chatId: uuid('chat_id').references(()=>chats.id,{onDelete: 'cascade'}),date: timestamp('date',{withTimezone: true}).notNull(),content: text('content').notNull(),locationTags: jsonb('location_tags'),userTags: text('user_tags').array(),mapFeatureId: text('map_feature_id'),createdAt: timestamp('created_at',{withTimezone: true}).defaultNow().notNull(),updatedAt: timestamp('updated_at',{withTimezone: true}).defaultNow().notNull(),});File: lib/types/index.ts (L79-92)
exporttypeCalendarNote={id: string;userId: string;chatId: string|null;date: Date;content: string;locationTags: any|null;userTags: string[]|null;mapFeatureId: string|null;createdAt: Date;updatedAt: Date;};exporttypeNewCalendarNote=Omit<CalendarNote,'id'|'createdAt'|'updatedAt'>;File: app/layout.tsx (L98-98)
File: components/header.tsx (L74-76)
<Buttonvariant="ghost"size="icon"onClick={toggleCalendar}title="Open Calendar"data-testid="calendar-toggle"><CalendarDaysclassName="h-[1.2rem] w-[1.2rem]"/></Button>File: components/mobile-icons-bar.tsx (L57-59)
<Buttonvariant="ghost"size="icon"onClick={toggleCalendar}title="Open Calendar"data-testid="mobile-calendar-button"><CalendarDaysclassName="h-[1.2rem] w-[1.2rem] transition-all rotate-0 scale-100"/></Button>File: components/chat.tsx (L159-161)
{isCalendarOpen ? (<CalendarNotepadchatId={id}/>) : (File: components/chat.tsx (L191-193)
{isCalendarOpen ? (<CalendarNotepadchatId={id}/>) : (
Based on the codebase context, the calendar system in QCX consists of several interconnected components. Here are all the calendar implementations:
Core Components
CalendarNotepad Component
The main UI component
CalendarNotepadprovides a date-based note-taking interface with location tagging capabilities 1 . It features:⌘+Enterkeyboard shortcutTimezoneClockcomponentServer Actions
The calendar system uses two server actions in
lib/actions/calendar.ts2 :getNotes(date, chatId)- Fetches notes for a specific date and user, optionally filtered by chat sessionsaveNote(noteData)- Inserts or updates notes, and creates a chat message for AI context when a note has achatIdDatabase Schema
The
calendarNotestable is defined inlib/db/schema.ts3 , storing:Type Definitions
Type definitions in
lib/types/index.tsdefine theCalendarNoteandNewCalendarNoteinterfaces 4 .Integration Points
State Management
The
CalendarToggleProviderincomponents/calendar-toggle-context.tsxmanages calendar visibility state 5 .UI Integration
components/header.tsx6components/mobile-icons-bar.tsx7CalendarNotepadwhen calendar is open in both mobile and desktop layouts incomponents/chat.tsx89Notes
The calendar system is tightly integrated with the map context, allowing users to tag notes with geographic locations and navigate to those locations. Notes can be associated with specific chat sessions to provide AI context.
Wiki pages you might want to explore:
Citations
File: components/calendar-notepad.tsx (L18-180)
File: lib/actions/calendar.ts (L1-112)
File: lib/db/schema.ts (L92-103)
File: lib/types/index.ts (L79-92)
File: app/layout.tsx (L98-98)
File: components/header.tsx (L74-76)
File: components/mobile-icons-bar.tsx (L57-59)
File: components/chat.tsx (L159-161)
File: components/chat.tsx (L191-193)