import QtQuick import qs.Common import qs.Services import qs.Widgets Item { id: root LayoutMirroring.enabled: I18n.isRtl LayoutMirroring.childrenInherit: true property var eventData: null property date initialDate: new Date() signal saved signal closeRequested property string fTitle: "" property bool fAllDay: false property date fDate: initialDate property string fStart: "10:00" property string fEnd: "11:00" property string fLocation: "" property string fDescription: "" property string fCalendarId: "" property int fReminder: -1 property string errorText: "" property bool saving: false readonly property var _cals: CalendarService.writableCalendars() readonly property var _remLabels: [I18n.tr("No reminder"), I18n.tr("At start"), I18n.tr("5 min before"), I18n.tr("10 min before"), I18n.tr("15 min before"), I18n.tr("30 min before"), I18n.tr("1 hour before"), I18n.tr("1 day before")] readonly property var _remMins: [-1, 0, 5, 10, 15, 30, 60, 1440] function _parseTime(value) { const m = value.trim().match(/^(\d{1,2}):(\d{2})$/); if (!m) return null; const h = parseInt(m[1]); const min = parseInt(m[2]); if (h > 23 || min > 59) return null; return { "h": h, "m": min }; } function _isoFromDateTime(dateObj, h, m) { const d = new Date(dateObj); d.setHours(h, m, 0, 0); return d.toISOString(); } function _allDayIso(dateObj, dayOffset) { return new Date(Date.UTC(dateObj.getFullYear(), dateObj.getMonth(), dateObj.getDate() + dayOffset)).toISOString(); } function _calendarName(id) { for (let i = 0; i < _cals.length; i++) { if (_cals[i].id === id) return _cals[i].name; } return _cals.length > 0 ? _cals[0].name : ""; } function save() { const title = fTitle.trim(); if (!title) { errorText = I18n.tr("Title is required"); return; } let calId = fCalendarId; if (!calId) { const def = CalendarService.defaultCalendar(); calId = def ? def.id : ""; } if (!calId) { errorText = I18n.tr("No writable calendar available"); return; } let startIso, endIso; if (fAllDay) { startIso = _allDayIso(fDate, 0); endIso = _allDayIso(fDate, 1); } else { const s = _parseTime(fStart); const e = _parseTime(fEnd); if (!s || !e) { errorText = I18n.tr("Use HH:MM time format"); return; } startIso = _isoFromDateTime(fDate, s.h, s.m); endIso = _isoFromDateTime(fDate, e.h, e.m); if (new Date(endIso).getTime() <= new Date(startIso).getTime()) { errorText = I18n.tr("End must be after start"); return; } } const fields = { "calendarId": calId, "summary": title, "description": fDescription, "location": fLocation, "start": startIso, "end": endIso, "allDay": fAllDay, "reminders": fReminder >= 0 ? [ { "method": "popup", "minutes": fReminder } ] : [] }; saving = true; errorText = ""; const cb = response => { saving = false; if (response.error) { errorText = response.error; return; } root.saved(); }; if (eventData && eventData.id) CalendarService.updateEvent(eventData.id, fields, cb); else CalendarService.createEvent(fields, cb); } Component.onCompleted: { if (!eventData) { fCalendarId = CalendarService.defaultCalendar() ? CalendarService.defaultCalendar().id : ""; return; } fTitle = eventData.title || ""; fAllDay = !!eventData.allDay; fDate = eventData.start; const fmt = "HH:mm"; fStart = Qt.formatTime(eventData.start, fmt); fEnd = Qt.formatTime(eventData.end, fmt); fLocation = eventData.location || ""; fDescription = eventData.description || ""; fCalendarId = eventData.calendarId || ""; if (eventData.reminders && eventData.reminders.length > 0) fReminder = eventData.reminders[0].minutes; } Rectangle { anchors.fill: parent radius: Theme.cornerRadius color: Qt.rgba(0, 0, 0, 0.45) MouseArea { anchors.fill: parent onClicked: root.closeRequested() } } Rectangle { anchors.centerIn: parent width: Math.min(parent.width - Theme.spacingL * 2, 400) height: Math.min(parent.height - Theme.spacingM, 300) radius: Theme.cornerRadius color: Theme.surfaceContainerHigh border.color: Theme.outlineMedium border.width: 1 MouseArea { anchors.fill: parent } DankFlickable { anchors.fill: parent anchors.margins: Theme.spacingM contentWidth: width contentHeight: form.implicitHeight clip: true Column { id: form width: parent.width spacing: Theme.spacingS StyledText { width: parent.width text: root.eventData ? I18n.tr("Edit event") : I18n.tr("New event") font.pixelSize: Theme.fontSizeMedium font.weight: Font.Medium color: Theme.surfaceText horizontalAlignment: Text.AlignLeft } DankTextField { width: parent.width labelText: I18n.tr("Title") leftIconName: "title" leftIconSize: Theme.iconSize - 6 placeholderText: I18n.tr("Event title") text: root.fTitle onTextChanged: root.fTitle = text } DankToggle { width: parent.width text: I18n.tr("All day") checked: root.fAllDay onToggled: checked => root.fAllDay = checked } Row { width: parent.width spacing: Theme.spacingXS DankActionButton { circular: false iconName: "chevron_left" iconSize: 16 onClicked: { let d = new Date(root.fDate); d.setDate(d.getDate() - 1); root.fDate = d; } } StyledText { width: parent.width - 72 text: Qt.formatDate(root.fDate, "ddd, MMM d yyyy") font.pixelSize: Theme.fontSizeSmall color: Theme.surfaceText horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter height: 32 } DankActionButton { circular: false iconName: "chevron_right" iconSize: 16 onClicked: { let d = new Date(root.fDate); d.setDate(d.getDate() + 1); root.fDate = d; } } } Row { width: parent.width spacing: Theme.spacingS visible: !root.fAllDay DankTextField { width: (parent.width - Theme.spacingS) / 2 labelText: I18n.tr("Start") leftIconName: "schedule" leftIconSize: Theme.iconSize - 6 placeholderText: "HH:MM" text: root.fStart onTextChanged: root.fStart = text } DankTextField { width: (parent.width - Theme.spacingS) / 2 labelText: I18n.tr("End") placeholderText: "HH:MM" text: root.fEnd onTextChanged: root.fEnd = text } } DankDropdown { width: parent.width text: I18n.tr("Calendar") options: root._cals.map(c => c.name) currentValue: root._calendarName(root.fCalendarId) onValueChanged: value => { for (let i = 0; i < root._cals.length; i++) { if (root._cals[i].name === value) { root.fCalendarId = root._cals[i].id; return; } } } } DankDropdown { width: parent.width text: I18n.tr("Reminder") options: root._remLabels currentValue: root._remLabels[Math.max(0, root._remMins.indexOf(root.fReminder))] onValueChanged: value => { const idx = root._remLabels.indexOf(value); if (idx >= 0) root.fReminder = root._remMins[idx]; } } DankTextField { width: parent.width labelText: I18n.tr("Location") leftIconName: "place" leftIconSize: Theme.iconSize - 6 placeholderText: I18n.tr("Add location") text: root.fLocation onTextChanged: root.fLocation = text } DankTextField { width: parent.width labelText: I18n.tr("Notes") leftIconName: "notes" leftIconSize: Theme.iconSize - 6 placeholderText: I18n.tr("Add notes") text: root.fDescription onTextChanged: root.fDescription = text } StyledText { width: parent.width text: root.errorText visible: root.errorText !== "" font.pixelSize: Theme.fontSizeSmall color: Theme.error wrapMode: Text.WordWrap } Row { width: parent.width spacing: Theme.spacingS DankButton { text: root.saving ? I18n.tr("Saving…") : I18n.tr("Save") iconName: "check" buttonHeight: 32 backgroundColor: Theme.primary textColor: Theme.primaryText enabled: !root.saving onClicked: root.save() } DankButton { text: I18n.tr("Cancel") buttonHeight: 32 onClicked: root.closeRequested() } } } } } }