diff --git a/apps/desktop/src/components/main/body/calendars.tsx b/apps/desktop/src/components/main/body/calendars.tsx deleted file mode 100644 index 185dd6b831..0000000000 --- a/apps/desktop/src/components/main/body/calendars.tsx +++ /dev/null @@ -1,528 +0,0 @@ -import { Button } from "@hypr/ui/components/ui/button"; -import { ButtonGroup } from "@hypr/ui/components/ui/button-group"; -import { Checkbox } from "@hypr/ui/components/ui/checkbox"; -import { Popover, PopoverContent, PopoverTrigger } from "@hypr/ui/components/ui/popover"; -import { - addDays, - addMonths, - cn, - eachDayOfInterval, - format, - getDay, - isSameDay, - isSameMonth, - startOfMonth, - subDays, -} from "@hypr/utils"; - -import { Calendar, CalendarDays, ChevronLeft, ChevronRight, FileText, Pen } from "lucide-react"; -import { useEffect, useRef, useState } from "react"; - -import * as persisted from "../../../store/tinybase/persisted"; -import { type Tab, useTabs } from "../../../store/zustand/tabs"; -import { StandardTabWrapper } from "./index"; -import { type TabItem, TabItemBase } from "./shared"; - -export const TabItemCalendar: TabItem> = ( - { - tab, - tabIndex, - handleCloseThis, - handleSelectThis, - handleCloseOthers, - handleCloseAll, - }, -) => { - return ( - } - title={"Calendar"} - active={tab.active} - tabIndex={tabIndex} - handleCloseThis={() => handleCloseThis(tab)} - handleSelectThis={() => handleSelectThis(tab)} - handleCloseOthers={handleCloseOthers} - handleCloseAll={handleCloseAll} - /> - ); -}; - -export function TabContentCalendar({ tab }: { tab: Tab }) { - if (tab.type !== "calendars") { - return null; - } - - const [sidebarOpen, setSidebarOpen] = useState(false); - - const { openCurrent } = useTabs(); - - const calendarIds = persisted.UI.useRowIds("calendars", persisted.STORE_ID); - - const [selectedCalendars, setSelectedCalendars] = useState>(() => new Set(calendarIds)); - - const toggleCalendar = (calendarId: string) => { - setSelectedCalendars((prev) => { - const next = new Set(prev); - if (next.has(calendarId)) { - next.delete(calendarId); - } else { - next.add(calendarId); - } - return next; - }); - }; - const monthStart = startOfMonth(tab.month); - const startDayOfWeek = getDay(monthStart); - const weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; - const monthLabel = format(tab.month, "MMMM yyyy"); - - // Calculate all days to display including previous and next month - const calendarStart = subDays(monthStart, startDayOfWeek); - const totalCells = 42; // 6 rows * 7 days - const calendarEnd = addDays(calendarStart, totalCells - 1); - const allDays = eachDayOfInterval({ start: calendarStart, end: calendarEnd }).map((day) => format(day, "yyyy-MM-dd")); - - const handlePreviousMonth = () => { - openCurrent({ ...tab, month: addMonths(tab.month, -1) }); - }; - - const handleNextMonth = () => { - openCurrent({ ...tab, month: addMonths(tab.month, 1) }); - }; - - const handleToday = () => { - openCurrent({ ...tab, month: new Date() }); - }; - - return ( - -
- {sidebarOpen && ( - - )} - -
-
-
- {!sidebarOpen && ( - - )} -
{monthLabel}
- - - - - - - -
- -
- {weekDays.map((day) => ( -
- {day} -
- ))} -
-
- -
- {Array.from({ length: 6 }).map((_, weekIndex) => ( -
- {allDays.slice(weekIndex * 7, (weekIndex + 1) * 7).map((day, dayIndex) => ( - - ))} -
- ))} -
-
-
-
- ); -} - -function CalendarCheckboxRow( - { id, checked, onToggle }: { id: string; checked: boolean; onToggle: () => void }, -) { - const calendar = persisted.UI.useRow("calendars", id, persisted.STORE_ID); - return ( -
- - -
- ); -} - -function TabContentCalendarDay({ - day, - isCurrentMonth, - isFirstColumn, - isLastRow, - selectedCalendars, -}: { - day: string; - isCurrentMonth: boolean; - isFirstColumn: boolean; - isLastColumn: boolean; - isLastRow: boolean; - selectedCalendars: Set; -}) { - const cellRef = useRef(null); - const contentRef = useRef(null); - const [maxVisibleItems, setMaxVisibleItems] = useState(5); - - const allEventIds = persisted.UI.useSliceRowIds( - persisted.INDEXES.eventsByDate, - day, - persisted.STORE_ID, - ); - - const store = persisted.UI.useStore(persisted.STORE_ID); - - const eventIds = allEventIds.filter((eventId) => { - const event = store?.getRow("events", eventId); - return event?.calendar_id && selectedCalendars.has(event.calendar_id as string); - }); - - const sessionIds = persisted.UI.useSliceRowIds( - persisted.INDEXES.sessionByDateWithoutEvent, - day, - persisted.STORE_ID, - ); - - const dayNumber = format(new Date(day), "d"); - const isToday = format(new Date(), "yyyy-MM-dd") === day; - const dayOfWeek = getDay(new Date(day)); - const isWeekend = dayOfWeek === 0 || dayOfWeek === 6; - - // Measure actual available height and calculate max visible items - useEffect(() => { - const measureHeight = () => { - if (cellRef.current && contentRef.current) { - const cellHeight = cellRef.current.clientHeight; - const contentTop = contentRef.current.offsetTop; - const availableHeight = cellHeight - contentTop; - const EVENT_HEIGHT = 20; // height of each event item (h-5) - const SPACING = 4; // space-y-1 - - // Calculate how many items can fit - const itemsWithSpacing = Math.floor((availableHeight + SPACING) / (EVENT_HEIGHT + SPACING)); - // Reserve space for "+x more" if needed - setMaxVisibleItems(Math.max(1, itemsWithSpacing)); - } - }; - - measureHeight(); - - // Re-measure on window resize - window.addEventListener("resize", measureHeight); - return () => window.removeEventListener("resize", measureHeight); - }, []); - - const totalItems = eventIds.length + sessionIds.length; - const visibleCount = totalItems > maxVisibleItems - ? maxVisibleItems - 1 - : totalItems; - const hiddenCount = totalItems - visibleCount; - - const allItems = [ - ...eventIds.map(id => ({ type: "event" as const, id })), - ...sessionIds.map(id => ({ type: "session" as const, id })), - ]; - - const visibleItems = allItems.slice(0, visibleCount); - const hiddenItems = allItems.slice(visibleCount); - - const hiddenEventIds = hiddenItems - .filter(item => item.type === "event") - .map(item => item.id); - const hiddenSessionIds = hiddenItems - .filter(item => item.type === "session") - .map(item => item.id); - - return ( -
-
- - {dayNumber} - -
- -
- {visibleItems.map((item) => - item.type === "event" - ? - : - )} - - {hiddenCount > 0 && ( - - )} -
-
- ); -} - -function TabContentCalendarDayEvents({ eventId }: { eventId: string }) { - const event = persisted.UI.useRow("events", eventId, persisted.STORE_ID); - const [open, setOpen] = useState(false); - const { openNew } = useTabs(); - - const sessionIds = persisted.UI.useSliceRowIds( - persisted.INDEXES.sessionsByEvent, - eventId, - persisted.STORE_ID, - ); - const linkedSessionId = sessionIds[0]; - const linkedSession = persisted.UI.useRow("sessions", linkedSessionId || "dummy", persisted.STORE_ID); - - const handleClick = () => { - setOpen(false); - - if (linkedSessionId) { - openNew({ type: "sessions", id: linkedSessionId, state: { editor: "raw" } }); - } else { - openNew({ type: "sessions", id: crypto.randomUUID(), state: { editor: "raw" } }); - } - }; - - const formatEventTime = () => { - if (!event.started_at || !event.ended_at) { - return ""; - } - const start = new Date(event.started_at); - const end = new Date(event.ended_at); - - if (isNaN(start.getTime()) || isNaN(end.getTime())) { - return ""; - } - - if (isSameDay(start, end)) { - return `${format(start, "MMM d")}, ${format(start, "h:mm a")} - ${format(end, "h:mm a")}`; - } - return `${format(start, "MMM d")}, ${format(start, "h:mm a")} - ${format(end, "MMM d")}, ${format(end, "h:mm a")}`; - }; - - return ( - - -
- -
- {event.title} -
-
-
- -
- {event.title || "Untitled Event"} -
- -

- {formatEventTime()} -

- - {linkedSessionId - ? ( -
- -
- {linkedSession?.title || "Untitled Note"} -
-
- ) - : ( -
- -
- Create Note -
-
- )} -
-
- ); -} - -function TabContentCalendarDaySessions({ sessionId }: { sessionId: string }) { - const session = persisted.UI.useRow("sessions", sessionId, persisted.STORE_ID); - const [open, setOpen] = useState(false); - const { openNew } = useTabs(); - - const event = persisted.UI.useRow("events", session.event_id || "dummy", persisted.STORE_ID); - - const handleClick = () => { - setOpen(false); - openNew({ type: "sessions", id: sessionId, state: { editor: "raw" } }); - }; - - const formatSessionTime = () => { - if (!session.created_at) { - return "Created: —"; - } - const created = new Date(session.created_at); - if (isNaN(created.getTime())) { - return "Created: —"; - } - return `Created: ${format(created, "MMM d, h:mm a")}`; - }; - - return ( - - -
- -
- {event && session.event_id ? event.title : session.title || "Untitled"} -
-
-
- -

- {event && session.event_id ? event.title : session.title || "Untitled"} -

- -

- {formatSessionTime()} -

- -
- -
- {event && session.event_id ? event.title : session.title || "Untitled"} -
-
-
-
- ); -} - -function TabContentCalendarDayMore({ - day, - eventIds, - sessionIds, - hiddenCount, -}: { - day: string; - eventIds: string[]; - sessionIds: string[]; - hiddenCount: number; -}) { - const [open, setOpen] = useState(false); - - return ( - - -
- +{hiddenCount} more -
-
- -
- {format(new Date(day), "MMMM d, yyyy")} -
- -
- {eventIds.map((eventId) => )} - {sessionIds.map((sessionId) => )} -
-
-
- ); -} diff --git a/apps/desktop/src/components/main/body/calendars/calendar-checkbox-row.tsx b/apps/desktop/src/components/main/body/calendars/calendar-checkbox-row.tsx new file mode 100644 index 0000000000..24efa6b784 --- /dev/null +++ b/apps/desktop/src/components/main/body/calendars/calendar-checkbox-row.tsx @@ -0,0 +1,24 @@ +import { Checkbox } from "@hypr/ui/components/ui/checkbox"; + +import * as persisted from "../../../../store/tinybase/persisted"; + +export function CalendarCheckboxRow( + { id, checked, onToggle }: { id: string; checked: boolean; onToggle: (checked: boolean) => void }, +) { + const calendar = persisted.UI.useRow("calendars", id, persisted.STORE_ID); + return ( +
+ onToggle(Boolean(v))} + /> + +
+ ); +} diff --git a/apps/desktop/src/components/main/body/calendars/calendar-day.tsx b/apps/desktop/src/components/main/body/calendars/calendar-day.tsx new file mode 100644 index 0000000000..fe2a7ba6fd --- /dev/null +++ b/apps/desktop/src/components/main/body/calendars/calendar-day.tsx @@ -0,0 +1,143 @@ +import { cn, format, getDay } from "@hypr/utils"; + +import { useEffect, useRef, useState } from "react"; + +import * as persisted from "../../../../store/tinybase/persisted"; +import { TabContentCalendarDayEvents } from "./day-events"; +import { TabContentCalendarDayMore } from "./day-more"; +import { TabContentCalendarDaySessions } from "./day-sessions"; + +export function TabContentCalendarDay({ + day, + isCurrentMonth, + isFirstColumn, + isLastRow, + selectedCalendars, +}: { + day: string; + isCurrentMonth: boolean; + isFirstColumn: boolean; + isLastRow: boolean; + selectedCalendars: Set; +}) { + const cellRef = useRef(null); + const contentRef = useRef(null); + const [maxVisibleItems, setMaxVisibleItems] = useState(5); + + const allEventIds = persisted.UI.useSliceRowIds( + persisted.INDEXES.eventsByDate, + day, + persisted.STORE_ID, + ); + + const store = persisted.UI.useStore(persisted.STORE_ID); + + const eventIds = allEventIds.filter((eventId) => { + const event = store?.getRow("events", eventId); + return event?.calendar_id && selectedCalendars.has(event.calendar_id as string); + }); + + const sessionIds = persisted.UI.useSliceRowIds( + persisted.INDEXES.sessionByDateWithoutEvent, + day, + persisted.STORE_ID, + ); + + const dayNumber = format(new Date(day), "d"); + const isToday = format(new Date(), "yyyy-MM-dd") === day; + const dayOfWeek = getDay(new Date(day)); + const isWeekend = dayOfWeek === 0 || dayOfWeek === 6; + + // Measure actual available height and calculate max visible items + useEffect(() => { + const measureHeight = () => { + if (cellRef.current && contentRef.current) { + const cellHeight = cellRef.current.clientHeight; + const contentTop = contentRef.current.offsetTop; + const availableHeight = cellHeight - contentTop; + const EVENT_HEIGHT = 20; // height of each event item (h-5) + const SPACING = 4; // space-y-1 + + // Calculate how many items can fit + const itemsWithSpacing = Math.floor((availableHeight + SPACING) / (EVENT_HEIGHT + SPACING)); + // Reserve space for "+x more" if needed + setMaxVisibleItems(Math.max(1, itemsWithSpacing)); + } + }; + + measureHeight(); + + // Re-measure on window resize + window.addEventListener("resize", measureHeight); + return () => window.removeEventListener("resize", measureHeight); + }, []); + + const totalItems = eventIds.length + sessionIds.length; + const visibleCount = totalItems > maxVisibleItems + ? maxVisibleItems - 1 + : totalItems; + const hiddenCount = totalItems - visibleCount; + + const allItems = [ + ...eventIds.map(id => ({ type: "event" as const, id })), + ...sessionIds.map(id => ({ type: "session" as const, id })), + ]; + + const visibleItems = allItems.slice(0, visibleCount); + const hiddenItems = allItems.slice(visibleCount); + + const hiddenEventIds = hiddenItems + .filter(item => item.type === "event") + .map(item => item.id); + const hiddenSessionIds = hiddenItems + .filter(item => item.type === "session") + .map(item => item.id); + + return ( +
+
+ + {dayNumber} + +
+ +
+ {visibleItems.map((item) => + item.type === "event" + ? + : + )} + + {hiddenCount > 0 && ( + + )} +
+
+ ); +} diff --git a/apps/desktop/src/components/main/body/calendars/day-events.tsx b/apps/desktop/src/components/main/body/calendars/day-events.tsx new file mode 100644 index 0000000000..65170f5e0e --- /dev/null +++ b/apps/desktop/src/components/main/body/calendars/day-events.tsx @@ -0,0 +1,91 @@ +import { Button } from "@hypr/ui/components/ui/button"; +import { Popover, PopoverContent, PopoverTrigger } from "@hypr/ui/components/ui/popover"; +import { cn, format, isSameDay } from "@hypr/utils"; + +import { Calendar, Pen, StickyNote } from "lucide-react"; +import { useState } from "react"; + +import * as persisted from "../../../../store/tinybase/persisted"; +import { useTabs } from "../../../../store/zustand/tabs"; + +export function TabContentCalendarDayEvents({ eventId }: { eventId: string }) { + const event = persisted.UI.useRow("events", eventId, persisted.STORE_ID); + const [open, setOpen] = useState(false); + const { openNew } = useTabs(); + + const title = event?.title || "Untitled Event"; + + const sessionIds = persisted.UI.useSliceRowIds( + persisted.INDEXES.sessionsByEvent, + eventId, + persisted.STORE_ID, + ); + const linkedSessionId = sessionIds[0]; + const linkedSession = persisted.UI.useRow("sessions", linkedSessionId || "dummy", persisted.STORE_ID); + + const handleOpenNote = () => { + setOpen(false); + + if (linkedSessionId) { + openNew({ type: "sessions", id: linkedSessionId, state: { editor: "raw" } }); + } else { + openNew({ type: "sessions", id: crypto.randomUUID(), state: { editor: "raw" } }); + } + }; + + const formatEventTime = () => { + if (!event || !event.started_at || !event.ended_at) { + return ""; + } + const start = new Date(event.started_at); + const end = new Date(event.ended_at); + + if (isNaN(start.getTime()) || isNaN(end.getTime())) { + return ""; + } + + if (isSameDay(start, end)) { + return `${format(start, "MMM d")}, ${format(start, "h:mm a")} - ${format(end, "h:mm a")}`; + } + return `${format(start, "MMM d")}, ${format(start, "h:mm a")} - ${format(end, "MMM d")}, ${format(end, "h:mm a")}`; + }; + + return ( + + + + + +
+ {title} +
+ +

+ {formatEventTime()} +

+ + {linkedSessionId + ? ( + + ) + : ( + + )} +
+
+ ); +} diff --git a/apps/desktop/src/components/main/body/calendars/day-more.tsx b/apps/desktop/src/components/main/body/calendars/day-more.tsx new file mode 100644 index 0000000000..676aa93f0e --- /dev/null +++ b/apps/desktop/src/components/main/body/calendars/day-more.tsx @@ -0,0 +1,45 @@ +import { Button } from "@hypr/ui/components/ui/button"; +import { Popover, PopoverContent, PopoverTrigger } from "@hypr/ui/components/ui/popover"; +import { format } from "@hypr/utils"; + +import { useState } from "react"; + +import { TabContentCalendarDayEvents } from "./day-events"; +import { TabContentCalendarDaySessions } from "./day-sessions"; + +export function TabContentCalendarDayMore({ + day, + eventIds, + sessionIds, + hiddenCount, +}: { + day: string; + eventIds: string[]; + sessionIds: string[]; + hiddenCount: number; +}) { + const [open, setOpen] = useState(false); + + return ( + + + + + +
+ {format(new Date(day), "MMMM d, yyyy")} +
+ +
+ {eventIds.map((eventId) => )} + {sessionIds.map((sessionId) => )} +
+
+
+ ); +} diff --git a/apps/desktop/src/components/main/body/calendars/day-sessions.tsx b/apps/desktop/src/components/main/body/calendars/day-sessions.tsx new file mode 100644 index 0000000000..b320efa69d --- /dev/null +++ b/apps/desktop/src/components/main/body/calendars/day-sessions.tsx @@ -0,0 +1,25 @@ +import { Button } from "@hypr/ui/components/ui/button"; + +import { StickyNote } from "lucide-react"; + +import * as persisted from "../../../../store/tinybase/persisted"; +import { useTabs } from "../../../../store/zustand/tabs"; + +export function TabContentCalendarDaySessions({ sessionId }: { sessionId: string }) { + const session = persisted.UI.useRow("sessions", sessionId, persisted.STORE_ID); + const { openNew } = useTabs(); + + const eventId = session?.event_id ?? ""; + const event = persisted.UI.useRow("events", eventId, persisted.STORE_ID); + + const handleClick = () => { + openNew({ type: "sessions", id: sessionId, state: { editor: "raw" } }); + }; + + return ( + + ); +} diff --git a/apps/desktop/src/components/main/body/calendars/event-popover-content.tsx b/apps/desktop/src/components/main/body/calendars/event-popover-content.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/desktop/src/components/main/body/calendars/index.tsx b/apps/desktop/src/components/main/body/calendars/index.tsx new file mode 100644 index 0000000000..26dc647ee6 --- /dev/null +++ b/apps/desktop/src/components/main/body/calendars/index.tsx @@ -0,0 +1,199 @@ +import { Button } from "@hypr/ui/components/ui/button"; +import { ButtonGroup } from "@hypr/ui/components/ui/button-group"; +import { + addDays, + addMonths, + cn, + eachDayOfInterval, + format, + getDay, + isSameMonth, + startOfMonth, + subDays, +} from "@hypr/utils"; + +import { Calendar, CalendarDays, ChevronLeft, ChevronRight } from "lucide-react"; +import { useState } from "react"; + +import * as persisted from "../../../../store/tinybase/persisted"; +import { type Tab, useTabs } from "../../../../store/zustand/tabs"; +import { StandardTabWrapper } from "../index"; +import { type TabItem, TabItemBase } from "../shared"; +import { CalendarCheckboxRow } from "./calendar-checkbox-row"; +import { TabContentCalendarDay } from "./calendar-day"; + +export const TabItemCalendar: TabItem> = ( + { + tab, + tabIndex, + handleCloseThis, + handleSelectThis, + handleCloseOthers, + handleCloseAll, + }, +) => { + return ( + } + title={"Calendar"} + active={tab.active} + tabIndex={tabIndex} + handleCloseThis={() => handleCloseThis(tab)} + handleSelectThis={() => handleSelectThis(tab)} + handleCloseOthers={handleCloseOthers} + handleCloseAll={handleCloseAll} + /> + ); +}; + +export function TabContentCalendar({ tab }: { tab: Tab }) { + if (tab.type !== "calendars") { + return null; + } + return ; +} + +function TabContentCalendarInner({ tab }: { tab: Extract }) { + const [sidebarOpen, setSidebarOpen] = useState(false); + + const { openCurrent } = useTabs(); + + const calendarIds = persisted.UI.useRowIds("calendars", persisted.STORE_ID); + + const [selectedCalendars, setSelectedCalendars] = useState>(() => new Set(calendarIds)); + + const monthStart = startOfMonth(tab.month); + const startDayOfWeek = getDay(monthStart); + const weekDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + const monthLabel = format(tab.month, "MMMM yyyy"); + + const calendarStart = subDays(monthStart, startDayOfWeek); + const totalCells = 42; + const calendarEnd = addDays(calendarStart, totalCells - 1); + const allDays = eachDayOfInterval({ start: calendarStart, end: calendarEnd }).map((day) => format(day, "yyyy-MM-dd")); + + const handlePreviousMonth = () => { + openCurrent({ ...tab, month: addMonths(tab.month, -1) }); + }; + + const handleNextMonth = () => { + openCurrent({ ...tab, month: addMonths(tab.month, 1) }); + }; + + const handleToday = () => { + openCurrent({ ...tab, month: new Date() }); + }; + + return ( + +
+ {sidebarOpen && ( + + )} + +
+
+
+ {!sidebarOpen && ( + + )} +
{monthLabel}
+ + + + + + + +
+ +
+ {weekDays.map((day, index) => ( +
+ {day} +
+ ))} +
+
+ +
+ {Array.from({ length: 6 }).map((_, weekIndex) => ( +
+ {allDays.slice(weekIndex * 7, (weekIndex + 1) * 7).map((day, dayIndex) => ( + + ))} +
+ ))} +
+
+
+
+ ); +} diff --git a/apps/desktop/src/components/main/body/contacts/details.tsx b/apps/desktop/src/components/main/body/contacts/details.tsx index 1b87983dde..681cf5cebc 100644 --- a/apps/desktop/src/components/main/body/contacts/details.tsx +++ b/apps/desktop/src/components/main/body/contacts/details.tsx @@ -1,9 +1,10 @@ -import { Building2, CircleMinus, FileText, Pencil, SearchIcon, TrashIcon } from "lucide-react"; +import { Building2, CircleMinus, FileText, SearchIcon } from "lucide-react"; import React, { useState } from "react"; import { Button } from "@hypr/ui/components/ui/button"; import { Input } from "@hypr/ui/components/ui/input"; import { Popover, PopoverContent, PopoverTrigger } from "@hypr/ui/components/ui/popover"; +import { Textarea } from "@hypr/ui/components/ui/textarea"; import * as persisted from "../../../../store/tinybase/persisted"; import { getInitials } from "./shared"; @@ -16,7 +17,6 @@ export function DetailsColumn({ handleDeletePerson: (id: string) => void; handleSessionClick: (id: string) => void; }) { - const [editingPerson, setEditingPerson] = useState(null); const selectedPersonData = persisted.UI.useRow("humans", selectedHumanId ?? "", persisted.STORE_ID); const mappingIdsByHuman = persisted.UI.useSliceRowIds( @@ -58,99 +58,122 @@ export function DetailsColumn({
{selectedPersonData && selectedHumanId ? ( - editingPerson === selectedHumanId - ? ( - setEditingPerson(null)} - onCancel={() => setEditingPerson(null)} - /> - ) - : ( - <> -
-
-
- - {getInitials(selectedPersonData.name || selectedPersonData.email)} - + <> +
+
+
+ + {getInitials(selectedPersonData.name || selectedPersonData.email)} + +
+
+
+
+ + {selectedPersonData.is_user && ( + You + )}
-
-
-
-

- {selectedPersonData.name || "Unnamed Contact"} - {selectedPersonData.is_user && ( - You - )} -

- {selectedPersonData.job_title && ( -

{selectedPersonData.job_title}

- )} - {selectedPersonData.email && ( -

{selectedPersonData.email}

+
+
+
+
+ +
+
+ + +
+
Company
+
+ +
+
+ + + + +
+ + {personSessions.length > 0 && ( +
+

Summary

+
+

+ AI-generated summary of all interactions and notes with this contact will appear here. This will + synthesize key discussion points, action items, and relationship context across all meetings and + notes. +

+
+
+ )} + +
+

Related Notes

+
+ {personSessions.length > 0 + ? ( + personSessions.map((session: any) => ( +
-
- - {!selectedPersonData.is_user && ( - + {session.created_at && ( +
+ {new Date(session.created_at).toLocaleDateString()} +
)} + + )) + ) + :

No related notes found

} +
+
+ + {!selectedPersonData.is_user && ( +
+
+
+

Danger Zone

+
+
+
+
+

Delete this contact

+

This action cannot be undone

+
+ )} -
-

Related Notes

-
-
- {personSessions.length > 0 - ? ( - personSessions.map((session: any) => ( - - )) - ) - :

No related notes found

} -
-
-
- - ) +
+
+ ) : (
@@ -161,91 +184,7 @@ export function DetailsColumn({ ); } -function OrganizationInfo({ organizationId }: { organizationId: string }) { - const organization = persisted.UI.useRow("organizations", organizationId, persisted.STORE_ID); - - if (!organization) { - return null; - } - - return ( -

- {organization.name} -

- ); -} - -function EditPersonForm({ - personId, - onSave, - onCancel, -}: { - personId: string; - onSave: () => void; - onCancel: () => void; -}) { - const personData = persisted.UI.useRow("humans", personId, persisted.STORE_ID); - - if (!personData) { - return null; - } - - return ( -
-
-
-

Edit Contact

-
- - -
-
-
- -
-
-
- - {getInitials(personData.name as string || "?")} - -
-
- -
- - - -
-
Company
-
- -
-
- - - -
-
-
- ); -} - -function EditPersonNameField({ personId }: { personId: string }) { +function EditablePersonNameField({ personId }: { personId: string }) { const value = persisted.UI.useCell("humans", personId, "name", persisted.STORE_ID); const handleChange = persisted.UI.useSetCellCallback( @@ -258,21 +197,16 @@ function EditPersonNameField({ personId }: { personId: string }) { ); return ( -
-
Name
-
- -
-
+ ); } -function EditPersonJobTitleField({ personId }: { personId: string }) { +function EditablePersonJobTitleField({ personId }: { personId: string }) { const value = persisted.UI.useCell("humans", personId, "job_title", persisted.STORE_ID); const handleChange = persisted.UI.useSetCellCallback( @@ -292,14 +226,14 @@ function EditPersonJobTitleField({ personId }: { personId: string }) { value={(value as string) || ""} onChange={handleChange} placeholder="Software Engineer" - className="border-none p-0 h-7 text-base focus-visible:ring-0 focus-visible:ring-offset-0" + className="border-none shadow-none p-0 h-7 text-base focus-visible:ring-0 focus-visible:ring-offset-0" />
); } -function EditPersonEmailField({ personId }: { personId: string }) { +function EditablePersonEmailField({ personId }: { personId: string }) { const value = persisted.UI.useCell("humans", personId, "email", persisted.STORE_ID); const handleChange = persisted.UI.useSetCellCallback( @@ -320,14 +254,14 @@ function EditPersonEmailField({ personId }: { personId: string }) { value={(value as string) || ""} onChange={handleChange} placeholder="john@example.com" - className="border-none p-0 h-7 text-base focus-visible:ring-0 focus-visible:ring-offset-0" + className="border-none shadow-none p-0 h-7 text-base focus-visible:ring-0 focus-visible:ring-offset-0" />
); } -function EditPersonLinkedInField({ personId }: { personId: string }) { +function EditablePersonLinkedInField({ personId }: { personId: string }) { const value = persisted.UI.useCell("humans", personId, "linkedin_username", persisted.STORE_ID); const handleChange = persisted.UI.useSetCellCallback( @@ -347,7 +281,35 @@ function EditPersonLinkedInField({ personId }: { personId: string }) { value={(value as string) || ""} onChange={handleChange} placeholder="https://www.linkedin.com/in/johntopia/" - className="border-none p-0 h-7 text-base focus-visible:ring-0 focus-visible:ring-offset-0" + className="border-none shadow-none p-0 h-7 text-base focus-visible:ring-0 focus-visible:ring-offset-0" + /> +
+
+ ); +} + +function EditablePersonMemoField({ personId }: { personId: string }) { + const value = persisted.UI.useCell("humans", personId, "memo", persisted.STORE_ID); + + const handleChange = persisted.UI.useSetCellCallback( + "humans", + personId, + "memo", + (e: React.ChangeEvent) => e.target.value, + [], + persisted.STORE_ID, + ); + + return ( +
+
Notes
+
+