From cbbc50d33d0dda852b280f531d37d6812b8ff700 Mon Sep 17 00:00:00 2001 From: John Jeong Date: Thu, 23 Oct 2025 14:05:14 +0900 Subject: [PATCH 01/13] add garageband --- plugins/notification/src/handler.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/notification/src/handler.rs b/plugins/notification/src/handler.rs index 5291fb894a..5efb3426c0 100644 --- a/plugins/notification/src/handler.rs +++ b/plugins/notification/src/handler.rs @@ -108,6 +108,7 @@ impl NotificationHandler { "dev.zed.Zed", "com.microsoft.VSCode", "com.todesktop.230313mzl4w4u92", + "com.apple.garageband10", ] .contains(&app.id.as_str()) }) { From ee154cb2e1acb19f61fe9772cac023671a497708 Mon Sep 17 00:00:00 2001 From: John Jeong Date: Thu, 23 Oct 2025 14:05:28 +0900 Subject: [PATCH 02/13] make contact header better --- .../src/components/main/body/contacts/organizations.tsx | 4 ++-- apps/desktop/src/components/main/body/contacts/shared.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/components/main/body/contacts/organizations.tsx b/apps/desktop/src/components/main/body/contacts/organizations.tsx index 1ee6083c9a..44c3a317aa 100644 --- a/apps/desktop/src/components/main/body/contacts/organizations.tsx +++ b/apps/desktop/src/components/main/body/contacts/organizations.tsx @@ -48,11 +48,11 @@ export function OrganizationsColumn({ {showNewOrg && ( diff --git a/apps/desktop/src/components/main/body/contacts/shared.tsx b/apps/desktop/src/components/main/body/contacts/shared.tsx index fe258b3aa3..cb01bf40ff 100644 --- a/apps/desktop/src/components/main/body/contacts/shared.tsx +++ b/apps/desktop/src/components/main/body/contacts/shared.tsx @@ -86,7 +86,7 @@ export function ColumnHeader({ return (
-

{title}

+

{title}

{onSearchChange && ( ); From 27e3a2208bd140fa6122ef58a3b41f90f2ef9c72 Mon Sep 17 00:00:00 2001 From: John Jeong Date: Thu, 23 Oct 2025 23:02:46 +0900 Subject: [PATCH 04/13] metadata chip polish wip --- .../sessions/outer-header/metadata/date.tsx | 1 + .../sessions/outer-header/metadata/index.tsx | 20 +++++++++---------- .../sessions/outer-header/metadata/link.tsx | 9 +++++---- .../sessions/outer-header/metadata/others.tsx | 20 +++++-------------- .../sessions/outer-header/metadata/shared.ts | 2 +- 5 files changed, 21 insertions(+), 31 deletions(-) diff --git a/apps/desktop/src/components/main/body/sessions/outer-header/metadata/date.tsx b/apps/desktop/src/components/main/body/sessions/outer-header/metadata/date.tsx index 21b28b033a..a18ab96dd4 100644 --- a/apps/desktop/src/components/main/body/sessions/outer-header/metadata/date.tsx +++ b/apps/desktop/src/components/main/body/sessions/outer-header/metadata/date.tsx @@ -1,4 +1,5 @@ import { formatDate, isSameDay } from "@hypr/utils"; + import { useMeetingMetadata } from "./shared"; export function MeetingDate({ sessionId }: { sessionId: string }) { diff --git a/apps/desktop/src/components/main/body/sessions/outer-header/metadata/index.tsx b/apps/desktop/src/components/main/body/sessions/outer-header/metadata/index.tsx index 670c1c43bc..9e26df6b3a 100644 --- a/apps/desktop/src/components/main/body/sessions/outer-header/metadata/index.tsx +++ b/apps/desktop/src/components/main/body/sessions/outer-header/metadata/index.tsx @@ -1,9 +1,11 @@ -import { CalendarIcon } from "lucide-react"; -import { useState } from "react"; - import { Button } from "@hypr/ui/components/ui/button"; import { Popover, PopoverContent, PopoverTrigger } from "@hypr/ui/components/ui/popover"; +import { Separator } from "@hypr/ui/components/ui/separator"; import { cn } from "@hypr/utils"; + +import { CalendarIcon } from "lucide-react"; +import { useState } from "react"; + import { MeetingDate } from "./date"; import { MeetingLink } from "./link"; import { MeetingParticipants } from "./participants"; @@ -46,20 +48,16 @@ export function MeetingMetadata({ sessionId }: { sessionId: string }) { "shadow-lg w-[340px] relative p-0 max-h-[80vh]", ])} > -
+
{meta.title} - + - + - +
); } - -function Divider() { - return
; -} diff --git a/apps/desktop/src/components/main/body/sessions/outer-header/metadata/link.tsx b/apps/desktop/src/components/main/body/sessions/outer-header/metadata/link.tsx index fecf78faac..2840b728f0 100644 --- a/apps/desktop/src/components/main/body/sessions/outer-header/metadata/link.tsx +++ b/apps/desktop/src/components/main/body/sessions/outer-header/metadata/link.tsx @@ -1,7 +1,3 @@ -import { openUrl } from "@tauri-apps/plugin-opener"; -import { ChevronDownIcon, CopyIcon, ExternalLinkIcon, VideoIcon } from "lucide-react"; -import { useCallback } from "react"; - import { Button } from "@hypr/ui/components/ui/button"; import { DropdownMenu, @@ -9,6 +5,11 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@hypr/ui/components/ui/dropdown-menu"; + +import { openUrl } from "@tauri-apps/plugin-opener"; +import { ChevronDownIcon, CopyIcon, ExternalLinkIcon, VideoIcon } from "lucide-react"; +import { useCallback } from "react"; + import { useMeetingMetadata } from "./shared"; export function MeetingLink({ sessionId }: { sessionId: string }) { diff --git a/apps/desktop/src/components/main/body/sessions/outer-header/metadata/others.tsx b/apps/desktop/src/components/main/body/sessions/outer-header/metadata/others.tsx index e2d6fbcee2..23237fe75d 100644 --- a/apps/desktop/src/components/main/body/sessions/outer-header/metadata/others.tsx +++ b/apps/desktop/src/components/main/body/sessions/outer-header/metadata/others.tsx @@ -1,13 +1,14 @@ +import { Avatar, AvatarFallback } from "@hypr/ui/components/ui/avatar"; +import { HoverCard, HoverCardContent, HoverCardTrigger } from "@hypr/ui/components/ui/hover-card"; +import { cn } from "@hypr/utils"; + import { Icon } from "@iconify-icon/react"; import { CheckIcon, MailIcon, MinusCircleIcon, SearchIcon, XIcon } from "lucide-react"; import { useMemo, useState } from "react"; -import { Avatar, AvatarFallback } from "@hypr/ui/components/ui/avatar"; -import { HoverCard, HoverCardContent, HoverCardTrigger } from "@hypr/ui/components/ui/hover-card"; -import { cn } from "@hypr/utils"; import { getInitials } from "../../../contacts/shared"; -export interface MeetingParticipant { +interface MeetingParticipant { id: string; full_name?: string | null; email?: string | null; @@ -19,17 +20,6 @@ export interface MeetingParticipant { } | null; } -export interface MeetingMetadata { - id: string; - title: string; - started_at: string; - ended_at: string; - location?: string | null; - meeting_link?: string | null; - description?: string | null; - participants: MeetingParticipant[]; -} - function ParticipantChip({ participant, attended = true, diff --git a/apps/desktop/src/components/main/body/sessions/outer-header/metadata/shared.ts b/apps/desktop/src/components/main/body/sessions/outer-header/metadata/shared.ts index 015049a111..68483f6b19 100644 --- a/apps/desktop/src/components/main/body/sessions/outer-header/metadata/shared.ts +++ b/apps/desktop/src/components/main/body/sessions/outer-header/metadata/shared.ts @@ -1,6 +1,6 @@ import * as persisted from "../../../../../../store/tinybase/persisted"; -type MeetingMetadata = { +export type MeetingMetadata = { tood: string; meeting_link?: string | null; title: string; From a561ef9a6b53750c0d825728067754e204e2a602 Mon Sep 17 00:00:00 2001 From: John Jeong Date: Fri, 24 Oct 2025 00:44:51 +0900 Subject: [PATCH 05/13] cal --- .../src/components/main/body/calendars.tsx | 528 ------------------ .../body/calendars/calendar-checkbox-row.tsx | 20 + .../main/body/calendars/calendar-day.tsx | 144 +++++ .../main/body/calendars/day-events.tsx | 89 +++ .../main/body/calendars/day-more.tsx | 45 ++ .../main/body/calendars/day-sessions.tsx | 24 + .../body/calendars/event-popover-content.tsx | 0 .../components/main/body/calendars/index.tsx | 7 + .../body/calendars/tab-content-calendar.tsx | 174 ++++++ .../main/body/calendars/tab-item-calendar.tsx | 28 + 10 files changed, 531 insertions(+), 528 deletions(-) delete mode 100644 apps/desktop/src/components/main/body/calendars.tsx create mode 100644 apps/desktop/src/components/main/body/calendars/calendar-checkbox-row.tsx create mode 100644 apps/desktop/src/components/main/body/calendars/calendar-day.tsx create mode 100644 apps/desktop/src/components/main/body/calendars/day-events.tsx create mode 100644 apps/desktop/src/components/main/body/calendars/day-more.tsx create mode 100644 apps/desktop/src/components/main/body/calendars/day-sessions.tsx create mode 100644 apps/desktop/src/components/main/body/calendars/event-popover-content.tsx create mode 100644 apps/desktop/src/components/main/body/calendars/index.tsx create mode 100644 apps/desktop/src/components/main/body/calendars/tab-content-calendar.tsx create mode 100644 apps/desktop/src/components/main/body/calendars/tab-item-calendar.tsx 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..2ae523181d --- /dev/null +++ b/apps/desktop/src/components/main/body/calendars/calendar-checkbox-row.tsx @@ -0,0 +1,20 @@ +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: () => void }, +) { + const calendar = persisted.UI.useRow("calendars", id, persisted.STORE_ID); + return ( +
+ + +
+ ); +} 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..42efad2c88 --- /dev/null +++ b/apps/desktop/src/components/main/body/calendars/calendar-day.tsx @@ -0,0 +1,144 @@ +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; + 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 && ( + + )} +
+
+ ); +} 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..4ed58703fc --- /dev/null +++ b/apps/desktop/src/components/main/body/calendars/day-events.tsx @@ -0,0 +1,89 @@ +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 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.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 || "Untitled Event"} +
+ +

+ {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..9896cd127a --- /dev/null +++ b/apps/desktop/src/components/main/body/calendars/day-sessions.tsx @@ -0,0 +1,24 @@ +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 event = persisted.UI.useRow("events", session.event_id || "dummy", 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..418ae00383 --- /dev/null +++ b/apps/desktop/src/components/main/body/calendars/index.tsx @@ -0,0 +1,7 @@ +export { CalendarCheckboxRow } from "./calendar-checkbox-row"; +export { TabContentCalendarDay } from "./calendar-day"; +export { TabContentCalendarDayEvents } from "./day-events"; +export { TabContentCalendarDayMore } from "./day-more"; +export { TabContentCalendarDaySessions } from "./day-sessions"; +export { TabContentCalendar } from "./tab-content-calendar"; +export { TabItemCalendar } from "./tab-item-calendar"; diff --git a/apps/desktop/src/components/main/body/calendars/tab-content-calendar.tsx b/apps/desktop/src/components/main/body/calendars/tab-content-calendar.tsx new file mode 100644 index 0000000000..176416c58a --- /dev/null +++ b/apps/desktop/src/components/main/body/calendars/tab-content-calendar.tsx @@ -0,0 +1,174 @@ +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 { 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 { CalendarCheckboxRow } from "./calendar-checkbox-row"; +import { TabContentCalendarDay } from "./calendar-day"; + +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, 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/calendars/tab-item-calendar.tsx b/apps/desktop/src/components/main/body/calendars/tab-item-calendar.tsx new file mode 100644 index 0000000000..c6598763b4 --- /dev/null +++ b/apps/desktop/src/components/main/body/calendars/tab-item-calendar.tsx @@ -0,0 +1,28 @@ +import { Calendar } from "lucide-react"; + +import { type Tab } from "../../../../store/zustand/tabs"; +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} + /> + ); +}; From ee0fc6a0cd13cea977b6569c3351e1d9a31be94a Mon Sep 17 00:00:00 2001 From: John Jeong Date: Fri, 24 Oct 2025 00:45:02 +0900 Subject: [PATCH 06/13] metadata chip ui --- .../outer-header/metadata/description.tsx | 15 + .../sessions/outer-header/metadata/index.tsx | 42 +- .../sessions/outer-header/metadata/link.tsx | 2 +- .../outer-header/metadata/location.tsx | 20 + .../outer-header/metadata/participants.tsx | 401 ++++++++++++++++-- .../sessions/outer-header/metadata/shared.ts | 6 + 6 files changed, 451 insertions(+), 35 deletions(-) create mode 100644 apps/desktop/src/components/main/body/sessions/outer-header/metadata/description.tsx create mode 100644 apps/desktop/src/components/main/body/sessions/outer-header/metadata/location.tsx diff --git a/apps/desktop/src/components/main/body/sessions/outer-header/metadata/description.tsx b/apps/desktop/src/components/main/body/sessions/outer-header/metadata/description.tsx new file mode 100644 index 0000000000..2cba8726f6 --- /dev/null +++ b/apps/desktop/src/components/main/body/sessions/outer-header/metadata/description.tsx @@ -0,0 +1,15 @@ +import { useMeetingMetadata } from "./shared"; + +export function MeetingDescription({ sessionId }: { sessionId: string }) { + const meta = useMeetingMetadata(sessionId)!; + + if (!meta.description) { + return null; + } + + return ( +
+ {meta.description} +
+ ); +} diff --git a/apps/desktop/src/components/main/body/sessions/outer-header/metadata/index.tsx b/apps/desktop/src/components/main/body/sessions/outer-header/metadata/index.tsx index 9e26df6b3a..6557db886a 100644 --- a/apps/desktop/src/components/main/body/sessions/outer-header/metadata/index.tsx +++ b/apps/desktop/src/components/main/body/sessions/outer-header/metadata/index.tsx @@ -1,13 +1,14 @@ import { Button } from "@hypr/ui/components/ui/button"; import { Popover, PopoverContent, PopoverTrigger } from "@hypr/ui/components/ui/popover"; -import { Separator } from "@hypr/ui/components/ui/separator"; import { cn } from "@hypr/utils"; import { CalendarIcon } from "lucide-react"; import { useState } from "react"; import { MeetingDate } from "./date"; +import { MeetingDescription } from "./description"; import { MeetingLink } from "./link"; +import { MeetingLocation } from "./location"; import { MeetingParticipants } from "./participants"; import { useMeetingMetadata } from "./shared"; @@ -25,6 +26,10 @@ export function MeetingMetadata({ sessionId }: { sessionId: string }) { ); } + const hasLocation = !!meta.location; + const hasMeetingLink = !!meta.meeting_link; + const hasDescription = !!meta.description; + return ( @@ -48,14 +53,37 @@ export function MeetingMetadata({ sessionId }: { sessionId: string }) { "shadow-lg w-[340px] relative p-0 max-h-[80vh]", ])} > -
- {meta.title} - - - +
+
{meta.title}
+ +
+ + {hasLocation && ( + <> + +
+ + )} + + {hasMeetingLink && ( + <> + +
+ + )} + - + +
+ + + {hasDescription && ( + <> +
+ + + )}
diff --git a/apps/desktop/src/components/main/body/sessions/outer-header/metadata/link.tsx b/apps/desktop/src/components/main/body/sessions/outer-header/metadata/link.tsx index 2840b728f0..071afdd5b7 100644 --- a/apps/desktop/src/components/main/body/sessions/outer-header/metadata/link.tsx +++ b/apps/desktop/src/components/main/body/sessions/outer-header/metadata/link.tsx @@ -22,7 +22,7 @@ export function MeetingLink({ sessionId }: { sessionId: string }) { }, [meta.meeting_link]); return ( -
+
+
+
+ + +
+
+ + + {participant.full_name ? getInitials(participant.full_name) : "?"} + + +
+
{displayName}
+ {participant.job_title &&
{participant.job_title}
} + {participant.organization?.name && ( +
{participant.organization.name}
+ )} +
+
+ {(participant.email || participant.linkedin_username) && ( +
+ {participant.email && ( + + + {participant.email} + + )} + {participant.linkedin_username && ( + + + linkedin.com/in/{participant.linkedin_username} + + )} +
+ )} +
+
+ ); } -function ParticipantRow({ mappingId }: { mappingId: string }) { - const humanId = UI.useCell("mapping_session_participant", mappingId, "human_id", STORE_ID); +export function MeetingParticipants({ sessionId }: { sessionId: string }) { + const [participantSearchQuery, setParticipantSearchQuery] = useState(""); + const [isFocused, setIsFocused] = useState(false); + const [selectedIndex, setSelectedIndex] = useState(-1); + const { openNew } = useTabs(); + const { user_id } = internal.UI.useValues(internal.STORE_ID); + + const store = persisted.UI.useStore(persisted.STORE_ID); + const indexes = persisted.UI.useIndexes(persisted.STORE_ID); + + const participantMappingIds = persisted.UI.useSliceRowIds( + persisted.INDEXES.sessionParticipantsBySession, + sessionId, + persisted.STORE_ID, + ); + + // Get participants with full data + const participants: MeetingParticipant[] = useMemo(() => { + const result: MeetingParticipant[] = []; + if (store && participantMappingIds) { + participantMappingIds.forEach((mappingId) => { + const humanId = store.getCell("mapping_session_participant", mappingId, "human_id") as + | string + | undefined; + if (humanId) { + const humanRow = store.getRow("humans", humanId); + if (humanRow) { + const orgId = humanRow.org_id as string | undefined; + const org = orgId ? store.getRow("organizations", orgId) : null; + + result.push({ + id: humanId, + full_name: humanRow.name as string | null, + email: humanRow.email as string | null, + job_title: humanRow.job_title as string | null, + linkedin_username: humanRow.linkedin_username as string | null, + organization: org && orgId + ? { id: orgId, name: org.name as string } + : null, + }); + } + } + }); + } + return result; + }, [store, participantMappingIds]); + + // TODO - sort participants based on attendance + const sortedParticipants = participants; + + const participantSearch = useQuery({ + enabled: !!store && !!indexes && !!participantSearchQuery.trim(), + deps: [store, indexes, participantSearchQuery, sessionId] as const, + queryFn: async (store, indexes, query, sessionId) => { + const results: MeetingParticipant[] = []; + const existingParticipantIds = new Set(); - if (!humanId) { - return null; - } + const participantMappings = indexes!.getSliceRowIds( + persisted.INDEXES.sessionParticipantsBySession, + sessionId, + ); + participantMappings?.forEach((mappingId: string) => { + const humanId = store!.getCell( + "mapping_session_participant", + mappingId, + "human_id", + ) as string | undefined; + if (humanId) { + existingParticipantIds.add(humanId); + } + }); - const human = UI.useRow("humans", humanId, STORE_ID); + const normalizedQuery = query.toLowerCase(); - if (!human) { - return null; - } + store!.forEachRow("humans", (rowId, forEachCell) => { + if (existingParticipantIds.has(rowId)) { + return; + } + + let name: string | undefined; + let email: string | undefined; + let job_title: string | undefined; + let linkedin_username: string | undefined; + let org_id: string | undefined; + + forEachCell((cellId, cell) => { + if (cellId === "name") { + name = cell as string; + } else if (cellId === "email") { + email = cell as string; + } else if (cellId === "job_title") { + job_title = cell as string; + } else if (cellId === "linkedin_username") { + linkedin_username = cell as string; + } else if (cellId === "org_id") { + org_id = cell as string; + } + }); + + if ( + name && !name.toLowerCase().includes(normalizedQuery) + && (!email || !email.toLowerCase().includes(normalizedQuery)) + ) { + return; + } + + const org = org_id ? store!.getRow("organizations", org_id) : null; + + results.push({ + id: rowId, + full_name: name || null, + email: email || null, + job_title: job_title || null, + linkedin_username: linkedin_username || null, + organization: org + ? { + id: org_id!, + name: org.name as string, + } + : null, + }); + }); + + return results.slice(0, 10); + }, + }); + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Backspace" && !participantSearchQuery && sortedParticipants.length > 0) { + e.preventDefault(); + handleParticipantRemove(sortedParticipants[sortedParticipants.length - 1].id); + return; + } + + if (!participantSearchQuery.trim() || (participantSearch.data?.length ?? 0) === 0) { + return; + } + + if (e.key === "ArrowDown") { + e.preventDefault(); + setSelectedIndex((prev) => (prev < (participantSearch.data?.length ?? 0) - 1 ? prev + 1 : prev)); + } else if (e.key === "ArrowUp") { + e.preventDefault(); + setSelectedIndex((prev) => (prev > 0 ? prev - 1 : 0)); + } else if (e.key === "Enter") { + e.preventDefault(); + if (selectedIndex >= 0 && participantSearch.data && selectedIndex < participantSearch.data.length) { + handleSelectParticipant(participantSearch.data[selectedIndex].id); + } + } else if (e.key === "Escape") { + e.preventDefault(); + setIsFocused(false); + setParticipantSearchQuery(""); + } + }; + + const handleSelectParticipant = (participantId: string) => { + handleParticipantAdd(participantId); + setParticipantSearchQuery(""); + setSelectedIndex(-1); + setIsFocused(true); + }; + + const handleParticipantClick = useCallback((participant: MeetingParticipant) => { + openNew({ + type: "contacts", + state: { + selectedPerson: participant.id, + selectedOrganization: null, + }, + }); + }, [openNew]); + + const handleParticipantAdd = useCallback((participantId: string) => { + if (!store) { + return; + } + + const mappingId = crypto.randomUUID(); + + store.setRow("mapping_session_participant", mappingId, { + user_id, + session_id: sessionId, + human_id: participantId, + created_at: new Date().toISOString(), + }); + + setParticipantSearchQuery(""); + }, [store, sessionId, user_id]); + + const handleParticipantRemove = useCallback((participantId: string) => { + if (!store || !participantMappingIds) { + return; + } + + const mappingId = participantMappingIds.find((id) => { + const humanId = store.getCell("mapping_session_participant", id, "human_id"); + return humanId === participantId; + }); + + if (mappingId) { + store.delRow("mapping_session_participant", mappingId); + } + }, [store, participantMappingIds]); return ( -
-
{human.name}
- {human.email &&
{human.email}
} - {human.job_title &&
{human.job_title}
} - {human.is_user &&
You
} +
+
Participants
+
+
document.getElementById("participant-search-input")?.focus()} + > + {sortedParticipants.map((participant) => ( + handleParticipantClick(participant)} + onRemove={() => handleParticipantRemove(participant.id)} + /> + ))} + {sortedParticipants.length === 0 && } + { + setParticipantSearchQuery(e.target.value); + setSelectedIndex(-1); + }} + onKeyDown={handleKeyDown} + onFocus={() => setIsFocused(true)} + onBlur={() => { + setTimeout(() => setIsFocused(false), 200); + }} + placeholder={sortedParticipants.length === 0 ? "Add participant" : ""} + className="flex-1 min-w-[120px] bg-transparent text-sm focus:outline-none placeholder:text-neutral-500" + /> +
+ + {isFocused && participantSearchQuery.trim() && ( +
+ {participantSearch.data && participantSearch.data.length > 0 + ? ( + participantSearch.data.map((participant, index) => ( + + )) + ) + : ( +
+ No matching participants found +
+ )} +
+ )} +
); } diff --git a/apps/desktop/src/components/main/body/sessions/outer-header/metadata/shared.ts b/apps/desktop/src/components/main/body/sessions/outer-header/metadata/shared.ts index 68483f6b19..f36e086599 100644 --- a/apps/desktop/src/components/main/body/sessions/outer-header/metadata/shared.ts +++ b/apps/desktop/src/components/main/body/sessions/outer-header/metadata/shared.ts @@ -6,6 +6,8 @@ export type MeetingMetadata = { title: string; started_at: string; ended_at: string; + location?: string | null; + description?: string | null; }; export function useMeetingMetadata(sessionId: string): MeetingMetadata | null { @@ -16,6 +18,8 @@ export function useMeetingMetadata(sessionId: string): MeetingMetadata | null { const title = persisted.UI.useCell("events", eventId ?? "", "title", persisted.STORE_ID); const startedAt = persisted.UI.useCell("events", eventId ?? "", "started_at", persisted.STORE_ID); const endedAt = persisted.UI.useCell("events", eventId ?? "", "ended_at", persisted.STORE_ID); + const location = persisted.UI.useCell("events", eventId ?? "", "location", persisted.STORE_ID); + const description = persisted.UI.useCell("events", eventId ?? "", "description", persisted.STORE_ID); if (!eventId || !title || !startedAt || !endedAt) { return null; @@ -27,5 +31,7 @@ export function useMeetingMetadata(sessionId: string): MeetingMetadata | null { title, started_at: startedAt, ended_at: endedAt, + location: location as string | null | undefined, + description: description as string | null | undefined, }; } From 46e458c3ae5e2781ed66a5d6d25205fe2f58a654 Mon Sep 17 00:00:00 2001 From: John Jeong Date: Fri, 24 Oct 2025 01:04:18 +0900 Subject: [PATCH 07/13] minor ui fixes --- .../components/main/body/sessions/outer-header/share.tsx | 4 ++-- .../main/body/sessions/outer-header/shared/folder.tsx | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/desktop/src/components/main/body/sessions/outer-header/share.tsx b/apps/desktop/src/components/main/body/sessions/outer-header/share.tsx index b70fa4ba3c..0629195a0f 100644 --- a/apps/desktop/src/components/main/body/sessions/outer-header/share.tsx +++ b/apps/desktop/src/components/main/body/sessions/outer-header/share.tsx @@ -75,7 +75,7 @@ export function ShareButton(_: { sessionId: string }) { - +
@@ -225,7 +225,7 @@ export function ShareButton(_: { sessionId: string }) { )}
-
- - {!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
+
+