feat: add date selection calendar page
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
extending the "basepages.prompt.md"
|
||||||
|
|
||||||
|
next page i need is a page where the user can select a date and time for the chosen activity. The page should have a calendar interface where users can pick a date, and a time picker for selecting the time. The design should remain playful and inviting, with the same love-rose color scheme and heart-shaped elements in the background.
|
||||||
|
|
||||||
|
place it logically into the existing pages
|
||||||
+119
@@ -0,0 +1,119 @@
|
|||||||
|
/* ==========================================================================
|
||||||
|
Page: Datum (datetime.html)
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
.page-datetime .container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-datetime .intro {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-datetime .intro p {
|
||||||
|
color: var(--color-text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calendar card ----------------------------------------------------------- */
|
||||||
|
|
||||||
|
.calendar-card {
|
||||||
|
background: var(--color-white);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
box-shadow: var(--shadow-soft);
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-label {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: var(--color-rose-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-nav-btn {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--color-rose-light);
|
||||||
|
color: var(--color-rose-dark);
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
touch-action: manipulation;
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-nav-btn:disabled {
|
||||||
|
opacity: 0.35;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekday-row,
|
||||||
|
.day-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(7, 1fr);
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekday-row {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekday-row span {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.day-cell {
|
||||||
|
position: relative;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: transparent;
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text);
|
||||||
|
cursor: pointer;
|
||||||
|
touch-action: manipulation;
|
||||||
|
transition: background 0.2s ease, color 0.2s ease, transform 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day-cell:active {
|
||||||
|
transform: scale(0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
.day-cell.is-empty {
|
||||||
|
cursor: default;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day-cell.is-disabled {
|
||||||
|
color: #d9c3cb;
|
||||||
|
cursor: not-allowed;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day-cell.is-today {
|
||||||
|
box-shadow: inset 0 0 0 2px var(--color-rose);
|
||||||
|
}
|
||||||
|
|
||||||
|
.day-cell.is-selected {
|
||||||
|
background: linear-gradient(135deg, var(--color-rose) 0%, var(--color-rose-dark) 100%);
|
||||||
|
color: var(--color-white);
|
||||||
|
box-shadow: var(--shadow-strong);
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
|
||||||
|
<title>Wähle das Datum – Date-Planer</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&family=Quicksand:wght@600;700&display=swap" rel="stylesheet" />
|
||||||
|
<link rel="stylesheet" href="style.css" />
|
||||||
|
<link rel="stylesheet" href="datetime.css" />
|
||||||
|
</head>
|
||||||
|
<body class="page-datetime has-continue-bar">
|
||||||
|
<div class="heart-background" aria-hidden="true">
|
||||||
|
<div class="heart-shape"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<main class="container">
|
||||||
|
<a class="back-link" href="activity.html">‹ Zurück</a>
|
||||||
|
<div class="intro">
|
||||||
|
<h1>Wann soll es losgehen? 💌</h1>
|
||||||
|
<p id="activitySummary">Wähle das Datum für euer Date.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="calendar-card" aria-label="Kalender">
|
||||||
|
<div class="calendar-header">
|
||||||
|
<button id="prevMonthBtn" class="month-nav-btn" type="button" aria-label="Vorheriger Monat">‹</button>
|
||||||
|
<h2 id="monthLabel" class="month-label">Monat Jahr</h2>
|
||||||
|
<button id="nextMonthBtn" class="month-nav-btn" type="button" aria-label="Nächster Monat">›</button>
|
||||||
|
</div>
|
||||||
|
<div class="weekday-row" id="weekdayRow"></div>
|
||||||
|
<div class="day-grid" id="dayGrid"></div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<div class="continue-bar">
|
||||||
|
<div class="container">
|
||||||
|
<button id="continueBtn" class="btn btn-primary" type="button" disabled>Weiter</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="datetime.js" defer></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+149
@@ -0,0 +1,149 @@
|
|||||||
|
// Datum: Kalender-Widget
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
const WEEKDAYS = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
|
||||||
|
const MONTHS = [
|
||||||
|
"Januar", "Februar", "März", "April", "Mai", "Juni",
|
||||||
|
"Juli", "August", "September", "Oktober", "November", "Dezember",
|
||||||
|
];
|
||||||
|
|
||||||
|
const activitySummary = document.getElementById("activitySummary");
|
||||||
|
const monthLabel = document.getElementById("monthLabel");
|
||||||
|
const weekdayRow = document.getElementById("weekdayRow");
|
||||||
|
const dayGrid = document.getElementById("dayGrid");
|
||||||
|
const prevMonthBtn = document.getElementById("prevMonthBtn");
|
||||||
|
const nextMonthBtn = document.getElementById("nextMonthBtn");
|
||||||
|
const continueBtn = document.getElementById("continueBtn");
|
||||||
|
|
||||||
|
// Ohne gewählte Aktivität ergibt diese Seite keinen Sinn.
|
||||||
|
const selectedActivity = localStorage.getItem("selectedActivity");
|
||||||
|
if (!selectedActivity) {
|
||||||
|
window.location.href = "activity.html";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
activitySummary.textContent = `Für unser Date: „${selectedActivity}“`;
|
||||||
|
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
let availableDates = new Set();
|
||||||
|
let viewYear = today.getFullYear();
|
||||||
|
let viewMonth = today.getMonth();
|
||||||
|
let selectedDate = null;
|
||||||
|
|
||||||
|
WEEKDAYS.forEach((day) => {
|
||||||
|
const span = document.createElement("span");
|
||||||
|
span.textContent = day;
|
||||||
|
weekdayRow.appendChild(span);
|
||||||
|
});
|
||||||
|
|
||||||
|
function toDateKey(date) {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||||
|
const day = String(date.getDate()).padStart(2, "0");
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch("appsettings.json")
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((settings) => {
|
||||||
|
availableDates = new Set(settings.availableDates || []);
|
||||||
|
|
||||||
|
const upcoming = Array.from(availableDates)
|
||||||
|
.map((dateKey) => new Date(`${dateKey}T00:00:00`))
|
||||||
|
.filter((date) => date >= today)
|
||||||
|
.sort((a, b) => a - b);
|
||||||
|
|
||||||
|
if (upcoming.length > 0) {
|
||||||
|
viewYear = upcoming[0].getFullYear();
|
||||||
|
viewMonth = upcoming[0].getMonth();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCalendar();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
dayGrid.textContent = "Verfügbare Termine konnten nicht geladen werden.";
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderCalendar() {
|
||||||
|
monthLabel.textContent = `${MONTHS[viewMonth]} ${viewYear}`;
|
||||||
|
dayGrid.innerHTML = "";
|
||||||
|
|
||||||
|
const firstOfMonth = new Date(viewYear, viewMonth, 1);
|
||||||
|
// Montag = 0 ... Sonntag = 6
|
||||||
|
const leadingBlanks = (firstOfMonth.getDay() + 6) % 7;
|
||||||
|
const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate();
|
||||||
|
|
||||||
|
for (let i = 0; i < leadingBlanks; i++) {
|
||||||
|
const blank = document.createElement("span");
|
||||||
|
blank.className = "day-cell is-empty";
|
||||||
|
dayGrid.appendChild(blank);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let day = 1; day <= daysInMonth; day++) {
|
||||||
|
const cellDate = new Date(viewYear, viewMonth, day);
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.type = "button";
|
||||||
|
button.className = "day-cell";
|
||||||
|
button.textContent = String(day);
|
||||||
|
|
||||||
|
const isAvailable = availableDates.has(toDateKey(cellDate));
|
||||||
|
const isToday = cellDate.getTime() === today.getTime();
|
||||||
|
const isSelected = selectedDate && cellDate.getTime() === selectedDate.getTime();
|
||||||
|
|
||||||
|
if (!isAvailable) {
|
||||||
|
button.classList.add("is-disabled");
|
||||||
|
button.disabled = true;
|
||||||
|
}
|
||||||
|
if (isToday) {
|
||||||
|
button.classList.add("is-today");
|
||||||
|
}
|
||||||
|
if (isSelected) {
|
||||||
|
button.classList.add("is-selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
selectedDate = cellDate;
|
||||||
|
renderCalendar();
|
||||||
|
updateContinueState();
|
||||||
|
});
|
||||||
|
|
||||||
|
dayGrid.appendChild(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isCurrentMonth = viewYear === today.getFullYear() && viewMonth === today.getMonth();
|
||||||
|
prevMonthBtn.disabled = isCurrentMonth;
|
||||||
|
}
|
||||||
|
|
||||||
|
prevMonthBtn.addEventListener("click", () => {
|
||||||
|
viewMonth -= 1;
|
||||||
|
if (viewMonth < 0) {
|
||||||
|
viewMonth = 11;
|
||||||
|
viewYear -= 1;
|
||||||
|
}
|
||||||
|
renderCalendar();
|
||||||
|
});
|
||||||
|
|
||||||
|
nextMonthBtn.addEventListener("click", () => {
|
||||||
|
viewMonth += 1;
|
||||||
|
if (viewMonth > 11) {
|
||||||
|
viewMonth = 0;
|
||||||
|
viewYear += 1;
|
||||||
|
}
|
||||||
|
renderCalendar();
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateContinueState() {
|
||||||
|
continueBtn.disabled = !selectedDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
continueBtn.addEventListener("click", () => {
|
||||||
|
if (!selectedDate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Ablage für die spätere Anbindung an Datenbank/Storage
|
||||||
|
localStorage.setItem("selectedDate", toDateKey(selectedDate));
|
||||||
|
window.location.href = "name.html";
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user