@agent-native/core 0.98.7 → 0.98.9
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 +12 -0
- package/corpus/core/docs/content/external-agents.mdx +17 -11
- package/corpus/core/docs/content/locales/ar-SA/external-agents.mdx +6 -2
- package/corpus/core/docs/content/locales/de-DE/external-agents.mdx +6 -2
- package/corpus/core/docs/content/locales/es-ES/external-agents.mdx +6 -2
- package/corpus/core/docs/content/locales/fr-FR/external-agents.mdx +6 -2
- package/corpus/core/docs/content/locales/hi-IN/external-agents.mdx +6 -2
- package/corpus/core/docs/content/locales/ja-JP/external-agents.mdx +6 -2
- package/corpus/core/docs/content/locales/ko-KR/external-agents.mdx +6 -2
- package/corpus/core/docs/content/locales/pt-BR/external-agents.mdx +6 -2
- package/corpus/core/docs/content/locales/zh-CN/external-agents.mdx +6 -2
- package/corpus/core/docs/content/locales/zh-TW/external-agents.mdx +6 -2
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/session-replay.ts +29 -7
- package/corpus/core/src/integrations/adapters/slack.ts +13 -2
- package/corpus/core/src/integrations/pending-tasks-retry-job.ts +2 -1
- package/corpus/core/src/integrations/pending-tasks-store.ts +1 -1
- package/corpus/core/src/integrations/plugin.ts +183 -48
- package/corpus/core/src/integrations/types.ts +7 -0
- package/corpus/core/src/mcp/build-server.ts +99 -26
- 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/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +24 -7
- package/dist/client/session-replay.js.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/integrations/adapters/slack.js +11 -2
- package/dist/integrations/adapters/slack.js.map +1 -1
- package/dist/integrations/pending-tasks-retry-job.d.ts.map +1 -1
- package/dist/integrations/pending-tasks-retry-job.js +2 -1
- package/dist/integrations/pending-tasks-retry-job.js.map +1 -1
- package/dist/integrations/pending-tasks-store.js +1 -1
- package/dist/integrations/pending-tasks-store.js.map +1 -1
- package/dist/integrations/plugin.d.ts.map +1 -1
- package/dist/integrations/plugin.js +140 -34
- package/dist/integrations/plugin.js.map +1 -1
- package/dist/integrations/types.d.ts +7 -0
- package/dist/integrations/types.d.ts.map +1 -1
- package/dist/integrations/types.js.map +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +82 -28
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/secrets/routes.d.ts +9 -9
- package/docs/content/external-agents.mdx +17 -11
- package/docs/content/locales/ar-SA/external-agents.mdx +6 -2
- package/docs/content/locales/de-DE/external-agents.mdx +6 -2
- package/docs/content/locales/es-ES/external-agents.mdx +6 -2
- package/docs/content/locales/fr-FR/external-agents.mdx +6 -2
- package/docs/content/locales/hi-IN/external-agents.mdx +6 -2
- package/docs/content/locales/ja-JP/external-agents.mdx +6 -2
- package/docs/content/locales/ko-KR/external-agents.mdx +6 -2
- package/docs/content/locales/pt-BR/external-agents.mdx +6 -2
- package/docs/content/locales/zh-CN/external-agents.mdx +6 -2
- package/docs/content/locales/zh-TW/external-agents.mdx +6 -2
- package/package.json +1 -1
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { useT } from "@agent-native/core/client";
|
|
2
|
+
import type { CalendarEvent } from "@shared/api";
|
|
3
|
+
import { IconBuilding, IconHome, IconMapPin } from "@tabler/icons-react";
|
|
4
|
+
import { useEffect, useMemo, useState } from "react";
|
|
5
|
+
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import { Input } from "@/components/ui/input";
|
|
8
|
+
import { Label } from "@/components/ui/label";
|
|
9
|
+
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
10
|
+
import { cn } from "@/lib/utils";
|
|
11
|
+
import {
|
|
12
|
+
createWorkingLocationDisplayLabels,
|
|
13
|
+
getWorkingLocationDetail,
|
|
14
|
+
getWorkingLocationEditableLabel,
|
|
15
|
+
getWorkingLocationLabel,
|
|
16
|
+
getWorkingLocationType,
|
|
17
|
+
type WorkingLocationKind,
|
|
18
|
+
type WorkingLocationSelection,
|
|
19
|
+
} from "@/lib/working-location";
|
|
20
|
+
|
|
21
|
+
interface WorkingLocationEditorProps {
|
|
22
|
+
event: CalendarEvent;
|
|
23
|
+
isRecurring: boolean;
|
|
24
|
+
readOnly?: boolean;
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
onSave: (selection: WorkingLocationSelection) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type LabeledWorkingLocation = Exclude<WorkingLocationKind, "homeOffice">;
|
|
30
|
+
|
|
31
|
+
function initialLabels(event: CalendarEvent) {
|
|
32
|
+
const type = getWorkingLocationType(event);
|
|
33
|
+
const editableLabel = getWorkingLocationEditableLabel(event);
|
|
34
|
+
return {
|
|
35
|
+
officeLocation: type === "officeLocation" ? editableLabel : "",
|
|
36
|
+
customLocation: type === "customLocation" ? editableLabel : "",
|
|
37
|
+
} satisfies Record<LabeledWorkingLocation, string>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function WorkingLocationEditor({
|
|
41
|
+
event,
|
|
42
|
+
isRecurring,
|
|
43
|
+
readOnly = false,
|
|
44
|
+
disabled = false,
|
|
45
|
+
onSave,
|
|
46
|
+
}: WorkingLocationEditorProps) {
|
|
47
|
+
const t = useT();
|
|
48
|
+
const workingLocationLabels = createWorkingLocationDisplayLabels(t);
|
|
49
|
+
const currentType = getWorkingLocationType(event);
|
|
50
|
+
const currentLabel = getWorkingLocationEditableLabel(event);
|
|
51
|
+
const [type, setType] = useState<WorkingLocationKind>(currentType);
|
|
52
|
+
const [labels, setLabels] = useState(() => initialLabels(event));
|
|
53
|
+
const [scope, setScope] = useState<"single" | "all">("single");
|
|
54
|
+
const detail = getWorkingLocationDetail(event, workingLocationLabels);
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
setType(getWorkingLocationType(event));
|
|
58
|
+
setLabels(initialLabels(event));
|
|
59
|
+
setScope("single");
|
|
60
|
+
}, [currentLabel, currentType, event.id]);
|
|
61
|
+
|
|
62
|
+
const label = type === "homeOffice" ? "" : labels[type];
|
|
63
|
+
const isValid = type === "homeOffice" || label.trim().length > 0;
|
|
64
|
+
const isDirty = useMemo(
|
|
65
|
+
() => type !== currentType || label.trim() !== currentLabel.trim(),
|
|
66
|
+
[currentLabel, currentType, label, type],
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (readOnly) {
|
|
70
|
+
return (
|
|
71
|
+
<div className="flex items-start gap-3 py-1.5">
|
|
72
|
+
<IconMapPin className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
|
|
73
|
+
<div className="min-w-0">
|
|
74
|
+
<div className="truncate text-sm text-foreground">
|
|
75
|
+
{getWorkingLocationLabel(event, workingLocationLabels)}
|
|
76
|
+
</div>
|
|
77
|
+
<div className="truncate text-xs text-muted-foreground">
|
|
78
|
+
{detail || t("eventForm.workingLocation")}
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const options: Array<{
|
|
86
|
+
value: WorkingLocationKind;
|
|
87
|
+
label: string;
|
|
88
|
+
icon: typeof IconHome;
|
|
89
|
+
}> = [
|
|
90
|
+
{ value: "homeOffice", label: t("eventForm.home"), icon: IconHome },
|
|
91
|
+
{
|
|
92
|
+
value: "officeLocation",
|
|
93
|
+
label: t("eventForm.office"),
|
|
94
|
+
icon: IconBuilding,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
value: "customLocation",
|
|
98
|
+
label: t("eventForm.other"),
|
|
99
|
+
icon: IconMapPin,
|
|
100
|
+
},
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<div className="py-1.5" data-working-location-editor>
|
|
105
|
+
<div className="mb-2 flex items-center gap-2 text-sm font-medium text-foreground">
|
|
106
|
+
<IconMapPin className="size-4 text-muted-foreground" />
|
|
107
|
+
{t("eventForm.workingLocation")}
|
|
108
|
+
</div>
|
|
109
|
+
<RadioGroup
|
|
110
|
+
value={type}
|
|
111
|
+
onValueChange={(value) => setType(value as WorkingLocationKind)}
|
|
112
|
+
className="grid grid-cols-3 gap-1.5"
|
|
113
|
+
aria-label={t("eventForm.workingLocation")}
|
|
114
|
+
disabled={disabled}
|
|
115
|
+
>
|
|
116
|
+
{options.map((option) => {
|
|
117
|
+
const Icon = option.icon;
|
|
118
|
+
const id = `working-location-${event.id}-${option.value}`;
|
|
119
|
+
return (
|
|
120
|
+
<div key={option.value} className="relative">
|
|
121
|
+
<RadioGroupItem
|
|
122
|
+
id={id}
|
|
123
|
+
value={option.value}
|
|
124
|
+
className="peer sr-only"
|
|
125
|
+
/>
|
|
126
|
+
<Label
|
|
127
|
+
htmlFor={id}
|
|
128
|
+
className={cn(
|
|
129
|
+
"flex h-8 cursor-pointer items-center justify-center gap-1 rounded-md border border-border px-2 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground peer-data-[state=checked]:border-foreground/40 peer-data-[state=checked]:bg-muted peer-data-[state=checked]:text-foreground",
|
|
130
|
+
"peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2",
|
|
131
|
+
disabled && "cursor-not-allowed opacity-50",
|
|
132
|
+
)}
|
|
133
|
+
>
|
|
134
|
+
<Icon className="size-3.5" />
|
|
135
|
+
<span className="truncate">{option.label}</span>
|
|
136
|
+
</Label>
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
})}
|
|
140
|
+
</RadioGroup>
|
|
141
|
+
|
|
142
|
+
{type !== "homeOffice" && (
|
|
143
|
+
<div className="mt-2">
|
|
144
|
+
<Input
|
|
145
|
+
value={labels[type]}
|
|
146
|
+
onChange={(event) =>
|
|
147
|
+
setLabels((current) => ({
|
|
148
|
+
...current,
|
|
149
|
+
[type]: event.target.value,
|
|
150
|
+
}))
|
|
151
|
+
}
|
|
152
|
+
aria-label={
|
|
153
|
+
type === "officeLocation"
|
|
154
|
+
? t("eventForm.office")
|
|
155
|
+
: t("eventForm.other")
|
|
156
|
+
}
|
|
157
|
+
placeholder={t("eventForm.addLocation")}
|
|
158
|
+
className="h-8 text-sm"
|
|
159
|
+
disabled={disabled}
|
|
160
|
+
/>
|
|
161
|
+
{type === "officeLocation" && detail && (
|
|
162
|
+
<p className="mt-1 truncate text-xs text-muted-foreground">
|
|
163
|
+
{detail}
|
|
164
|
+
</p>
|
|
165
|
+
)}
|
|
166
|
+
</div>
|
|
167
|
+
)}
|
|
168
|
+
|
|
169
|
+
{isRecurring && (
|
|
170
|
+
<div className="mt-2">
|
|
171
|
+
<p className="mb-1 text-[10px] font-medium text-muted-foreground">
|
|
172
|
+
{t("eventForm.applyTo")}
|
|
173
|
+
</p>
|
|
174
|
+
<RadioGroup
|
|
175
|
+
value={scope}
|
|
176
|
+
onValueChange={(value) => setScope(value as "single" | "all")}
|
|
177
|
+
className="grid grid-cols-2 gap-1.5"
|
|
178
|
+
aria-label={t("eventForm.applyTo")}
|
|
179
|
+
disabled={disabled}
|
|
180
|
+
>
|
|
181
|
+
{(
|
|
182
|
+
[
|
|
183
|
+
["single", t("eventForm.thisDayOnly")],
|
|
184
|
+
["all", t("eventForm.allDays")],
|
|
185
|
+
] as const
|
|
186
|
+
).map(([value, text]) => {
|
|
187
|
+
const id = `working-location-scope-${event.id}-${value}`;
|
|
188
|
+
return (
|
|
189
|
+
<div key={value} className="relative">
|
|
190
|
+
<RadioGroupItem
|
|
191
|
+
id={id}
|
|
192
|
+
value={value}
|
|
193
|
+
className="peer sr-only"
|
|
194
|
+
/>
|
|
195
|
+
<Label
|
|
196
|
+
htmlFor={id}
|
|
197
|
+
className="flex h-7 cursor-pointer items-center justify-center rounded-md border border-border px-2 text-center text-xs font-medium text-muted-foreground transition-colors hover:bg-muted/50 peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2 peer-data-[state=checked]:border-foreground/40 peer-data-[state=checked]:bg-muted peer-data-[state=checked]:text-foreground"
|
|
198
|
+
>
|
|
199
|
+
{text}
|
|
200
|
+
</Label>
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
203
|
+
})}
|
|
204
|
+
</RadioGroup>
|
|
205
|
+
</div>
|
|
206
|
+
)}
|
|
207
|
+
|
|
208
|
+
<div className="mt-2 flex justify-end">
|
|
209
|
+
<Button
|
|
210
|
+
size="sm"
|
|
211
|
+
className="h-7 text-xs"
|
|
212
|
+
disabled={disabled || !isValid || !isDirty}
|
|
213
|
+
onClick={() =>
|
|
214
|
+
onSave({ type, label, scope: isRecurring ? scope : undefined })
|
|
215
|
+
}
|
|
216
|
+
>
|
|
217
|
+
{t("eventForm.save")}
|
|
218
|
+
</Button>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
@@ -11,6 +11,12 @@ import {
|
|
|
11
11
|
} from "@tanstack/react-query";
|
|
12
12
|
import { nanoid } from "nanoid";
|
|
13
13
|
|
|
14
|
+
import {
|
|
15
|
+
buildWorkingLocationProperties,
|
|
16
|
+
getWorkingLocationEditableLabel,
|
|
17
|
+
getWorkingLocationType,
|
|
18
|
+
} from "@/lib/working-location";
|
|
19
|
+
|
|
14
20
|
import {
|
|
15
21
|
applyCalendarEventRsvp,
|
|
16
22
|
calendarEventOverlapsListParams,
|
|
@@ -37,6 +43,8 @@ type UpdateEventInput = Partial<CalendarEvent> & {
|
|
|
37
43
|
sendUpdates?: "all" | "none";
|
|
38
44
|
notificationMessage?: string;
|
|
39
45
|
scope?: UpdateEventScope;
|
|
46
|
+
workingLocationType?: "homeOffice" | "officeLocation" | "customLocation";
|
|
47
|
+
workingLocationLabel?: string;
|
|
40
48
|
};
|
|
41
49
|
|
|
42
50
|
type EventListSnapshot = Array<[QueryKey, CalendarEvent[] | undefined]>;
|
|
@@ -48,6 +56,14 @@ type RsvpEventMutationContext = EventListMutationContext & {
|
|
|
48
56
|
previousEvent?: CalendarEvent;
|
|
49
57
|
};
|
|
50
58
|
|
|
59
|
+
type UpdateEventResult = Partial<CalendarEvent> & {
|
|
60
|
+
id?: string;
|
|
61
|
+
replacedId?: string;
|
|
62
|
+
success?: boolean;
|
|
63
|
+
updated?: string[];
|
|
64
|
+
message?: string;
|
|
65
|
+
};
|
|
66
|
+
|
|
51
67
|
const LIST_EVENTS_QUERY_KEY = ["action", "list-events"] as const;
|
|
52
68
|
const OPTIMISTIC_EVENT_PREFIX = "optimistic_event_";
|
|
53
69
|
|
|
@@ -148,6 +164,16 @@ export function mergeAttendeeLists(
|
|
|
148
164
|
return Array.from(merged.values());
|
|
149
165
|
}
|
|
150
166
|
|
|
167
|
+
export function shouldDeferOptimisticEventUpdate(
|
|
168
|
+
target: CalendarEvent | undefined,
|
|
169
|
+
hasWorkingLocationUpdate: boolean,
|
|
170
|
+
): boolean {
|
|
171
|
+
return (
|
|
172
|
+
hasWorkingLocationUpdate ||
|
|
173
|
+
(target?.eventType === "workingLocation" && target.allDay === true)
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
151
177
|
function updateListEventQueries(
|
|
152
178
|
queryClient: ReturnType<typeof useQueryClient>,
|
|
153
179
|
updater: (
|
|
@@ -293,90 +319,136 @@ export function useCreateEvent() {
|
|
|
293
319
|
|
|
294
320
|
export function useUpdateEvent() {
|
|
295
321
|
const queryClient = useQueryClient();
|
|
296
|
-
return useActionMutation<
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
old
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
322
|
+
return useActionMutation<UpdateEventResult, UpdateEventInput>(
|
|
323
|
+
"update-event",
|
|
324
|
+
{
|
|
325
|
+
onMutate: async (newData) => {
|
|
326
|
+
await queryClient.cancelQueries({
|
|
327
|
+
queryKey: ["action", "list-events"],
|
|
328
|
+
});
|
|
329
|
+
const previous = queryClient.getQueriesData<CalendarEvent[]>({
|
|
330
|
+
queryKey: ["action", "list-events"],
|
|
331
|
+
});
|
|
332
|
+
const {
|
|
333
|
+
addGoogleMeet,
|
|
334
|
+
addZoom,
|
|
335
|
+
addAttendees,
|
|
336
|
+
sendUpdates,
|
|
337
|
+
notificationMessage,
|
|
338
|
+
scope,
|
|
339
|
+
workingLocationType,
|
|
340
|
+
workingLocationLabel,
|
|
341
|
+
...optimisticData
|
|
342
|
+
} = newData;
|
|
343
|
+
const hasWorkingLocationUpdate =
|
|
344
|
+
workingLocationType !== undefined ||
|
|
345
|
+
workingLocationLabel !== undefined;
|
|
346
|
+
queryClient.setQueriesData<CalendarEvent[]>(
|
|
347
|
+
{ queryKey: ["action", "list-events"] },
|
|
348
|
+
(old) => {
|
|
349
|
+
const target = old?.find((event) => event.id === optimisticData.id);
|
|
350
|
+
// A working-location change can alter the visual grouping of several
|
|
351
|
+
// days. Keep the editor mounted until Google confirms the write so a
|
|
352
|
+
// rejected request does not appear to save and then snap back.
|
|
353
|
+
if (
|
|
354
|
+
shouldDeferOptimisticEventUpdate(target, hasWorkingLocationUpdate)
|
|
355
|
+
)
|
|
356
|
+
return old;
|
|
357
|
+
return old?.map((e) => {
|
|
358
|
+
const matchesScope =
|
|
359
|
+
e.id === optimisticData.id ||
|
|
360
|
+
(scope === "all" &&
|
|
361
|
+
!!target?.recurringEventId &&
|
|
362
|
+
e.recurringEventId === target.recurringEventId);
|
|
363
|
+
if (!matchesScope) return e;
|
|
364
|
+
const nextWorkingLocationType =
|
|
365
|
+
workingLocationType ?? getWorkingLocationType(e);
|
|
366
|
+
const nextWorkingLocationLabel =
|
|
367
|
+
workingLocationLabel ?? getWorkingLocationEditableLabel(e);
|
|
368
|
+
return {
|
|
369
|
+
...e,
|
|
370
|
+
...optimisticData,
|
|
371
|
+
...(hasWorkingLocationUpdate
|
|
372
|
+
? {
|
|
373
|
+
workingLocationProperties: buildWorkingLocationProperties(
|
|
374
|
+
e,
|
|
375
|
+
{
|
|
376
|
+
type: nextWorkingLocationType,
|
|
377
|
+
label: nextWorkingLocationLabel,
|
|
378
|
+
},
|
|
379
|
+
),
|
|
380
|
+
}
|
|
381
|
+
: {}),
|
|
382
|
+
...(addAttendees
|
|
383
|
+
? {
|
|
384
|
+
attendees: mergeAttendeeLists(e.attendees, addAttendees),
|
|
385
|
+
}
|
|
386
|
+
: {}),
|
|
387
|
+
};
|
|
388
|
+
});
|
|
389
|
+
},
|
|
390
|
+
);
|
|
391
|
+
return { previous };
|
|
392
|
+
},
|
|
393
|
+
onSuccess: (updated, input) => {
|
|
394
|
+
const eventPatch = updated as UpdateEventResult | undefined;
|
|
395
|
+
if (!eventPatch?.id) return;
|
|
396
|
+
queryClient.setQueriesData<CalendarEvent[]>(
|
|
397
|
+
{ queryKey: ["action", "list-events"] },
|
|
398
|
+
(old) => reconcileUpdatedEventList(old, input.id, eventPatch),
|
|
399
|
+
);
|
|
400
|
+
},
|
|
401
|
+
onError: (_err, _newData, context) => {
|
|
402
|
+
const previous = (context as EventListMutationContext | undefined)
|
|
403
|
+
?.previous;
|
|
404
|
+
if (previous) {
|
|
405
|
+
for (const [key, data] of previous) {
|
|
406
|
+
queryClient.setQueryData(key, data);
|
|
407
|
+
}
|
|
371
408
|
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
409
|
+
},
|
|
410
|
+
onSettled: () => {
|
|
411
|
+
queryClient.invalidateQueries({ queryKey: ["action", "list-events"] });
|
|
412
|
+
},
|
|
376
413
|
},
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export function reconcileUpdatedEventList(
|
|
418
|
+
events: CalendarEvent[] | undefined,
|
|
419
|
+
originalId: string,
|
|
420
|
+
result: UpdateEventResult,
|
|
421
|
+
): CalendarEvent[] | undefined {
|
|
422
|
+
if (!result.id) return events;
|
|
423
|
+
const {
|
|
424
|
+
success: _success,
|
|
425
|
+
updated: _updated,
|
|
426
|
+
message: _message,
|
|
427
|
+
replacedId,
|
|
428
|
+
...eventPatch
|
|
429
|
+
} = result;
|
|
430
|
+
const targetId = replacedId ?? originalId;
|
|
431
|
+
|
|
432
|
+
return events?.map((event) => {
|
|
433
|
+
if (event.id !== targetId) return event;
|
|
434
|
+
const idChanged = event.id !== eventPatch.id;
|
|
435
|
+
return {
|
|
436
|
+
...event,
|
|
437
|
+
...eventPatch,
|
|
438
|
+
...(idChanged ? { _replacedId: event.id } : {}),
|
|
439
|
+
};
|
|
377
440
|
});
|
|
378
441
|
}
|
|
379
442
|
|
|
443
|
+
export function findEventByCurrentOrReplacedId(
|
|
444
|
+
events: CalendarEvent[],
|
|
445
|
+
eventId: string,
|
|
446
|
+
): CalendarEvent | undefined {
|
|
447
|
+
return events.find(
|
|
448
|
+
(event) => event.id === eventId || event._replacedId === eventId,
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
380
452
|
export function useDeleteEvent() {
|
|
381
453
|
const queryClient = useQueryClient();
|
|
382
454
|
return useActionMutation<
|
|
@@ -672,6 +672,7 @@ const messages = {
|
|
|
672
672
|
addingMeetingLink: "新增 {{provider}} 連結",
|
|
673
673
|
alerts: "警報",
|
|
674
674
|
allDay: "全天",
|
|
675
|
+
allDays: "所有日期",
|
|
675
676
|
allEvents: "所有活動",
|
|
676
677
|
applyChangesTo: "將更改應用到",
|
|
677
678
|
applyTo: "適用於",
|
|
@@ -752,6 +753,7 @@ const messages = {
|
|
|
752
753
|
notifyGuestsQuestion: "通知來賓?",
|
|
753
754
|
none: "無",
|
|
754
755
|
office: "辦公室",
|
|
756
|
+
other: "其他",
|
|
755
757
|
openInSidebar: "在側邊欄中開啟",
|
|
756
758
|
openInGoogleCalendar: "在 Google Calendar 中開啟",
|
|
757
759
|
optionalDescription: "可選描述",
|
|
@@ -803,6 +805,7 @@ const messages = {
|
|
|
803
805
|
start: "開始",
|
|
804
806
|
startDate: "開始日期",
|
|
805
807
|
subscribedFeeds: "已訂閱的來源",
|
|
808
|
+
thisDayOnly: "僅限當天",
|
|
806
809
|
thisEvent: "本次活動",
|
|
807
810
|
timeSelected: "選定時間",
|
|
808
811
|
timezone: "時區",
|
|
@@ -826,6 +829,9 @@ const messages = {
|
|
|
826
829
|
weekly: "每週",
|
|
827
830
|
workingFrom: "工作自",
|
|
828
831
|
workingLocation: "工作地點",
|
|
832
|
+
workingLocationDesk: "座位 {{desk}}",
|
|
833
|
+
workingLocationFloor: "樓層 {{floor}}",
|
|
834
|
+
workingLocationTitle: "工作地點:{{location}}",
|
|
829
835
|
yearly: "每年",
|
|
830
836
|
zoom: "Zoom",
|
|
831
837
|
zoomAdded: "新增Zoom",
|