@agent-native/core 0.159.3 → 0.160.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/assets/server/lib/generation.ts +10 -6
- package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +18 -4
- package/corpus/templates/calendar/actions/create-event.ts +4 -2
- package/corpus/templates/calendar/actions/event-action-helpers.ts +56 -9
- package/corpus/templates/calendar/actions/update-event.ts +1 -0
- package/corpus/templates/calendar/agent-native.config.ts +1 -0
- package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +8 -2
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +102 -68
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +150 -49
- package/corpus/templates/calendar/app/components/calendar/MonthView.tsx +57 -23
- package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +4 -1
- package/corpus/templates/calendar/app/components/calendar/TimezoneSwitchDialog.tsx +70 -0
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +127 -88
- package/corpus/templates/calendar/app/components/calendar/WorkingLocationEditor.tsx +59 -25
- package/corpus/templates/calendar/app/hooks/use-event-drag.ts +55 -18
- package/corpus/templates/calendar/app/i18n-data.ts +60 -0
- package/corpus/templates/calendar/app/lib/all-day-layout.ts +16 -8
- package/corpus/templates/calendar/app/lib/calendar-drafts.ts +56 -0
- package/corpus/templates/calendar/app/lib/calendar-timezone.ts +351 -0
- package/corpus/templates/calendar/app/lib/event-layout.ts +14 -8
- package/corpus/templates/calendar/app/lib/out-of-office.ts +29 -61
- package/corpus/templates/calendar/app/lib/working-location.ts +34 -2
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +317 -139
- package/corpus/templates/calendar/changelog/2026-08-13-calendar-stays-pinned-to-the-saved-timezone-and-asks-before-.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-13-working-locations-can-be-added-from-calendar-days-with-a-ful.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-14-choosing-office-or-other-when-adding-a-working-location-creates.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-14-creating-an-other-working-location-now-keeps-the-custom-name.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-14-timed-working-locations-keep-home-office-or-custom-titles.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-14-timed-working-locations-now-keep-a-home-office-or-custom-title.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-14-turning-a-midnight-ending-working-location-back-to-all-day.md +6 -0
- package/corpus/templates/calendar/server/lib/google-calendar.ts +22 -1
- package/dist/agent/engine/builder-engine.d.ts +16 -2
- package/dist/agent/engine/builder-engine.js +178 -115
- package/dist/agent/engine/builtin.js +13 -4
- package/dist/agent/engine/credential-errors.d.ts +23 -0
- package/dist/agent/engine/credential-errors.js +19 -0
- package/dist/agent/engine/error-detail.d.ts +15 -0
- package/dist/agent/engine/error-detail.js +29 -0
- package/dist/agent/engine/registry.d.ts +14 -3
- package/dist/agent/engine/registry.js +106 -61
- package/dist/agent/engine/types.d.ts +16 -0
- package/dist/agent/engine/types.js +9 -0
- package/dist/agent/production-agent.d.ts +7 -0
- package/dist/agent/production-agent.js +58 -20
- package/dist/agent/run-manager.js +30 -0
- package/dist/agent/thread-data-builder.js +4 -0
- package/dist/agent/types.d.ts +9 -0
- package/dist/client/agent-chat-adapter.js +46 -15
- package/dist/client/chat-first/app-pane.js +1 -1
- package/dist/client/chat-first/copy.js +1 -0
- package/dist/client/error-format.js +25 -2
- package/dist/client/sse-event-processor.d.ts +2 -0
- package/dist/client/sse-event-processor.js +12 -1
- package/dist/feature-flags/index.d.ts +0 -2
- package/dist/feature-flags/index.js +3 -2
- package/dist/feature-flags/server.d.ts +2 -0
- package/dist/feature-flags/server.js +2 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/jobs/background-automation-runner.js +9 -15
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +2 -2
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/register-framework-secrets.js +25 -0
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/agent-chat-plugin.js +9 -2
- package/dist/server/auth.js +10 -0
- package/dist/server/credential-provider.d.ts +80 -0
- package/dist/server/credential-provider.js +168 -1
- package/dist/server/google-realtime-session.d.ts +1 -1
- package/dist/server/google-realtime-session.js +6 -6
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +7 -2
- package/dist/server/onboarding-html.js +16 -257
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/realtime-voice.js +12 -6
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/server/transcribe-voice.js +18 -16
- package/dist/shared/auth-copy.d.ts +34 -0
- package/dist/shared/auth-copy.js +293 -0
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +1 -0
- package/dist/transcription/builder-transcription.js +8 -4
- package/docs/content/actions-advanced.mdx +1 -1
- package/docs/content/portal.mdx +21 -8
- package/package.json +3 -1
|
@@ -5,22 +5,16 @@ import {
|
|
|
5
5
|
IconBuilding,
|
|
6
6
|
IconHome,
|
|
7
7
|
IconMapPin,
|
|
8
|
+
IconPlus,
|
|
8
9
|
} from "@tabler/icons-react";
|
|
9
10
|
import {
|
|
10
11
|
startOfWeek,
|
|
11
12
|
endOfWeek,
|
|
12
13
|
eachDayOfInterval,
|
|
13
14
|
eachHourOfInterval,
|
|
14
|
-
isSameDay,
|
|
15
|
-
isToday,
|
|
16
15
|
format,
|
|
17
|
-
parseISO,
|
|
18
|
-
differenceInMinutes,
|
|
19
|
-
startOfDay,
|
|
20
16
|
set,
|
|
21
|
-
addDays,
|
|
22
17
|
addMinutes,
|
|
23
|
-
min,
|
|
24
18
|
} from "date-fns";
|
|
25
19
|
import { useState, useEffect, useRef, useMemo, useCallback, memo } from "react";
|
|
26
20
|
|
|
@@ -42,6 +36,13 @@ import {
|
|
|
42
36
|
layoutAllDayEvents,
|
|
43
37
|
partitionAllDayEvents,
|
|
44
38
|
} from "@/lib/all-day-layout";
|
|
39
|
+
import {
|
|
40
|
+
dateToCalendarDateKey,
|
|
41
|
+
getBrowserTimezone,
|
|
42
|
+
getDateKeyInTimezone,
|
|
43
|
+
getEventDateKey,
|
|
44
|
+
getEventSegmentForCalendarDay,
|
|
45
|
+
} from "@/lib/calendar-timezone";
|
|
45
46
|
import { getEventDisplayColor, allOtherDeclined } from "@/lib/event-colors";
|
|
46
47
|
import {
|
|
47
48
|
computeTimedEventLayout,
|
|
@@ -72,6 +73,7 @@ import { shouldRenderWeekDragSegment } from "./week-drag-segment";
|
|
|
72
73
|
interface WeekViewProps {
|
|
73
74
|
events: CalendarEvent[];
|
|
74
75
|
selectedDate: Date;
|
|
76
|
+
timezone?: string;
|
|
75
77
|
onDateSelect: (date: Date) => void;
|
|
76
78
|
onDeleteEvent: (eventId: string) => void;
|
|
77
79
|
onEventTimeChange?: (eventId: string, newStart: Date, newEnd: Date) => void;
|
|
@@ -81,6 +83,7 @@ interface WeekViewProps {
|
|
|
81
83
|
endTime: string,
|
|
82
84
|
options?: { explicitDuration?: boolean },
|
|
83
85
|
) => void;
|
|
86
|
+
onCreateWorkingLocation?: (date: Date) => void;
|
|
84
87
|
quickEditEventId?: string | null;
|
|
85
88
|
onQuickEditSave?: (
|
|
86
89
|
eventId: string,
|
|
@@ -148,7 +151,7 @@ function minutesToTimeString(totalMinutes: number): string {
|
|
|
148
151
|
/** Convert minutes-from-START_HOUR on a given day into a Date, for ghost label formatting */
|
|
149
152
|
function minutesToDate(day: Date, totalMinutes: number): Date {
|
|
150
153
|
return addMinutes(
|
|
151
|
-
set(
|
|
154
|
+
set(day, { hours: START_HOUR, minutes: 0, seconds: 0 }),
|
|
152
155
|
totalMinutes,
|
|
153
156
|
);
|
|
154
157
|
}
|
|
@@ -173,15 +176,10 @@ function formatEventTime(start: Date, end: Date): string {
|
|
|
173
176
|
return `${startWithAmPm}\u2013${endStr}`;
|
|
174
177
|
}
|
|
175
178
|
|
|
176
|
-
function getSegmentStyle(event: CalendarEvent, day: Date) {
|
|
177
|
-
const
|
|
178
|
-
const
|
|
179
|
-
const
|
|
180
|
-
const dayEnd = addDays(dayBase, 1);
|
|
181
|
-
const segStart = evStart > dayBase ? evStart : dayBase;
|
|
182
|
-
const segEnd = min([evEnd, dayEnd]);
|
|
183
|
-
const topMinutes = Math.max(0, differenceInMinutes(segStart, dayBase));
|
|
184
|
-
const durationMinutes = Math.max(15, differenceInMinutes(segEnd, segStart));
|
|
179
|
+
function getSegmentStyle(event: CalendarEvent, day: Date, timezone: string) {
|
|
180
|
+
const segment = getEventSegmentForCalendarDay(event, day, timezone);
|
|
181
|
+
const topMinutes = segment?.topMinutes ?? 0;
|
|
182
|
+
const durationMinutes = Math.max(15, segment?.durationMinutes ?? 15);
|
|
185
183
|
return {
|
|
186
184
|
top: `${(topMinutes / 60) * HOUR_HEIGHT}px`,
|
|
187
185
|
height: `${(durationMinutes / 60) * HOUR_HEIGHT}px`,
|
|
@@ -192,6 +190,7 @@ interface WeekEventCardProps {
|
|
|
192
190
|
event: CalendarEvent;
|
|
193
191
|
day: Date;
|
|
194
192
|
dayIndex: number;
|
|
193
|
+
timezone: string;
|
|
195
194
|
layout: Map<string, TimedEventLayout>;
|
|
196
195
|
now: Date;
|
|
197
196
|
prefs: ViewPreferences;
|
|
@@ -246,6 +245,7 @@ const WeekEventCard = memo(function WeekEventCard({
|
|
|
246
245
|
event,
|
|
247
246
|
day,
|
|
248
247
|
dayIndex,
|
|
248
|
+
timezone,
|
|
249
249
|
layout,
|
|
250
250
|
now,
|
|
251
251
|
prefs,
|
|
@@ -272,6 +272,9 @@ const WeekEventCard = memo(function WeekEventCard({
|
|
|
272
272
|
onPopoverOpenChange,
|
|
273
273
|
}: WeekEventCardProps) {
|
|
274
274
|
const t = useT();
|
|
275
|
+
const workingLocationLabels = createWorkingLocationDisplayLabels(t);
|
|
276
|
+
const title = getWorkingLocationChipLabel(event, workingLocationLabels);
|
|
277
|
+
const ariaTitle = getWorkingLocationTitle(event, workingLocationLabels);
|
|
275
278
|
const li = layout.get(event.id) ?? {
|
|
276
279
|
left: 0,
|
|
277
280
|
width: 100,
|
|
@@ -284,12 +287,10 @@ const WeekEventCard = memo(function WeekEventCard({
|
|
|
284
287
|
overrideTop !== null && overrideHeight !== null && overrideDayIndex !== null
|
|
285
288
|
? { top: overrideTop, height: overrideHeight, dayIndex: overrideDayIndex }
|
|
286
289
|
: null;
|
|
287
|
-
const
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
const
|
|
291
|
-
const isStart = isSameDay(start, day);
|
|
292
|
-
const isEnd = end <= segDayEnd;
|
|
290
|
+
const segment = getEventSegmentForCalendarDay(event, day, timezone);
|
|
291
|
+
const isStart =
|
|
292
|
+
getEventDateKey(event, timezone) === dateToCalendarDateKey(day);
|
|
293
|
+
const isEnd = segment?.endsOnDay ?? true;
|
|
293
294
|
const isDragPreviewSegment =
|
|
294
295
|
isBeingDragged && overrides?.dayIndex === dayIndex;
|
|
295
296
|
const segmentStartsHere = isStart || isDragPreviewSegment;
|
|
@@ -321,26 +322,19 @@ const WeekEventCard = memo(function WeekEventCard({
|
|
|
321
322
|
top: `${overrides.top}px`,
|
|
322
323
|
height: `${overrides.height}px`,
|
|
323
324
|
}
|
|
324
|
-
: getSegmentStyle(event, day);
|
|
325
|
+
: getSegmentStyle(event, day, timezone);
|
|
325
326
|
const color = getEventDisplayColor(event, prefs);
|
|
326
|
-
const segStart = isStart ? start : dayBase;
|
|
327
|
-
const segEnd = min([end, segDayEnd]);
|
|
328
327
|
const durationMin = overrides
|
|
329
328
|
? (overrides.height / HOUR_HEIGHT) * 60
|
|
330
|
-
:
|
|
329
|
+
: (segment?.durationMinutes ?? 15);
|
|
331
330
|
// Compute display times (use drag overrides if active)
|
|
332
331
|
const displayStart = overrides
|
|
333
|
-
?
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
(overrides.top / HOUR_HEIGHT) * 60,
|
|
340
|
-
)
|
|
341
|
-
: start;
|
|
342
|
-
const displayEnd = overrides ? addMinutes(displayStart, durationMin) : end;
|
|
343
|
-
const isPast = end < now;
|
|
332
|
+
? minutesToDate(day, START_HOUR * 60 + (overrides.top / HOUR_HEIGHT) * 60)
|
|
333
|
+
: minutesToDate(day, segment?.startMinutes ?? 0);
|
|
334
|
+
const displayEnd = overrides
|
|
335
|
+
? addMinutes(displayStart, durationMin)
|
|
336
|
+
: minutesToDate(day, segment?.endMinutes ?? durationMin);
|
|
337
|
+
const isPast = new Date(event.end) < now;
|
|
344
338
|
const isDeclined = event.responseStatus === "declined";
|
|
345
339
|
const allOthersOut = allOtherDeclined(event);
|
|
346
340
|
|
|
@@ -367,10 +361,8 @@ const WeekEventCard = memo(function WeekEventCard({
|
|
|
367
361
|
)}
|
|
368
362
|
aria-label={
|
|
369
363
|
event.ownerName || event.overlayEmail
|
|
370
|
-
? `${
|
|
371
|
-
|
|
372
|
-
}'s calendar`
|
|
373
|
-
: event.title
|
|
364
|
+
? `${ariaTitle}, ${event.ownerName || event.overlayEmail}'s calendar`
|
|
365
|
+
: ariaTitle
|
|
374
366
|
}
|
|
375
367
|
style={{
|
|
376
368
|
...style,
|
|
@@ -429,7 +421,7 @@ const WeekEventCard = memo(function WeekEventCard({
|
|
|
429
421
|
!isPast && !isDeclined && "font-semibold",
|
|
430
422
|
)}
|
|
431
423
|
>
|
|
432
|
-
{
|
|
424
|
+
{title}
|
|
433
425
|
</span>
|
|
434
426
|
</div>
|
|
435
427
|
) : (
|
|
@@ -451,7 +443,7 @@ const WeekEventCard = memo(function WeekEventCard({
|
|
|
451
443
|
/>
|
|
452
444
|
)}
|
|
453
445
|
<EventStatusIcon event={event} className="shrink-0" />
|
|
454
|
-
<span className="truncate">{
|
|
446
|
+
<span className="truncate">{title}</span>
|
|
455
447
|
</div>
|
|
456
448
|
{segmentStartsHere && (
|
|
457
449
|
<div
|
|
@@ -502,6 +494,7 @@ const WeekEventCard = memo(function WeekEventCard({
|
|
|
502
494
|
return (
|
|
503
495
|
<EventDetailPopover
|
|
504
496
|
event={event}
|
|
497
|
+
timezone={timezone}
|
|
505
498
|
onDelete={onDeleteEvent}
|
|
506
499
|
isDraft={isDraft}
|
|
507
500
|
defaultOpen={defaultOpen}
|
|
@@ -548,10 +541,12 @@ const WeekCreateGhost = memo(function WeekCreateGhost({
|
|
|
548
541
|
export const WeekView = memo(function WeekView({
|
|
549
542
|
events,
|
|
550
543
|
selectedDate,
|
|
544
|
+
timezone = getBrowserTimezone(),
|
|
551
545
|
onDateSelect,
|
|
552
546
|
onDeleteEvent,
|
|
553
547
|
onEventTimeChange,
|
|
554
548
|
onClickTimeSlot,
|
|
549
|
+
onCreateWorkingLocation,
|
|
555
550
|
quickEditEventId,
|
|
556
551
|
onQuickEditSave,
|
|
557
552
|
onQuickEditCancel,
|
|
@@ -664,8 +659,8 @@ export const WeekView = memo(function WeekView({
|
|
|
664
659
|
[allDayEvents],
|
|
665
660
|
);
|
|
666
661
|
const workingLocationLayout = useMemo(
|
|
667
|
-
() => layoutAllDayEvents(workingLocations, days),
|
|
668
|
-
[days, workingLocations],
|
|
662
|
+
() => layoutAllDayEvents(workingLocations, days, timezone),
|
|
663
|
+
[days, timezone, workingLocations],
|
|
669
664
|
);
|
|
670
665
|
const workingLocationGroups = useMemo(
|
|
671
666
|
() =>
|
|
@@ -684,29 +679,45 @@ export const WeekView = memo(function WeekView({
|
|
|
684
679
|
[prefs, workingLocationLabels, workingLocationLayout.placements],
|
|
685
680
|
);
|
|
686
681
|
const regularAllDayLayout = useMemo(() => {
|
|
687
|
-
return layoutAllDayEvents(regularEvents, days);
|
|
688
|
-
}, [days, regularEvents]);
|
|
682
|
+
return layoutAllDayEvents(regularEvents, days, timezone);
|
|
683
|
+
}, [days, regularEvents, timezone]);
|
|
689
684
|
|
|
690
685
|
// Pre-compute timed events per day with layout — include events spanning into this day
|
|
691
686
|
const dayData = useMemo(() => {
|
|
692
687
|
return days.map((day) => {
|
|
693
|
-
const
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
const evEnd = parseISO(e.end);
|
|
698
|
-
return evStart < dayEnd && evEnd > dayStart;
|
|
699
|
-
});
|
|
700
|
-
const layout = computeTimedEventLayout(dayEvents, day);
|
|
688
|
+
const dayEvents = timedEvents.filter((event) =>
|
|
689
|
+
getEventSegmentForCalendarDay(event, day, timezone),
|
|
690
|
+
);
|
|
691
|
+
const layout = computeTimedEventLayout(dayEvents, day, timezone);
|
|
701
692
|
return { day, events: dayEvents, layout };
|
|
702
693
|
});
|
|
703
|
-
}, [days, timedEvents]);
|
|
694
|
+
}, [days, timedEvents, timezone]);
|
|
704
695
|
|
|
705
696
|
// Current time indicator
|
|
706
|
-
const
|
|
697
|
+
const nowParts = getDateKeyInTimezone(now, timezone)
|
|
698
|
+
? new Intl.DateTimeFormat("en-US", {
|
|
699
|
+
timeZone: timezone,
|
|
700
|
+
hourCycle: "h23",
|
|
701
|
+
hour: "2-digit",
|
|
702
|
+
minute: "2-digit",
|
|
703
|
+
})
|
|
704
|
+
.formatToParts(now)
|
|
705
|
+
.reduce(
|
|
706
|
+
(parts, part) => {
|
|
707
|
+
if (part.type === "hour") parts.hour = Number(part.value);
|
|
708
|
+
if (part.type === "minute") parts.minute = Number(part.value);
|
|
709
|
+
return parts;
|
|
710
|
+
},
|
|
711
|
+
{ hour: 0, minute: 0 },
|
|
712
|
+
)
|
|
713
|
+
: null;
|
|
714
|
+
const nowMinutes = nowParts
|
|
715
|
+
? (nowParts.hour - START_HOUR) * 60 + nowParts.minute
|
|
716
|
+
: -1;
|
|
707
717
|
const nowTop = (nowMinutes / 60) * HOUR_HEIGHT;
|
|
708
718
|
const showNowIndicator =
|
|
709
719
|
nowMinutes >= 0 && nowMinutes <= (END_HOUR - START_HOUR) * 60;
|
|
720
|
+
const currentDateKey = getDateKeyInTimezone(now, timezone);
|
|
710
721
|
|
|
711
722
|
const hasWorkingLocations = workingLocationLayout.rowCount > 0;
|
|
712
723
|
const hasRegularAllDayEvents = regularAllDayLayout.rowCount > 0;
|
|
@@ -774,7 +785,10 @@ export const WeekView = memo(function WeekView({
|
|
|
774
785
|
function nameForToken(token: "shortGeneric" | "longGeneric" | "short") {
|
|
775
786
|
try {
|
|
776
787
|
return (
|
|
777
|
-
new Intl.DateTimeFormat("en-US", {
|
|
788
|
+
new Intl.DateTimeFormat("en-US", {
|
|
789
|
+
timeZone: timezone,
|
|
790
|
+
timeZoneName: token,
|
|
791
|
+
})
|
|
778
792
|
.formatToParts(now)
|
|
779
793
|
.find((p) => p.type === "timeZoneName")?.value ?? ""
|
|
780
794
|
);
|
|
@@ -783,10 +797,7 @@ export const WeekView = memo(function WeekView({
|
|
|
783
797
|
}
|
|
784
798
|
}
|
|
785
799
|
|
|
786
|
-
|
|
787
|
-
try {
|
|
788
|
-
iana = Intl.DateTimeFormat().resolvedOptions().timeZone ?? "";
|
|
789
|
-
} catch {}
|
|
800
|
+
const iana = timezone;
|
|
790
801
|
|
|
791
802
|
const longGeneric = nameForToken("longGeneric");
|
|
792
803
|
let shortGeneric = nameForToken("shortGeneric");
|
|
@@ -804,7 +815,7 @@ export const WeekView = memo(function WeekView({
|
|
|
804
815
|
tzLong: longGeneric || iana,
|
|
805
816
|
tzIana: iana,
|
|
806
817
|
};
|
|
807
|
-
}, []);
|
|
818
|
+
}, [now, timezone]);
|
|
808
819
|
|
|
809
820
|
// Drag-to-move and drag-to-resize
|
|
810
821
|
const handleEventTimeChange = useCallback(
|
|
@@ -827,6 +838,7 @@ export const WeekView = memo(function WeekView({
|
|
|
827
838
|
days,
|
|
828
839
|
onEventTimeChange: handleEventTimeChange,
|
|
829
840
|
events,
|
|
841
|
+
timezone,
|
|
830
842
|
});
|
|
831
843
|
|
|
832
844
|
const canDrag = !!onEventTimeChange;
|
|
@@ -934,30 +946,53 @@ export const WeekView = memo(function WeekView({
|
|
|
934
946
|
</div>
|
|
935
947
|
|
|
936
948
|
{/* Day columns */}
|
|
937
|
-
{days.map((day) =>
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
isToday(day) ? "bg-primary/5" : "hover:bg-accent/40",
|
|
944
|
-
)}
|
|
945
|
-
>
|
|
946
|
-
<span className="text-[10px] font-medium text-muted-foreground sm:text-xs">
|
|
947
|
-
{isMobile ? format(day, "EEEEE") : format(day, "EEE")}
|
|
948
|
-
</span>
|
|
949
|
-
<span
|
|
949
|
+
{days.map((day) => {
|
|
950
|
+
const isCurrentDay = dateToCalendarDateKey(day) === currentDateKey;
|
|
951
|
+
return (
|
|
952
|
+
<div
|
|
953
|
+
key={day.toISOString()}
|
|
954
|
+
onClick={() => onDateSelect(day)}
|
|
950
955
|
className={cn(
|
|
951
|
-
"flex
|
|
952
|
-
|
|
953
|
-
? "bg-foreground text-background"
|
|
954
|
-
: "text-foreground",
|
|
956
|
+
"group/day relative flex flex-1 cursor-pointer flex-col items-center justify-center gap-0.5 border-r border-border py-1.5 sm:flex-row sm:gap-1.5 sm:py-2.5 last:border-r-0",
|
|
957
|
+
isCurrentDay ? "bg-primary/5" : "hover:bg-accent/40",
|
|
955
958
|
)}
|
|
956
959
|
>
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
960
|
+
<span className="text-[10px] font-medium text-muted-foreground sm:text-xs">
|
|
961
|
+
{isMobile ? format(day, "EEEEE") : format(day, "EEE")}
|
|
962
|
+
</span>
|
|
963
|
+
<span
|
|
964
|
+
className={cn(
|
|
965
|
+
"flex h-6 w-6 items-center justify-center rounded-full text-xs font-semibold sm:h-7 sm:w-7 sm:text-sm",
|
|
966
|
+
isCurrentDay
|
|
967
|
+
? "bg-foreground text-background"
|
|
968
|
+
: "text-foreground",
|
|
969
|
+
)}
|
|
970
|
+
>
|
|
971
|
+
{format(day, "d")}
|
|
972
|
+
</span>
|
|
973
|
+
{onCreateWorkingLocation && (
|
|
974
|
+
<Tooltip>
|
|
975
|
+
<TooltipTrigger asChild>
|
|
976
|
+
<button
|
|
977
|
+
type="button"
|
|
978
|
+
aria-label={t("calendarView.addWorkingLocation")}
|
|
979
|
+
className="absolute right-1 top-1 flex size-5 items-center justify-center rounded text-muted-foreground opacity-0 transition-opacity hover:bg-accent hover:text-foreground focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring group-hover/day:opacity-100"
|
|
980
|
+
onClick={(event) => {
|
|
981
|
+
event.stopPropagation();
|
|
982
|
+
onCreateWorkingLocation(day);
|
|
983
|
+
}}
|
|
984
|
+
>
|
|
985
|
+
<IconPlus aria-hidden="true" className="size-3.5" />
|
|
986
|
+
</button>
|
|
987
|
+
</TooltipTrigger>
|
|
988
|
+
<TooltipContent side="bottom">
|
|
989
|
+
{t("calendarView.addWorkingLocation")}
|
|
990
|
+
</TooltipContent>
|
|
991
|
+
</Tooltip>
|
|
992
|
+
)}
|
|
993
|
+
</div>
|
|
994
|
+
);
|
|
995
|
+
})}
|
|
961
996
|
{timeGridScrollbarWidth > 0 && (
|
|
962
997
|
<div
|
|
963
998
|
aria-hidden="true"
|
|
@@ -1065,6 +1100,7 @@ export const WeekView = memo(function WeekView({
|
|
|
1065
1100
|
<EventDetailPopover
|
|
1066
1101
|
key={`${event.overlayEmail ?? event.accountEmail ?? "primary"}:${event.id}`}
|
|
1067
1102
|
event={event}
|
|
1103
|
+
timezone={timezone}
|
|
1068
1104
|
onDelete={onDeleteEvent}
|
|
1069
1105
|
isDraft={draftEventIds.includes(event.id)}
|
|
1070
1106
|
defaultOpen={quickEditEventId === event.id}
|
|
@@ -1145,6 +1181,7 @@ export const WeekView = memo(function WeekView({
|
|
|
1145
1181
|
<EventDetailPopover
|
|
1146
1182
|
key={`${event.overlayEmail ?? event.accountEmail ?? "primary"}:${event.id}`}
|
|
1147
1183
|
event={event}
|
|
1184
|
+
timezone={timezone}
|
|
1148
1185
|
onDelete={onDeleteEvent}
|
|
1149
1186
|
isDraft={draftEventIds.includes(event.id)}
|
|
1150
1187
|
defaultOpen={quickEditEventId === event.id}
|
|
@@ -1245,7 +1282,7 @@ export const WeekView = memo(function WeekView({
|
|
|
1245
1282
|
|
|
1246
1283
|
{/* Day columns */}
|
|
1247
1284
|
{dayData.map(({ day, events: dayEvents, layout }, dayIndex) => {
|
|
1248
|
-
const isCurrentDay =
|
|
1285
|
+
const isCurrentDay = dateToCalendarDateKey(day) === currentDateKey;
|
|
1249
1286
|
|
|
1250
1287
|
// Collect events that were dragged into this column from another day
|
|
1251
1288
|
const draggedInEvents: CalendarEvent[] = [];
|
|
@@ -1265,7 +1302,7 @@ export const WeekView = memo(function WeekView({
|
|
|
1265
1302
|
(event) => {
|
|
1266
1303
|
const overrides = getDragOverrides(event.id);
|
|
1267
1304
|
return (
|
|
1268
|
-
getOutOfOfficeSegment(event, day) !== null ||
|
|
1305
|
+
getOutOfOfficeSegment(event, day, timezone) !== null ||
|
|
1269
1306
|
(dragEventId === event.id && overrides?.dayIndex === dayIndex)
|
|
1270
1307
|
);
|
|
1271
1308
|
},
|
|
@@ -1353,12 +1390,13 @@ export const WeekView = memo(function WeekView({
|
|
|
1353
1390
|
const isBeingDragged = dragEventId === event.id;
|
|
1354
1391
|
const overrides = getDragOverrides(event.id);
|
|
1355
1392
|
const canonicalDayIndex =
|
|
1356
|
-
getFirstVisibleOutOfOfficeDayIndex(event, days);
|
|
1393
|
+
getFirstVisibleOutOfOfficeDayIndex(event, days, timezone);
|
|
1357
1394
|
return (
|
|
1358
1395
|
<OutOfOfficeEvent
|
|
1359
1396
|
key={`${event._tempId ?? event.id}:${day.toISOString()}`}
|
|
1360
1397
|
event={event}
|
|
1361
1398
|
day={day}
|
|
1399
|
+
timezone={timezone}
|
|
1362
1400
|
hourHeight={HOUR_HEIGHT}
|
|
1363
1401
|
color={
|
|
1364
1402
|
getEventDisplayColor(event, prefs) ??
|
|
@@ -1451,6 +1489,7 @@ export const WeekView = memo(function WeekView({
|
|
|
1451
1489
|
event={event}
|
|
1452
1490
|
day={day}
|
|
1453
1491
|
dayIndex={dayIndex}
|
|
1492
|
+
timezone={timezone}
|
|
1454
1493
|
layout={layout}
|
|
1455
1494
|
now={now}
|
|
1456
1495
|
prefs={prefs}
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
interface WorkingLocationEditorProps {
|
|
22
22
|
event: CalendarEvent;
|
|
23
23
|
isRecurring: boolean;
|
|
24
|
+
isDraft?: boolean;
|
|
24
25
|
readOnly?: boolean;
|
|
25
26
|
disabled?: boolean;
|
|
26
27
|
onSave: (selection: WorkingLocationSelection) => void;
|
|
@@ -40,6 +41,7 @@ function initialLabels(event: CalendarEvent) {
|
|
|
40
41
|
export function WorkingLocationEditor({
|
|
41
42
|
event,
|
|
42
43
|
isRecurring,
|
|
44
|
+
isDraft = false,
|
|
43
45
|
readOnly = false,
|
|
44
46
|
disabled = false,
|
|
45
47
|
onSave,
|
|
@@ -54,18 +56,42 @@ export function WorkingLocationEditor({
|
|
|
54
56
|
const detail = getWorkingLocationDetail(event, workingLocationLabels);
|
|
55
57
|
|
|
56
58
|
useEffect(() => {
|
|
57
|
-
setType(
|
|
58
|
-
setLabels(
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
setType(currentType);
|
|
60
|
+
setLabels((current) => {
|
|
61
|
+
const incoming = initialLabels(event);
|
|
62
|
+
if (!isDraft) return incoming;
|
|
63
|
+
return {
|
|
64
|
+
officeLocation:
|
|
65
|
+
currentType === "officeLocation"
|
|
66
|
+
? incoming.officeLocation
|
|
67
|
+
: current.officeLocation,
|
|
68
|
+
customLocation:
|
|
69
|
+
currentType === "customLocation"
|
|
70
|
+
? incoming.customLocation
|
|
71
|
+
: current.customLocation,
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
if (!isDraft) setScope("single");
|
|
75
|
+
}, [currentLabel, currentType, event.id, isDraft]);
|
|
61
76
|
|
|
62
77
|
const label = type === "homeOffice" ? "" : labels[type];
|
|
63
|
-
const isValid = type
|
|
78
|
+
const isValid = type !== "customLocation" || label.trim().length > 0;
|
|
64
79
|
const isDirty = useMemo(
|
|
65
80
|
() => type !== currentType || label.trim() !== currentLabel.trim(),
|
|
66
81
|
[currentLabel, currentType, label, type],
|
|
67
82
|
);
|
|
68
83
|
|
|
84
|
+
const commitDraft = (
|
|
85
|
+
nextType: WorkingLocationKind,
|
|
86
|
+
nextLabels: Record<LabeledWorkingLocation, string>,
|
|
87
|
+
) => {
|
|
88
|
+
if (!isDraft) return;
|
|
89
|
+
onSave({
|
|
90
|
+
type: nextType,
|
|
91
|
+
label: nextType === "homeOffice" ? "" : nextLabels[nextType],
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
|
|
69
95
|
if (readOnly) {
|
|
70
96
|
return (
|
|
71
97
|
<div className="flex items-start gap-3 py-1.5">
|
|
@@ -108,7 +134,11 @@ export function WorkingLocationEditor({
|
|
|
108
134
|
</div>
|
|
109
135
|
<RadioGroup
|
|
110
136
|
value={type}
|
|
111
|
-
onValueChange={(value) =>
|
|
137
|
+
onValueChange={(value) => {
|
|
138
|
+
const nextType = value as WorkingLocationKind;
|
|
139
|
+
setType(nextType);
|
|
140
|
+
commitDraft(nextType, labels);
|
|
141
|
+
}}
|
|
112
142
|
className="grid grid-cols-3 gap-1.5"
|
|
113
143
|
aria-label={t("eventForm.workingLocation")}
|
|
114
144
|
disabled={disabled}
|
|
@@ -143,12 +173,14 @@ export function WorkingLocationEditor({
|
|
|
143
173
|
<div className="mt-2">
|
|
144
174
|
<Input
|
|
145
175
|
value={labels[type]}
|
|
146
|
-
onChange={(
|
|
147
|
-
|
|
148
|
-
...
|
|
149
|
-
[type]:
|
|
150
|
-
}
|
|
151
|
-
|
|
176
|
+
onChange={(inputEvent) => {
|
|
177
|
+
const nextLabels = {
|
|
178
|
+
...labels,
|
|
179
|
+
[type]: inputEvent.target.value,
|
|
180
|
+
};
|
|
181
|
+
setLabels(nextLabels);
|
|
182
|
+
commitDraft(type, nextLabels);
|
|
183
|
+
}}
|
|
152
184
|
aria-label={
|
|
153
185
|
type === "officeLocation"
|
|
154
186
|
? t("eventForm.office")
|
|
@@ -166,7 +198,7 @@ export function WorkingLocationEditor({
|
|
|
166
198
|
</div>
|
|
167
199
|
)}
|
|
168
200
|
|
|
169
|
-
{isRecurring && (
|
|
201
|
+
{isRecurring && !isDraft && (
|
|
170
202
|
<div className="mt-2">
|
|
171
203
|
<p className="mb-1 text-[10px] font-medium text-muted-foreground">
|
|
172
204
|
{t("eventForm.applyTo")}
|
|
@@ -205,18 +237,20 @@ export function WorkingLocationEditor({
|
|
|
205
237
|
</div>
|
|
206
238
|
)}
|
|
207
239
|
|
|
208
|
-
|
|
209
|
-
<
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
240
|
+
{!isDraft && (
|
|
241
|
+
<div className="mt-2 flex justify-end">
|
|
242
|
+
<Button
|
|
243
|
+
size="sm"
|
|
244
|
+
className="h-7 text-xs"
|
|
245
|
+
disabled={disabled || !isValid || !isDirty}
|
|
246
|
+
onClick={() =>
|
|
247
|
+
onSave({ type, label, scope: isRecurring ? scope : undefined })
|
|
248
|
+
}
|
|
249
|
+
>
|
|
250
|
+
{t("eventForm.save")}
|
|
251
|
+
</Button>
|
|
252
|
+
</div>
|
|
253
|
+
)}
|
|
220
254
|
</div>
|
|
221
255
|
);
|
|
222
256
|
}
|