feat: P2.3 Folder Sharing + P2.5 Email Import + P2.6 Contact Import + P2.7 Free/Busy
- P2.3: Folder sharing system — ShareFolderDialog, sharing-store, sharing-settings - P2.5: Email import (.eml, .tgz, .zip) with dedup and progress - P2.6: Contact import (vCard + CSV) with auto-mapping - P2.7: Free/Busy view grid with color-coded slots
This commit is contained in:
@@ -4,13 +4,14 @@ import { useState, useEffect, useCallback, useRef, useMemo } from "react";
|
||||
import { useTranslations, useLocale } from "next-intl";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { X, Trash2, Check, Users, CalendarDays, Copy, Pencil, Clock, MapPin, Video, Repeat, Bell, AlignLeft, Plus } from "lucide-react";
|
||||
import { X, Trash2, Check, Users, CalendarDays, Copy, Pencil, Clock, MapPin, Video, Repeat, Bell, AlignLeft, Plus, Eye, EyeOff } from "lucide-react";
|
||||
import { format, parseISO, addHours, addDays, isSameDay } from "date-fns";
|
||||
import type { CalendarEvent, Calendar, CalendarParticipant, CalendarEventAlert, CalendarRecurrenceRule } from "@/lib/jmap/types";
|
||||
import { RecurrenceEditor, buildRecurrenceSummary, isSimpleRecurrenceRule } from "./recurrence-editor";
|
||||
import { parseDuration, getEventColor } from "./event-card";
|
||||
import { buildAllDayDuration, getEventDisplayEndDate, getEventEndDate, getEventStartDate, getPrimaryCalendarId } from "@/lib/calendar-utils";
|
||||
import { ParticipantInput, type ParticipantInputHandle } from "./participant-input";
|
||||
import { FreeBusyView } from "./free-busy-view";
|
||||
import {
|
||||
isOrganizer,
|
||||
getUserParticipantId,
|
||||
@@ -351,6 +352,7 @@ export function EventModal({
|
||||
.map(p => ({ name: p.name, email: p.email }));
|
||||
});
|
||||
const [sendInvitations, setSendInvitations] = useState(true);
|
||||
const [showFreeBusy, setShowFreeBusy] = useState(false);
|
||||
const participantInputRef = useRef<ParticipantInputHandle>(null);
|
||||
|
||||
// Plugin transform: collect conflict warnings for the current event form.
|
||||
@@ -1074,6 +1076,44 @@ export function EventModal({
|
||||
onAdd={handleAddAttendee}
|
||||
onRemove={handleRemoveAttendee}
|
||||
/>
|
||||
{attendees.length > 0 && !allDay && (
|
||||
<div className="mt-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setShowFreeBusy((prev) => !prev)}
|
||||
className="text-xs"
|
||||
>
|
||||
{showFreeBusy ? (
|
||||
<EyeOff className="w-3.5 h-3.5 me-1" />
|
||||
) : (
|
||||
<Eye className="w-3.5 h-3.5 me-1" />
|
||||
)}
|
||||
{showFreeBusy ? t("freeBusy.hide") : t("freeBusy.check")}
|
||||
</Button>
|
||||
{showFreeBusy && (
|
||||
<div className="mt-3">
|
||||
<FreeBusyView
|
||||
participants={attendees}
|
||||
startDate={(() => {
|
||||
const d = new Date(`${startDate}T${startTime}:00`);
|
||||
return isNaN(d.getTime()) ? new Date() : d;
|
||||
})()}
|
||||
endDate={(() => {
|
||||
const d = new Date(`${endDate}T${endTime}:00`);
|
||||
return isNaN(d.getTime()) ? addHours(new Date(`${startDate}T${startTime}:00`), 8) : d;
|
||||
})()}
|
||||
onTimeSelect={(start, end) => {
|
||||
setStartDate(formatDateInput(start));
|
||||
setStartTime(formatTimeInput(start));
|
||||
setEndDate(formatDateInput(end));
|
||||
setEndTime(formatTimeInput(end));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{isEdit && statusCounts && (existingParticipants.length > 0) && (
|
||||
<p className="text-xs text-muted-foreground mt-1.5">
|
||||
{t("participants.status_summary", {
|
||||
|
||||
Reference in New Issue
Block a user