From eaf4d6b0856e949edd1d852f1f78325313e33d6a Mon Sep 17 00:00:00 2001 From: Mathias Date: Fri, 18 Jul 2025 13:12:26 +0200 Subject: [PATCH] Improve mobile calendar --- mobile-calendar/src/App.jsx | 77 +++-- mobile-calendar/src/App.sass | 530 ++++++++++++++++++++++++++++------- 2 files changed, 483 insertions(+), 124 deletions(-) diff --git a/mobile-calendar/src/App.jsx b/mobile-calendar/src/App.jsx index 18978b6..8e1f364 100644 --- a/mobile-calendar/src/App.jsx +++ b/mobile-calendar/src/App.jsx @@ -121,12 +121,19 @@ const App = () => { }; const formatDate = (dateString) => { - const date = new Date(dateString); - return date.toLocaleDateString('de-DE', { - weekday: 'short', - day: '2-digit', - month: '2-digit' - }); + // Parse the date string ensuring we don't have timezone issues + const dateParts = dateString.split('-'); + const date = new Date(parseInt(dateParts[0]), parseInt(dateParts[1]) - 1, parseInt(dateParts[2])); + const today = new Date(); + today.setHours(0, 0, 0, 0); // Reset time for comparison + + return { + dayNumber: date.getDate(), + dayName: date.toLocaleDateString('de-DE', { weekday: 'long' }), + monthYear: date.toLocaleDateString('de-DE', { month: 'long', year: 'numeric' }), + isToday: date.getTime() === today.getTime(), + isThisMonth: date.getMonth() === currentDate.getMonth() && date.getFullYear() === currentDate.getFullYear() + }; }; const getCurrentMonthYear = () => { @@ -320,29 +327,43 @@ const App = () => { ) : (
- {Object.entries(groupedEvents).map(([date, dayEvents]) => ( -
-
-

{formatDate(date)}

- {dayEvents.length} Termin{dayEvents.length !== 1 ? 'e' : ''} + {Object.entries(groupedEvents).map(([date, dayEvents]) => { + const dateInfo = formatDate(date); + return ( +
+
+
+
{dateInfo.dayNumber}
+
+
{dateInfo.dayName}
+ {!dateInfo.isThisMonth && ( +
{dateInfo.monthYear}
+ )} +
+
+
+ {dayEvents.length} + Termin{dayEvents.length !== 1 ? 'e' : ''} +
+
+
+ {dayEvents.map(event => ( + + ))} +
-
- {dayEvents.map(event => ( - - ))} -
-
- ))} + ); + })}
)}
diff --git a/mobile-calendar/src/App.sass b/mobile-calendar/src/App.sass index 28dcd82..0ea8478 100644 --- a/mobile-calendar/src/App.sass +++ b/mobile-calendar/src/App.sass @@ -51,13 +51,15 @@ body // Header .header - background: var(--surface-color) - border-bottom: 1px solid var(--border-color) + background: linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(248, 249, 250, 0.98) 100%) + backdrop-filter: blur(20px) + border-bottom: 1px solid rgba(255, 255, 255, 0.2) padding: var(--spacing-sm) var(--spacing-md) position: sticky top: 0 z-index: 100 - box-shadow: var(--shadow-light) + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1) + border-radius: 0 0 var(--border-radius) var(--border-radius) .header-content display: flex @@ -68,16 +70,18 @@ body h1 font-size: 1.5rem - font-weight: 600 + font-weight: 700 color: var(--text-primary) display: flex align-items: center gap: var(--spacing-sm) justify-content: center + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) svg color: var(--primary-color) font-size: 1.25rem + filter: drop-shadow(0 2px 4px rgba(0, 122, 255, 0.3)) .month-navigation display: flex @@ -92,21 +96,27 @@ body margin: 0 min-width: 140px text-align: center + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) .btn-icon - width: 40px - height: 40px + width: 44px + height: 44px border-radius: 50% display: flex align-items: center justify-content: center - background: var(--background-color) - border: 1px solid var(--border-color) + background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%) + border: none + color: white + transition: all 0.3s ease + box-shadow: 0 4px 12px rgba(0, 122, 255, 0.3) &:hover - background: var(--primary-color) - color: white - border-color: var(--primary-color) + transform: translateY(-2px) scale(1.05) + box-shadow: 0 6px 20px rgba(0, 122, 255, 0.4) + + &:active + transform: translateY(0) scale(0.95) .view-controls display: flex @@ -114,32 +124,56 @@ body justify-content: space-between gap: var(--spacing-md) padding-top: var(--spacing-sm) - border-top: 1px solid var(--border-color) + border-top: 1px solid rgba(255, 255, 255, 0.3) .view-type-toggle display: flex - gap: var(--spacing-xs) - background: var(--background-color) + gap: 4px + background: rgba(255, 255, 255, 0.7) padding: 4px - border-radius: var(--border-radius-small) + border-radius: var(--border-radius) + backdrop-filter: blur(10px) + border: 1px solid rgba(255, 255, 255, 0.3) + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1) .btn padding: var(--spacing-xs) var(--spacing-md) border-radius: var(--border-radius-small) font-size: 0.875rem + font-weight: 600 + transition: all 0.3s ease + border: none + + &.btn-primary + background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%) + color: white + box-shadow: 0 2px 8px rgba(0, 122, 255, 0.3) + + &.btn-secondary + background: transparent + color: var(--text-secondary) + + &:hover + background: rgba(255, 255, 255, 0.5) + color: var(--text-primary) .user-select - padding: var(--spacing-sm) - border: 1px solid var(--border-color) - border-radius: var(--border-radius-small) - background: var(--surface-color) + padding: var(--spacing-sm) var(--spacing-md) + border: 1px solid rgba(255, 255, 255, 0.3) + border-radius: var(--border-radius) + background: rgba(255, 255, 255, 0.7) + backdrop-filter: blur(10px) font-size: 0.875rem + font-weight: 500 min-width: 120px + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) + transition: all 0.3s ease &:focus outline: none border-color: var(--primary-color) - box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1) + box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.2) + background: rgba(255, 255, 255, 0.9) // Main Content .main @@ -152,32 +186,47 @@ body // Error Message .error-message - background: var(--danger-color) + background: linear-gradient(135deg, var(--danger-color) 0%, #dc3545 100%) color: white - padding: var(--spacing-md) - border-radius: var(--border-radius-small) + padding: var(--spacing-md) var(--spacing-lg) + border-radius: var(--border-radius) margin-bottom: var(--spacing-md) display: flex align-items: center justify-content: space-between font-weight: 500 + box-shadow: 0 4px 15px rgba(255, 59, 48, 0.3) + backdrop-filter: blur(10px) + border: 1px solid rgba(255, 255, 255, 0.1) + animation: slideInFromTop 0.3s ease button - background: none + background: rgba(255, 255, 255, 0.2) border: none color: white font-size: 1.25rem cursor: pointer padding: 0 - width: 24px - height: 24px + width: 28px + height: 28px border-radius: 50% display: flex align-items: center justify-content: center + transition: all 0.3s ease + backdrop-filter: blur(10px) &:hover - background: rgba(255, 255, 255, 0.1) + background: rgba(255, 255, 255, 0.3) + transform: scale(1.1) + +@keyframes slideInFromTop + from + opacity: 0 + transform: translateY(-10px) + to + opacity: 1 + transform: translateY(0) // Loading State .loading @@ -189,14 +238,24 @@ body color: var(--text-secondary) .loading-icon - font-size: 3rem + font-size: 3.5rem color: var(--primary-color) - margin-bottom: var(--spacing-md) - animation: pulse 2s infinite + margin-bottom: var(--spacing-lg) + animation: pulse-glow 2s infinite + filter: drop-shadow(0 0 20px rgba(0, 122, 255, 0.3)) p - font-size: 1.1rem - font-weight: 500 + font-size: 1.125rem + font-weight: 600 + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) + +@keyframes pulse-glow + 0%, 100% + opacity: 1 + transform: scale(1) + 50% + opacity: 0.7 + transform: scale(1.05) @keyframes pulse 0%, 100% @@ -204,6 +263,14 @@ body 50% opacity: 0.5 +@keyframes pulse-today + 0%, 100% + transform: scale(1) + box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.7) + 50% + transform: scale(1.05) + box-shadow: 0 0 0 10px rgba(255, 255, 255, 0) + // Calendar Content .calendar-content display: flex @@ -221,12 +288,19 @@ body border-radius: var(--border-radius) box-shadow: var(--shadow-light) text-align: center + border: 2px dashed var(--border-color) + transition: all 0.3s ease + + &:hover + border-color: var(--primary-color) + box-shadow: var(--shadow-medium) svg font-size: 3rem margin-bottom: var(--spacing-md) color: var(--primary-color) opacity: 0.5 + animation: float 3s ease-in-out infinite p font-size: 1.1rem @@ -238,6 +312,12 @@ body color: var(--text-secondary) line-height: 1.4 +@keyframes float + 0%, 100% + transform: translateY(0) + 50% + transform: translateY(-10px) + // Events List .events-list display: flex @@ -249,32 +329,120 @@ body border-radius: var(--border-radius) box-shadow: var(--shadow-light) overflow: hidden + transition: all 0.3s ease + border: 2px solid transparent + + &:hover + box-shadow: var(--shadow-medium) + transform: translateY(-2px) + + &.today + border-color: var(--primary-color) + box-shadow: 0 4px 20px rgba(0, 122, 255, 0.2) + + .day-header + background: linear-gradient(135deg, var(--primary-color) 0%, #0056b3 100%) + + .day-number + background: rgba(255, 255, 255, 0.2) + color: white + animation: pulse-today 2s infinite + + &.other-month + opacity: 0.7 + + .day-header + background: linear-gradient(135deg, var(--text-muted) 0%, #6c757d 100%) .day-header - background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%) + background: linear-gradient(135deg, var(--secondary-color) 0%, #4c46a6 100%) color: white - padding: var(--spacing-md) + padding: var(--spacing-md) var(--spacing-lg) display: flex align-items: center justify-content: space-between + position: relative + overflow: hidden - h3 - font-size: 1.125rem - font-weight: 600 - margin: 0 + &::before + content: '' + position: absolute + top: 0 + left: 0 + right: 0 + bottom: 0 + background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Ccircle cx='30' cy='30' r='2'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E") repeat + opacity: 0.3 + + .day-info + display: flex + align-items: center + gap: var(--spacing-md) + position: relative + z-index: 1 + + .day-number + background: rgba(255, 255, 255, 0.15) + border: 2px solid rgba(255, 255, 255, 0.3) + border-radius: 50% + width: 64px + height: 64px + display: flex + align-items: center + justify-content: center + font-size: 1.75rem + font-weight: 700 + color: white + transition: all 0.3s ease + backdrop-filter: blur(10px) + + .day-details + display: flex + flex-direction: column + gap: 2px + + .day-name + font-size: 1.125rem + font-weight: 600 + text-transform: capitalize + letter-spacing: 0.5px + + .month-indicator + font-size: 0.75rem + opacity: 0.9 + font-weight: 400 .event-count - background: rgba(255, 255, 255, 0.2) - padding: 4px 8px - border-radius: 12px - font-size: 0.75rem - font-weight: 500 + display: flex + flex-direction: column + align-items: center + gap: 2px + background: rgba(255, 255, 255, 0.15) + padding: var(--spacing-sm) var(--spacing-md) + border-radius: var(--border-radius-small) + backdrop-filter: blur(10px) + border: 1px solid rgba(255, 255, 255, 0.2) + position: relative + z-index: 1 + + .count-number + font-size: 1.25rem + font-weight: 700 + line-height: 1 + + .count-label + font-size: 0.625rem + font-weight: 500 + opacity: 0.9 + text-transform: uppercase + letter-spacing: 0.5px .day-events - padding: var(--spacing-sm) + padding: var(--spacing-md) display: flex flex-direction: column gap: var(--spacing-sm) + background: linear-gradient(to bottom, rgba(248, 249, 250, 0.5), rgba(255, 255, 255, 1)) // Calendar Event .calendar-event @@ -287,15 +455,33 @@ body align-items: flex-start justify-content: space-between gap: var(--spacing-md) - transition: all 0.2s ease + transition: all 0.3s ease + position: relative + overflow: hidden + + &::before + content: '' + position: absolute + top: 0 + left: 0 + width: 4px + height: 100% + background: linear-gradient(to bottom, var(--primary-color), rgba(0, 122, 255, 0.3)) + transition: width 0.3s ease &:hover box-shadow: var(--shadow-medium) - transform: translateY(-1px) + transform: translateY(-2px) + border-color: transparent + + &::before + width: 8px .event-content flex: 1 min-width: 0 + position: relative + z-index: 1 .event-text font-weight: 500 @@ -303,22 +489,38 @@ body font-size: 1rem margin-bottom: var(--spacing-xs) word-break: break-word + line-height: 1.4 .event-user font-size: 0.875rem font-weight: 600 opacity: 0.8 + display: flex + align-items: center + gap: var(--spacing-xs) + + &::before + content: '' + width: 8px + height: 8px + border-radius: 50% + background: currentColor + opacity: 0.6 .event-actions display: flex gap: var(--spacing-xs) flex-shrink: 0 + position: relative + z-index: 1 .edit-form width: 100% display: flex flex-direction: column gap: var(--spacing-sm) + position: relative + z-index: 1 .form-group display: flex @@ -337,6 +539,7 @@ body border-radius: var(--border-radius-small) font-size: 0.875rem background: var(--surface-color) + transition: all 0.2s ease &:focus outline: none @@ -352,55 +555,86 @@ body // Buttons .btn border: none - border-radius: var(--border-radius-small) + border-radius: var(--border-radius) padding: var(--spacing-sm) var(--spacing-md) font-size: 0.875rem - font-weight: 500 + font-weight: 600 cursor: pointer display: flex align-items: center justify-content: center gap: var(--spacing-xs) - transition: all 0.2s ease + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) text-decoration: none + position: relative + overflow: hidden + + &::before + content: '' + position: absolute + top: 0 + left: -100% + width: 100% + height: 100% + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent) + transition: left 0.5s ease + + &:hover::before + left: 100% &:disabled opacity: 0.5 cursor: not-allowed + transform: none !important + + &:hover::before + left: -100% &.btn-primary - background: var(--primary-color) + background: linear-gradient(135deg, var(--primary-color) 0%, #0056b3 100%) color: white + box-shadow: 0 4px 15px rgba(0, 122, 255, 0.3) &:hover:not(:disabled) - background: #0056b3 - transform: translateY(-1px) + transform: translateY(-2px) + box-shadow: 0 6px 20px rgba(0, 122, 255, 0.4) + + &:active:not(:disabled) + transform: translateY(0) &.btn-secondary - background: var(--border-color) + background: linear-gradient(135deg, rgba(255, 255, 255, 0.9) 0%, rgba(248, 249, 250, 0.9) 100%) color: var(--text-primary) + border: 1px solid rgba(0, 0, 0, 0.1) + backdrop-filter: blur(10px) &:hover:not(:disabled) - background: #d1d5db + background: linear-gradient(135deg, rgba(255, 255, 255, 1) 0%, rgba(240, 242, 245, 1) 100%) + transform: translateY(-1px) + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1) &.btn-danger - background: var(--danger-color) + background: linear-gradient(135deg, var(--danger-color) 0%, #dc3545 100%) color: white + box-shadow: 0 4px 15px rgba(255, 59, 48, 0.3) &:hover:not(:disabled) - background: #dc3545 + transform: translateY(-2px) + box-shadow: 0 6px 20px rgba(255, 59, 48, 0.4) &.btn-small padding: var(--spacing-xs) font-size: 0.75rem - width: 32px - height: 32px + width: 36px + height: 36px + border-radius: 50% svg font-size: 0.875rem svg font-size: 1rem + filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.1)) // Add Form Overlay .add-form-overlay @@ -409,59 +643,75 @@ body left: 0 right: 0 bottom: 0 - background: rgba(0, 0, 0, 0.5) + background: rgba(0, 0, 0, 0.6) + backdrop-filter: blur(8px) display: flex align-items: center justify-content: center z-index: 1000 padding: var(--spacing-md) + animation: fadeIn 0.3s ease .add-form - background: var(--surface-color) + background: linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(248, 249, 250, 0.98) 100%) + backdrop-filter: blur(20px) border-radius: var(--border-radius) - padding: var(--spacing-lg) + padding: var(--spacing-xl) width: 100% - max-width: 400px - box-shadow: var(--shadow-medium) + max-width: 420px + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2) max-height: 80vh overflow-y: auto + border: 1px solid rgba(255, 255, 255, 0.3) + animation: slideIn 0.3s ease h3 - font-size: 1.25rem - font-weight: 600 - margin-bottom: var(--spacing-md) + font-size: 1.375rem + font-weight: 700 + margin-bottom: var(--spacing-lg) color: var(--text-primary) text-align: center + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) .form-group display: flex flex-direction: column - margin-bottom: var(--spacing-md) + margin-bottom: var(--spacing-lg) label font-size: 0.875rem - font-weight: 500 + font-weight: 600 color: var(--text-secondary) - margin-bottom: var(--spacing-xs) + margin-bottom: var(--spacing-sm) + text-transform: uppercase + letter-spacing: 0.5px input, select width: 100% padding: var(--spacing-md) - border: 1px solid var(--border-color) - border-radius: var(--border-radius-small) + border: 2px solid rgba(255, 255, 255, 0.3) + border-radius: var(--border-radius) font-size: 1rem - background: var(--surface-color) + background: rgba(255, 255, 255, 0.7) + backdrop-filter: blur(10px) + transition: all 0.3s ease + font-weight: 500 &:focus outline: none border-color: var(--primary-color) - box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.1) + box-shadow: 0 0 0 4px rgba(0, 122, 255, 0.15) + background: rgba(255, 255, 255, 0.9) + + &::placeholder + color: var(--text-muted) + font-weight: 400 .form-actions display: flex - gap: var(--spacing-sm) + gap: var(--spacing-md) justify-content: flex-end - margin-top: var(--spacing-lg) + margin-top: var(--spacing-xl) // Floating Action Button .fab-container @@ -471,27 +721,59 @@ body z-index: 100 .fab - width: 56px - height: 56px + width: 64px + height: 64px border-radius: 50% - background: var(--primary-color) + background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%) color: white border: none cursor: pointer display: flex align-items: center justify-content: center - font-size: 1.25rem - box-shadow: var(--shadow-medium) - transition: all 0.2s ease + font-size: 1.5rem + box-shadow: 0 8px 32px rgba(0, 122, 255, 0.4) + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) + position: relative + overflow: hidden + + &::before + content: '' + position: absolute + top: 0 + left: 0 + right: 0 + bottom: 0 + background: linear-gradient(45deg, transparent 30%, rgba(255, 255, 255, 0.2) 50%, transparent 70%) + transform: translateX(-100%) + transition: transform 0.6s ease &:hover - background: #0056b3 - transform: translateY(-2px) - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2) + transform: translateY(-4px) scale(1.1) + box-shadow: 0 12px 40px rgba(0, 122, 255, 0.5) + + &::before + transform: translateX(100%) &:active - transform: translateY(0) + transform: translateY(-2px) scale(1.05) + + svg + filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2)) + +@keyframes fadeIn + from + opacity: 0 + to + opacity: 1 + +@keyframes slideIn + from + opacity: 0 + transform: translateY(20px) scale(0.95) + to + opacity: 1 + transform: translateY(0) scale(1) // Responsive Design @media (max-width: 480px) @@ -500,6 +782,7 @@ body .header padding: var(--spacing-xs) var(--spacing-sm) + border-radius: 0 .header-content gap: var(--spacing-sm) @@ -513,8 +796,8 @@ body min-width: 120px .btn-icon - width: 36px - height: 36px + width: 40px + height: 40px .view-controls flex-direction: column @@ -527,33 +810,88 @@ body .user-select width: 100% - .day-header - padding: var(--spacing-sm) + .day-section + .day-header + padding: var(--spacing-sm) var(--spacing-md) - h3 - font-size: 1rem + .day-info + gap: var(--spacing-sm) + + .day-number + width: 52px + height: 52px + font-size: 1.375rem + + .day-details + .day-name + font-size: 0.9rem + + .month-indicator + font-size: 0.65rem + + .event-count + padding: var(--spacing-xs) var(--spacing-sm) + + .count-number + font-size: 1rem + + .count-label + font-size: 0.5rem + + .day-events + padding: var(--spacing-sm) .calendar-event padding: var(--spacing-sm) flex-direction: column align-items: stretch + gap: var(--spacing-sm) + + .event-content + .event-text + font-size: 0.9rem + + .event-user + font-size: 0.8rem .event-actions justify-content: flex-end - margin-top: var(--spacing-sm) + margin-top: var(--spacing-xs) + + .btn-small + width: 32px + height: 32px .add-form-overlay padding: var(--spacing-sm) .add-form - padding: var(--spacing-md) + padding: var(--spacing-lg) max-height: 90vh + h3 + font-size: 1.25rem + + .form-group + margin-bottom: var(--spacing-md) + + input, select + padding: var(--spacing-sm) + .fab-container bottom: var(--spacing-md) right: var(--spacing-md) .fab - width: 48px - height: 48px - font-size: 1.125rem + width: 56px + height: 56px + font-size: 1.25rem + + .empty-state + padding: var(--spacing-lg) + + svg + font-size: 2.5rem + + p + font-size: 1rem