@agent-native/core 0.98.8 → 0.98.10
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/core/CHANGELOG.md +13 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/handlers.ts +39 -24
- package/corpus/core/src/agent/durable-background.ts +6 -0
- package/corpus/core/src/mcp/build-server.ts +104 -26
- package/corpus/core/src/server/agent-chat/plugin-options.ts +3 -1
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +4 -0
- package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +31 -2
- package/corpus/templates/calendar/AGENTS.md +11 -0
- package/corpus/templates/calendar/README.md +2 -1
- package/corpus/templates/calendar/actions/create-event.ts +7 -6
- package/corpus/templates/calendar/actions/event-action-helpers.ts +49 -0
- package/corpus/templates/calendar/actions/update-event.ts +225 -14
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +185 -70
- package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +26 -4
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +112 -69
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +621 -524
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +275 -164
- package/corpus/templates/calendar/app/components/calendar/WorkingLocationEditor.tsx +222 -0
- package/corpus/templates/calendar/app/hooks/use-events.ts +151 -79
- package/corpus/templates/calendar/app/i18n/zh-TW.ts +6 -0
- package/corpus/templates/calendar/app/i18n-data.ts +60 -0
- package/corpus/templates/calendar/app/lib/all-day-layout.ts +126 -0
- package/corpus/templates/calendar/app/lib/event-form-utils.ts +18 -1
- package/corpus/templates/calendar/app/lib/event-mutation-inputs.ts +1 -1
- package/corpus/templates/calendar/app/lib/working-location.ts +163 -0
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +21 -2
- package/corpus/templates/calendar/changelog/2026-07-07-working-locations-from-google-calendar-now-appear-as-native-.md +6 -0
- package/corpus/templates/calendar/server/lib/calendar-availability.ts +2 -0
- package/corpus/templates/calendar/server/lib/google-api.ts +6 -1
- package/corpus/templates/calendar/server/lib/google-calendar.ts +49 -19
- package/corpus/templates/calendar/shared/api.ts +2 -0
- package/dist/a2a/handlers.d.ts.map +1 -1
- package/dist/a2a/handlers.js +37 -24
- package/dist/a2a/handlers.js.map +1 -1
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +7 -0
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +90 -28
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +5 -5
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-chat/plugin-options.d.ts +3 -1
- package/dist/server/agent-chat/plugin-options.d.ts.map +1 -1
- package/dist/server/agent-chat/plugin-options.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
attachmentsInput,
|
|
14
14
|
attendeesInput,
|
|
15
15
|
buildReminderOverrides,
|
|
16
|
+
buildStatusEventFields,
|
|
16
17
|
cliBoolean,
|
|
17
18
|
googleColorIdInput,
|
|
18
19
|
normalizeAttendees,
|
|
@@ -23,7 +24,9 @@ import {
|
|
|
23
24
|
remindersInput,
|
|
24
25
|
requireActionUserEmail,
|
|
25
26
|
resolveOwnedAccountEmail,
|
|
27
|
+
validateStatusEventTiming,
|
|
26
28
|
visibilityInput,
|
|
29
|
+
workingLocationTypeInput,
|
|
27
30
|
} from "./event-action-helpers.js";
|
|
28
31
|
|
|
29
32
|
function mergeAttendees(
|
|
@@ -64,6 +67,16 @@ function mergeAttendees(
|
|
|
64
67
|
return Array.from(merged.values());
|
|
65
68
|
}
|
|
66
69
|
|
|
70
|
+
function workingLocationTitle(
|
|
71
|
+
properties: NonNullable<CalendarEvent["workingLocationProperties"]>,
|
|
72
|
+
): string {
|
|
73
|
+
if (properties.type === "homeOffice") return "Home";
|
|
74
|
+
if (properties.type === "officeLocation") {
|
|
75
|
+
return properties.officeLocation?.label || "Office";
|
|
76
|
+
}
|
|
77
|
+
return properties.customLocation?.label || "Working location";
|
|
78
|
+
}
|
|
79
|
+
|
|
67
80
|
export default defineAction({
|
|
68
81
|
description:
|
|
69
82
|
"Update a Google Calendar event. Supports title, description, location, time, event color, attachments, reminders, and recurrence rules such as RRULE:FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR.",
|
|
@@ -80,6 +93,15 @@ export default defineAction({
|
|
|
80
93
|
title: z.string().optional().describe("New event title"),
|
|
81
94
|
description: z.string().optional().describe("New event description"),
|
|
82
95
|
location: z.string().optional().describe("New event location"),
|
|
96
|
+
workingLocationType: workingLocationTypeInput.describe(
|
|
97
|
+
"For existing working-location events: homeOffice, officeLocation, or customLocation. Google Calendar event types cannot be changed after creation.",
|
|
98
|
+
),
|
|
99
|
+
workingLocationLabel: z
|
|
100
|
+
.string()
|
|
101
|
+
.optional()
|
|
102
|
+
.describe(
|
|
103
|
+
"For existing working-location events: label shown in Google Calendar.",
|
|
104
|
+
),
|
|
83
105
|
start: z.string().optional().describe("New start time/date as ISO string"),
|
|
84
106
|
end: z.string().optional().describe("New end time/date as ISO string"),
|
|
85
107
|
startTimeZone: z
|
|
@@ -92,10 +114,10 @@ export default defineAction({
|
|
|
92
114
|
.describe("IANA timezone for the event end, e.g. America/New_York"),
|
|
93
115
|
allDay: cliBoolean.optional().describe("Whether the event is all-day"),
|
|
94
116
|
transparency: availabilityInput.describe(
|
|
95
|
-
"Google Calendar availability: opaque blocks time (Busy), transparent does not block time (Free).",
|
|
117
|
+
"Google Calendar availability: opaque blocks time (Busy), transparent does not block time (Free). Existing working-location events are always sent to Google as transparent.",
|
|
96
118
|
),
|
|
97
119
|
visibility: visibilityInput.describe(
|
|
98
|
-
"Google Calendar visibility: default, public, private, or confidential.",
|
|
120
|
+
"Google Calendar visibility: default, public, private, or confidential. Existing working-location events are always sent to Google as public.",
|
|
99
121
|
),
|
|
100
122
|
status: z
|
|
101
123
|
.enum(["confirmed", "tentative", "cancelled"])
|
|
@@ -193,6 +215,13 @@ export default defineAction({
|
|
|
193
215
|
reminderMethod: args.reminderMethod,
|
|
194
216
|
useDefaultReminders: args.remindersUseDefault,
|
|
195
217
|
});
|
|
218
|
+
const hasWorkingLocationPatch =
|
|
219
|
+
args.workingLocationType !== undefined ||
|
|
220
|
+
args.workingLocationLabel !== undefined;
|
|
221
|
+
const hasTimePatch =
|
|
222
|
+
args.start !== undefined ||
|
|
223
|
+
args.end !== undefined ||
|
|
224
|
+
args.allDay !== undefined;
|
|
196
225
|
|
|
197
226
|
const attendeesToAdd = normalizeAttendees(args.addAttendees);
|
|
198
227
|
let attendees = normalizeAttendees(args.attendees);
|
|
@@ -216,7 +245,8 @@ export default defineAction({
|
|
|
216
245
|
attendeesToAdd !== undefined ||
|
|
217
246
|
Object.keys(reminderFields).length > 0 ||
|
|
218
247
|
args.addGoogleMeet === true ||
|
|
219
|
-
args.addZoom === true
|
|
248
|
+
args.addZoom === true ||
|
|
249
|
+
hasWorkingLocationPatch;
|
|
220
250
|
|
|
221
251
|
if (!hasPatch) {
|
|
222
252
|
throw new Error("No event updates provided.");
|
|
@@ -253,6 +283,91 @@ export default defineAction({
|
|
|
253
283
|
return existingEvent;
|
|
254
284
|
};
|
|
255
285
|
|
|
286
|
+
if (args.location !== undefined && !hasWorkingLocationPatch) {
|
|
287
|
+
const existingEvent = await loadExistingEvent();
|
|
288
|
+
if (existingEvent.eventType === "workingLocation") {
|
|
289
|
+
throw new Error(
|
|
290
|
+
"Working-location events do not support a generic location. Use workingLocationType and workingLocationLabel instead.",
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (hasTimePatch) {
|
|
296
|
+
const existingEvent = await loadExistingEvent();
|
|
297
|
+
const existingStatusEventType =
|
|
298
|
+
existingEvent.eventType === "outOfOffice" ||
|
|
299
|
+
existingEvent.eventType === "focusTime" ||
|
|
300
|
+
existingEvent.eventType === "workingLocation"
|
|
301
|
+
? existingEvent.eventType
|
|
302
|
+
: "default";
|
|
303
|
+
validateStatusEventTiming({
|
|
304
|
+
eventType: existingStatusEventType,
|
|
305
|
+
allDay: args.allDay ?? existingEvent.allDay,
|
|
306
|
+
start: args.start ?? existingEvent.start,
|
|
307
|
+
end: args.end ?? existingEvent.end,
|
|
308
|
+
});
|
|
309
|
+
if (
|
|
310
|
+
existingEvent.eventType === "workingLocation" &&
|
|
311
|
+
existingEvent.workingLocationProperties
|
|
312
|
+
) {
|
|
313
|
+
Object.assign(updates, {
|
|
314
|
+
eventType: "workingLocation" as const,
|
|
315
|
+
transparency: "transparent" as const,
|
|
316
|
+
visibility: "public" as const,
|
|
317
|
+
workingLocationProperties: existingEvent.workingLocationProperties,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (hasWorkingLocationPatch) {
|
|
323
|
+
const existingEvent = await loadExistingEvent();
|
|
324
|
+
if (existingEvent.eventType !== "workingLocation") {
|
|
325
|
+
throw new Error(
|
|
326
|
+
"Working location details can only be updated on existing working-location events. Google Calendar event types cannot be changed after creation.",
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
const nextWorkingLocationType =
|
|
330
|
+
args.workingLocationType ??
|
|
331
|
+
existingEvent.workingLocationProperties?.type ??
|
|
332
|
+
"customLocation";
|
|
333
|
+
const existingWorkingLocationLabel =
|
|
334
|
+
existingEvent.workingLocationProperties?.type === "officeLocation"
|
|
335
|
+
? existingEvent.workingLocationProperties.officeLocation?.label
|
|
336
|
+
: existingEvent.workingLocationProperties?.type === "customLocation"
|
|
337
|
+
? existingEvent.workingLocationProperties.customLocation?.label
|
|
338
|
+
: undefined;
|
|
339
|
+
const nextWorkingLocationLabel =
|
|
340
|
+
args.workingLocationLabel ??
|
|
341
|
+
args.location ??
|
|
342
|
+
existingWorkingLocationLabel ??
|
|
343
|
+
existingEvent.title;
|
|
344
|
+
const workingLocationFields = buildStatusEventFields({
|
|
345
|
+
eventType: "workingLocation",
|
|
346
|
+
title: args.title ?? existingEvent.title,
|
|
347
|
+
workingLocationType: nextWorkingLocationType,
|
|
348
|
+
workingLocationLabel: nextWorkingLocationLabel,
|
|
349
|
+
});
|
|
350
|
+
const nextWorkingLocationProperties =
|
|
351
|
+
nextWorkingLocationType === "officeLocation"
|
|
352
|
+
? {
|
|
353
|
+
type: "officeLocation" as const,
|
|
354
|
+
officeLocation: {
|
|
355
|
+
...(existingEvent.workingLocationProperties?.type ===
|
|
356
|
+
"officeLocation"
|
|
357
|
+
? existingEvent.workingLocationProperties.officeLocation
|
|
358
|
+
: {}),
|
|
359
|
+
label: nextWorkingLocationLabel,
|
|
360
|
+
},
|
|
361
|
+
}
|
|
362
|
+
: workingLocationFields.workingLocationProperties;
|
|
363
|
+
Object.assign(updates, {
|
|
364
|
+
transparency: "transparent",
|
|
365
|
+
visibility: "public",
|
|
366
|
+
workingLocationProperties: nextWorkingLocationProperties,
|
|
367
|
+
});
|
|
368
|
+
delete updates.location;
|
|
369
|
+
}
|
|
370
|
+
|
|
256
371
|
if (attendeesToAdd !== undefined) {
|
|
257
372
|
const existingEvent = await loadExistingEvent();
|
|
258
373
|
attendees = mergeAttendees(existingEvent.attendees, attendeesToAdd);
|
|
@@ -296,16 +411,109 @@ export default defineAction({
|
|
|
296
411
|
};
|
|
297
412
|
}
|
|
298
413
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
414
|
+
let result: Awaited<ReturnType<typeof googleCalendar.updateEvent>>;
|
|
415
|
+
let returnedGoogleEventId = googleEventId;
|
|
416
|
+
const replaceRecurringWorkingLocation =
|
|
417
|
+
hasWorkingLocationPatch &&
|
|
418
|
+
(args.scope ?? "single") === "single" &&
|
|
419
|
+
existingEvent?.recurringEventId &&
|
|
420
|
+
updates.workingLocationProperties;
|
|
421
|
+
|
|
422
|
+
if (replaceRecurringWorkingLocation) {
|
|
423
|
+
const hasUnsupportedReplacementPatch =
|
|
424
|
+
args.title !== undefined ||
|
|
425
|
+
args.description !== undefined ||
|
|
426
|
+
args.start !== undefined ||
|
|
427
|
+
args.end !== undefined ||
|
|
428
|
+
args.startTimeZone !== undefined ||
|
|
429
|
+
args.endTimeZone !== undefined ||
|
|
430
|
+
args.allDay !== undefined ||
|
|
431
|
+
args.status !== undefined ||
|
|
432
|
+
args.colorId !== undefined ||
|
|
433
|
+
args.attachments !== undefined ||
|
|
434
|
+
args.reminders !== undefined ||
|
|
435
|
+
args.reminderMinutes !== undefined ||
|
|
436
|
+
args.remindersUseDefault !== undefined ||
|
|
437
|
+
args.addGoogleMeet === true ||
|
|
438
|
+
args.addZoom === true ||
|
|
439
|
+
args.recurrence !== undefined ||
|
|
440
|
+
args.attendees !== undefined ||
|
|
441
|
+
args.addAttendees !== undefined ||
|
|
442
|
+
args.notificationMessage !== undefined;
|
|
443
|
+
if (hasUnsupportedReplacementPatch) {
|
|
444
|
+
throw new Error(
|
|
445
|
+
"Change the working location separately from other event fields for a single recurring occurrence.",
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
const now = new Date().toISOString();
|
|
449
|
+
const replacement = await googleCalendar.createEvent(
|
|
450
|
+
{
|
|
451
|
+
id: "",
|
|
452
|
+
title: workingLocationTitle(updates.workingLocationProperties!),
|
|
453
|
+
description: "",
|
|
454
|
+
start: existingEvent!.start,
|
|
455
|
+
end: existingEvent!.end,
|
|
456
|
+
startTimeZone: existingEvent!.startTimeZone,
|
|
457
|
+
endTimeZone: existingEvent!.endTimeZone,
|
|
458
|
+
location: "",
|
|
459
|
+
allDay: existingEvent!.allDay,
|
|
460
|
+
source: "google",
|
|
461
|
+
accountEmail,
|
|
462
|
+
colorId: existingEvent!.colorId,
|
|
463
|
+
transparency: "transparent",
|
|
464
|
+
visibility: "public",
|
|
465
|
+
status: "confirmed",
|
|
466
|
+
eventType: "workingLocation",
|
|
467
|
+
workingLocationProperties: updates.workingLocationProperties,
|
|
468
|
+
createdAt: now,
|
|
469
|
+
updatedAt: now,
|
|
470
|
+
},
|
|
471
|
+
{ account: { ownerEmail, accountEmail } },
|
|
472
|
+
);
|
|
473
|
+
if (!replacement.id) {
|
|
474
|
+
throw new Error(
|
|
475
|
+
"Google did not return an id for the working location.",
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
try {
|
|
479
|
+
await googleCalendar.deleteEvent(
|
|
480
|
+
googleEventId,
|
|
481
|
+
{ ownerEmail, accountEmail },
|
|
482
|
+
{ scope: "single" },
|
|
483
|
+
);
|
|
484
|
+
} catch (error) {
|
|
485
|
+
try {
|
|
486
|
+
await googleCalendar.deleteEvent(
|
|
487
|
+
replacement.id,
|
|
488
|
+
{ ownerEmail, accountEmail },
|
|
489
|
+
{ scope: "single" },
|
|
490
|
+
);
|
|
491
|
+
} catch (cleanupError) {
|
|
492
|
+
console.error(
|
|
493
|
+
"Failed to clean up a working-location replacement after the original occurrence could not be cancelled.",
|
|
494
|
+
cleanupError,
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
throw error;
|
|
498
|
+
}
|
|
499
|
+
returnedGoogleEventId = replacement.id;
|
|
500
|
+
result = {
|
|
501
|
+
htmlLink: replacement.htmlLink,
|
|
502
|
+
meetLink: replacement.meetLink,
|
|
503
|
+
conferenceData: replacement.conferenceData,
|
|
504
|
+
};
|
|
505
|
+
} else {
|
|
506
|
+
result = await googleCalendar.updateEvent(googleEventId, updates, {
|
|
507
|
+
account: { ownerEmail, accountEmail },
|
|
508
|
+
sendUpdates:
|
|
509
|
+
args.sendUpdates ??
|
|
510
|
+
(guestNotificationMessage || (attendeesToAdd?.length ?? 0) > 0
|
|
511
|
+
? "all"
|
|
512
|
+
: undefined),
|
|
513
|
+
addGoogleMeet: args.addGoogleMeet,
|
|
514
|
+
scope: args.scope,
|
|
515
|
+
});
|
|
516
|
+
}
|
|
309
517
|
|
|
310
518
|
const returnedPatch: Partial<CalendarEvent> = {};
|
|
311
519
|
if (result.htmlLink) returnedPatch.htmlLink = result.htmlLink;
|
|
@@ -340,7 +548,10 @@ export default defineAction({
|
|
|
340
548
|
|
|
341
549
|
return {
|
|
342
550
|
success: true,
|
|
343
|
-
id: `google-${
|
|
551
|
+
id: `google-${returnedGoogleEventId}`,
|
|
552
|
+
...(returnedGoogleEventId !== googleEventId
|
|
553
|
+
? { replacedId: `google-${googleEventId}` }
|
|
554
|
+
: {}),
|
|
344
555
|
accountEmail,
|
|
345
556
|
updated: updatedKeys,
|
|
346
557
|
htmlLink: result.htmlLink,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useT } from "@agent-native/core/client";
|
|
2
2
|
import type { CalendarEvent } from "@shared/api";
|
|
3
|
-
import { IconAlertTriangleFilled } from "@tabler/icons-react";
|
|
3
|
+
import { IconAlertTriangleFilled, IconMapPin } from "@tabler/icons-react";
|
|
4
4
|
import {
|
|
5
5
|
eachHourOfInterval,
|
|
6
6
|
format,
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
useViewPreferences,
|
|
29
29
|
type ViewPreferences,
|
|
30
30
|
} from "@/hooks/use-view-preferences";
|
|
31
|
+
import { partitionAllDayEvents } from "@/lib/all-day-layout";
|
|
31
32
|
import { getEventDisplayColor, allOtherDeclined } from "@/lib/event-colors";
|
|
32
33
|
import {
|
|
33
34
|
shouldSuppressAfterPopoverClose,
|
|
@@ -35,6 +36,12 @@ import {
|
|
|
35
36
|
} from "@/lib/popover-click-guard";
|
|
36
37
|
import { EventStatusIcon } from "@/lib/rsvp-status";
|
|
37
38
|
import { cn } from "@/lib/utils";
|
|
39
|
+
import {
|
|
40
|
+
createWorkingLocationDisplayLabels,
|
|
41
|
+
getWorkingLocationChipLabel,
|
|
42
|
+
getWorkingLocationTitle,
|
|
43
|
+
isWorkingLocationEvent,
|
|
44
|
+
} from "@/lib/working-location";
|
|
38
45
|
|
|
39
46
|
import { EventDetailPopover } from "./EventDetailPopover";
|
|
40
47
|
|
|
@@ -256,6 +263,8 @@ const DayEventCard = memo(function DayEventCard({
|
|
|
256
263
|
onDraftCreate,
|
|
257
264
|
onDraftDiscard,
|
|
258
265
|
}: DayEventCardProps) {
|
|
266
|
+
const t = useT();
|
|
267
|
+
const workingLocationLabels = createWorkingLocationDisplayLabels(t);
|
|
259
268
|
const li = layout.get(event.id) ?? {
|
|
260
269
|
left: 0,
|
|
261
270
|
width: 100,
|
|
@@ -323,10 +332,10 @@ const DayEventCard = memo(function DayEventCard({
|
|
|
323
332
|
)}
|
|
324
333
|
aria-label={
|
|
325
334
|
event.ownerName || event.overlayEmail
|
|
326
|
-
? `${event
|
|
335
|
+
? `${getWorkingLocationTitle(event, workingLocationLabels)}, ${
|
|
327
336
|
event.ownerName || event.overlayEmail
|
|
328
337
|
}'s calendar`
|
|
329
|
-
: event
|
|
338
|
+
: getWorkingLocationTitle(event, workingLocationLabels)
|
|
330
339
|
}
|
|
331
340
|
style={{
|
|
332
341
|
...posStyle,
|
|
@@ -381,8 +390,13 @@ const DayEventCard = memo(function DayEventCard({
|
|
|
381
390
|
!isPast && !isDeclined && "font-semibold",
|
|
382
391
|
)}
|
|
383
392
|
>
|
|
384
|
-
{event
|
|
393
|
+
{getWorkingLocationChipLabel(event, workingLocationLabels)}
|
|
385
394
|
</span>
|
|
395
|
+
{isWorkingLocationEvent(event) && (
|
|
396
|
+
<span className="shrink-0 text-[10px] font-normal text-foreground/55">
|
|
397
|
+
{t("eventForm.workingLocation")}
|
|
398
|
+
</span>
|
|
399
|
+
)}
|
|
386
400
|
</div>
|
|
387
401
|
) : (
|
|
388
402
|
<>
|
|
@@ -403,8 +417,15 @@ const DayEventCard = memo(function DayEventCard({
|
|
|
403
417
|
/>
|
|
404
418
|
)}
|
|
405
419
|
<EventStatusIcon event={event} className="shrink-0" />
|
|
406
|
-
<span className="truncate">
|
|
420
|
+
<span className="truncate">
|
|
421
|
+
{getWorkingLocationChipLabel(event, workingLocationLabels)}
|
|
422
|
+
</span>
|
|
407
423
|
</div>
|
|
424
|
+
{isWorkingLocationEvent(event) && isStart && (
|
|
425
|
+
<div className="mt-0.5 truncate text-[10px] leading-tight text-foreground/60">
|
|
426
|
+
{t("eventForm.workingLocation")}
|
|
427
|
+
</div>
|
|
428
|
+
)}
|
|
408
429
|
{isStart && (
|
|
409
430
|
<div
|
|
410
431
|
className={cn(
|
|
@@ -517,6 +538,7 @@ export const DayView = memo(function DayView({
|
|
|
517
538
|
isLoading = false,
|
|
518
539
|
}: DayViewProps) {
|
|
519
540
|
const t = useT();
|
|
541
|
+
const workingLocationLabels = createWorkingLocationDisplayLabels(t);
|
|
520
542
|
const { setFocusedEvent } = useCalendarSetters();
|
|
521
543
|
const { prefs } = useViewPreferences();
|
|
522
544
|
const [now, setNow] = useState(new Date());
|
|
@@ -568,6 +590,10 @@ export const DayView = memo(function DayView({
|
|
|
568
590
|
);
|
|
569
591
|
|
|
570
592
|
const allDayEvents = useMemo(() => events.filter((e) => e.allDay), [events]);
|
|
593
|
+
const { workingLocations, regularEvents: regularAllDayEvents } = useMemo(
|
|
594
|
+
() => partitionAllDayEvents(allDayEvents),
|
|
595
|
+
[allDayEvents],
|
|
596
|
+
);
|
|
571
597
|
const timedEvents = useMemo(() => events.filter((e) => !e.allDay), [events]);
|
|
572
598
|
const layout = useMemo(() => computeLayout(timedEvents), [timedEvents]);
|
|
573
599
|
|
|
@@ -726,72 +752,161 @@ export const DayView = memo(function DayView({
|
|
|
726
752
|
</Tooltip>
|
|
727
753
|
</div>
|
|
728
754
|
|
|
729
|
-
{/*
|
|
755
|
+
{/* Working locations and ordinary all-day events */}
|
|
730
756
|
{allDayEvents.length > 0 && (
|
|
731
|
-
<div className="border-b border-border bg-card/50
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
757
|
+
<div className="border-b border-border bg-card/50">
|
|
758
|
+
{workingLocations.length > 0 && (
|
|
759
|
+
<div data-working-location-lane className="px-4 py-1.5">
|
|
760
|
+
<p className="mb-1 flex items-center gap-1 text-[10px] font-medium uppercase text-muted-foreground">
|
|
761
|
+
<IconMapPin aria-hidden="true" className="size-3" />
|
|
762
|
+
{t("eventForm.workingLocation")}
|
|
763
|
+
</p>
|
|
764
|
+
<div className="grid gap-1 sm:grid-cols-2">
|
|
765
|
+
{workingLocations.map((event) => {
|
|
766
|
+
const color = getEventDisplayColor(event, prefs);
|
|
767
|
+
return (
|
|
768
|
+
<EventDetailPopover
|
|
769
|
+
key={`${event.overlayEmail ?? event.accountEmail ?? "primary"}:${event.id}`}
|
|
770
|
+
event={event}
|
|
771
|
+
onDelete={onDeleteEvent}
|
|
772
|
+
isDraft={draftEventIds.includes(event.id)}
|
|
773
|
+
defaultOpen={quickEditEventId === event.id}
|
|
774
|
+
onTitleSave={onQuickEditSave}
|
|
775
|
+
onDismissNew={onQuickEditCancel}
|
|
776
|
+
onDraftUpdate={onDraftUpdate}
|
|
777
|
+
onDraftCreate={onDraftCreate}
|
|
778
|
+
onDraftDiscard={onDraftDiscard}
|
|
779
|
+
>
|
|
780
|
+
<button
|
|
781
|
+
className={cn(
|
|
782
|
+
"relative flex h-6 w-full items-center gap-1.5 truncate rounded-sm px-2 text-left text-xs font-medium text-foreground transition-opacity hover:opacity-80",
|
|
783
|
+
event.ownerColor && "pr-5",
|
|
784
|
+
)}
|
|
785
|
+
aria-label={
|
|
786
|
+
event.ownerName || event.overlayEmail
|
|
787
|
+
? `${getWorkingLocationTitle(event, workingLocationLabels)}, ${
|
|
788
|
+
event.ownerName || event.overlayEmail
|
|
789
|
+
}'s calendar`
|
|
790
|
+
: getWorkingLocationTitle(
|
|
791
|
+
event,
|
|
792
|
+
workingLocationLabels,
|
|
793
|
+
)
|
|
794
|
+
}
|
|
795
|
+
style={{
|
|
796
|
+
backgroundColor: color
|
|
797
|
+
? `${color}1f`
|
|
798
|
+
: "hsl(var(--muted))",
|
|
799
|
+
borderLeft: `2px solid ${
|
|
800
|
+
color ?? "hsl(var(--muted-foreground))"
|
|
801
|
+
}`,
|
|
802
|
+
}}
|
|
803
|
+
>
|
|
804
|
+
<IconMapPin
|
|
805
|
+
aria-hidden="true"
|
|
806
|
+
className="size-3 shrink-0 opacity-70"
|
|
807
|
+
/>
|
|
808
|
+
<span className="truncate">
|
|
809
|
+
{getWorkingLocationChipLabel(
|
|
810
|
+
event,
|
|
811
|
+
workingLocationLabels,
|
|
812
|
+
)}
|
|
813
|
+
</span>
|
|
814
|
+
{event.ownerColor && (
|
|
815
|
+
<span
|
|
816
|
+
aria-hidden="true"
|
|
817
|
+
className="absolute right-2 top-1/2 size-1.5 -translate-y-1/2 rounded-full ring-1 ring-background/70"
|
|
818
|
+
style={{ backgroundColor: event.ownerColor }}
|
|
819
|
+
/>
|
|
820
|
+
)}
|
|
821
|
+
</button>
|
|
822
|
+
</EventDetailPopover>
|
|
823
|
+
);
|
|
824
|
+
})}
|
|
825
|
+
</div>
|
|
826
|
+
</div>
|
|
827
|
+
)}
|
|
828
|
+
|
|
829
|
+
{regularAllDayEvents.length > 0 && (
|
|
830
|
+
<div
|
|
831
|
+
data-all-day-event-lane
|
|
832
|
+
className={cn(
|
|
833
|
+
"px-4 py-2",
|
|
834
|
+
workingLocations.length > 0 && "border-t border-border/60",
|
|
835
|
+
)}
|
|
836
|
+
>
|
|
837
|
+
<p className="mb-1.5 text-[11px] font-medium uppercase text-muted-foreground">
|
|
838
|
+
{t("eventForm.allDay")}
|
|
839
|
+
</p>
|
|
840
|
+
<div className="flex flex-col gap-1">
|
|
841
|
+
{regularAllDayEvents.map((event) => {
|
|
842
|
+
const color = getEventDisplayColor(event, prefs);
|
|
843
|
+
return (
|
|
844
|
+
<EventDetailPopover
|
|
845
|
+
key={`${event.overlayEmail ?? event.accountEmail ?? "primary"}:${event.id}`}
|
|
846
|
+
event={event}
|
|
847
|
+
onDelete={onDeleteEvent}
|
|
848
|
+
isDraft={draftEventIds.includes(event.id)}
|
|
849
|
+
defaultOpen={quickEditEventId === event.id}
|
|
850
|
+
onTitleSave={onQuickEditSave}
|
|
851
|
+
onDismissNew={onQuickEditCancel}
|
|
852
|
+
onDraftUpdate={onDraftUpdate}
|
|
853
|
+
onDraftCreate={onDraftCreate}
|
|
854
|
+
onDraftDiscard={onDraftDiscard}
|
|
855
|
+
>
|
|
856
|
+
<button
|
|
857
|
+
className={cn(
|
|
858
|
+
"relative flex w-full items-center gap-1.5 rounded-md px-3 py-1.5 text-left text-sm font-medium text-foreground transition-all hover:brightness-110",
|
|
859
|
+
event.ownerColor && "pr-5",
|
|
860
|
+
)}
|
|
861
|
+
aria-label={
|
|
862
|
+
event.ownerName || event.overlayEmail
|
|
863
|
+
? `${getWorkingLocationTitle(event, workingLocationLabels)}, ${
|
|
864
|
+
event.ownerName || event.overlayEmail
|
|
865
|
+
}'s calendar`
|
|
866
|
+
: getWorkingLocationTitle(
|
|
867
|
+
event,
|
|
868
|
+
workingLocationLabels,
|
|
869
|
+
)
|
|
870
|
+
}
|
|
871
|
+
style={
|
|
872
|
+
color
|
|
873
|
+
? {
|
|
874
|
+
backgroundColor: `${color}30`,
|
|
875
|
+
borderLeft: `3px solid ${color}`,
|
|
876
|
+
}
|
|
877
|
+
: {
|
|
878
|
+
backgroundColor: "hsl(var(--primary) / 0.15)",
|
|
879
|
+
borderLeft: "3px solid hsl(var(--primary))",
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
>
|
|
883
|
+
{allOtherDeclined(event) && (
|
|
884
|
+
<IconAlertTriangleFilled
|
|
885
|
+
size={14}
|
|
886
|
+
className="shrink-0 text-current opacity-70"
|
|
887
|
+
/>
|
|
888
|
+
)}
|
|
889
|
+
<EventStatusIcon event={event} className="shrink-0" />
|
|
890
|
+
<span className="truncate">
|
|
891
|
+
{getWorkingLocationChipLabel(
|
|
892
|
+
event,
|
|
893
|
+
workingLocationLabels,
|
|
894
|
+
)}
|
|
895
|
+
</span>
|
|
896
|
+
{event.ownerColor && (
|
|
897
|
+
<span
|
|
898
|
+
aria-hidden="true"
|
|
899
|
+
className="absolute right-2 top-1/2 size-1.5 -translate-y-1/2 rounded-full ring-1 ring-background/70"
|
|
900
|
+
style={{ backgroundColor: event.ownerColor }}
|
|
901
|
+
/>
|
|
902
|
+
)}
|
|
903
|
+
</button>
|
|
904
|
+
</EventDetailPopover>
|
|
905
|
+
);
|
|
906
|
+
})}
|
|
907
|
+
</div>
|
|
908
|
+
</div>
|
|
909
|
+
)}
|
|
795
910
|
</div>
|
|
796
911
|
)}
|
|
797
912
|
|