import "root:/widgets" import "root:/services" import "root:/config" import QtQuick Column { id: root spacing: Appearance.spacing.normal width: 250 property date currentDate: new Date() property bool isCurrentMonth: true property string currentTime: Qt.formatDateTime(new Date(), "HH:mm:ss") property int currentYear: new Date().getFullYear() // Update time every second Timer { interval: 1000 running: true repeat: true onTriggered: { root.currentTime = Qt.formatDateTime(new Date(), "HH:mm:ss") } } // Time and Date header Column { anchors.horizontalCenter: parent.horizontalCenter spacing: Appearance.spacing.small StyledText { anchors.horizontalCenter: parent.horizontalCenter text: root.currentTime font.pointSize: Appearance.font.size.larger font.family: Appearance.font.family.mono } Row { anchors.horizontalCenter: parent.horizontalCenter spacing: Appearance.spacing.small Row { spacing: Appearance.spacing.small MaterialIcon { text: "chevron_left" color: Colours.palette.m3onSurfaceVariant MouseArea { anchors.fill: parent onClicked: { root.currentYear-- root.currentDate = new Date(root.currentYear, monthView.currentIndex, 1) } } } StyledText { text: root.currentYear font.pointSize: Appearance.font.size.large font.weight: 700 } MaterialIcon { text: "chevron_right" color: Colours.palette.m3onSurfaceVariant MouseArea { anchors.fill: parent onClicked: { root.currentYear++ root.currentDate = new Date(root.currentYear, monthView.currentIndex, 1) } } } } Row { spacing: Appearance.spacing.small MaterialIcon { text: "chevron_left" color: Colours.palette.m3onSurfaceVariant MouseArea { anchors.fill: parent onClicked: { monthView.decrementCurrentIndex() } } } StyledText { text: Qt.formatDateTime(root.currentDate, "MMMM") font.pointSize: Appearance.font.size.large font.weight: 400 color: Colours.palette.m3onSurfaceVariant } MaterialIcon { text: "chevron_right" color: Colours.palette.m3onSurfaceVariant MouseArea { anchors.fill: parent onClicked: { monthView.incrementCurrentIndex() } } } } } } // Calendar grid Rectangle { width: 280 height: 250 anchors.horizontalCenter: parent.horizontalCenter color: "transparent" border.color: Colours.palette.m3outline border.width: 1 radius: 8 ListView { id: monthView anchors.fill: parent anchors.margins: 12 orientation: ListView.Horizontal snapMode: ListView.SnapOneItem highlightRangeMode: ListView.StrictlyEnforceRange highlightMoveDuration: Appearance.anim.durations.normal highlightMoveVelocity: -1 highlightResizeDuration: Appearance.anim.durations.normal highlightResizeVelocity: -1 model: 12 // Show all months currentIndex: new Date().getMonth() delegate: Grid { width: monthView.width height: monthView.height columns: 7 spacing: Appearance.spacing.small // Day headers Repeater { model: ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"] StyledText { width: 30 horizontalAlignment: Text.AlignHCenter text: modelData font.pointSize: Appearance.font.size.small color: Colours.palette.m3onSurfaceVariant } } // Calendar days Repeater { model: { const firstDay = new Date(root.currentYear, monthView.currentIndex, 1); const lastDay = new Date(root.currentYear, monthView.currentIndex + 1, 0); const daysInMonth = lastDay.getDate(); const firstDayOfWeek = firstDay.getDay() || 7; // Convert Sunday (0) to 7 const today = new Date(); let days = []; // Add previous month's days const prevMonthLastDay = new Date(root.currentYear, monthView.currentIndex, 0).getDate(); for (let i = firstDayOfWeek - 1; i > 0; i--) { days.push({ day: prevMonthLastDay - i + 1, isCurrentMonth: false, isNextMonth: false }); } // Add days of the current month for (let i = 1; i <= daysInMonth; i++) { const isToday = i === today.getDate() && monthView.currentIndex === today.getMonth() && root.currentYear === today.getFullYear(); days.push({ day: i, isCurrentMonth: true, isToday: isToday, isNextMonth: false }); } // Add next month's days const remainingCells = Math.ceil((firstDayOfWeek - 1 + daysInMonth) / 7) * 7 - days.length; for (let i = 1; i <= remainingCells; i++) { days.push({ day: i, isCurrentMonth: false, isNextMonth: true }); } return days; } Rectangle { width: 30 height: 30 radius: Appearance.rounding.full color: modelData.isToday ? Colours.palette.m3primary : "transparent" StyledText { anchors.centerIn: parent text: modelData.day font.pointSize: Appearance.font.size.small font.family: Appearance.font.family.mono color: modelData.isToday ? Colours.palette.m3onPrimary : modelData.isCurrentMonth ? Colours.palette.m3onSurface : modelData.isNextMonth ? "#404040" : "#505050" } } } } onCurrentIndexChanged: { root.currentDate = new Date(root.currentYear, currentIndex, 1) root.isCurrentMonth = currentIndex === new Date().getMonth() && root.currentYear === new Date().getFullYear() } } } // Reset to current month when popout closes Connections { target: root.parent function onVisibleChanged() { if (!root.parent.visible) { monthView.currentIndex = new Date().getMonth() root.currentYear = new Date().getFullYear() root.currentDate = new Date() root.isCurrentMonth = true } } } }