compact calendar now
This commit is contained in:
@@ -95,7 +95,7 @@
|
|||||||
<div class="text-sm font-medium opacity-60">{{ court_name }} · {{ week_label }}</div>
|
<div class="text-sm font-medium opacity-60">{{ court_name }} · {{ week_label }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="overflow-x-auto rounded-lg border border-base-300 bg-base-100 shadow-sm">
|
<div class="overflow-x-auto border border-base-300 bg-base-100 shadow-sm">
|
||||||
<table id="cal" class="w-full border-collapse text-sm">
|
<table id="cal" class="w-full border-collapse text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-base-200">
|
<tr class="bg-base-200">
|
||||||
@@ -110,6 +110,14 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for row in rows %}
|
{% for row in rows %}
|
||||||
|
{% if row.free_group %}
|
||||||
|
<tr>
|
||||||
|
<td class="border border-base-300 bg-base-200 px-2 py-1 text-center text-xs font-medium">{{ row.hour_label }}</td>
|
||||||
|
<td colspan="7" class="border border-base-300 px-2 py-1">
|
||||||
|
<div class="text-center text-xs uppercase tracking-wide opacity-30">{{ t(key="free", lang=lang) }}</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="border border-base-300 bg-base-200 p-2 text-center font-medium opacity-70">{{ row.hour_label }}</td>
|
<td class="border border-base-300 bg-base-200 p-2 text-center font-medium opacity-70">{{ row.hour_label }}</td>
|
||||||
{% for cell in row.cells %}
|
{% for cell in row.cells %}
|
||||||
@@ -147,6 +155,7 @@
|
|||||||
</td>
|
</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -64,6 +64,9 @@ pub struct Cell {
|
|||||||
pub struct Row {
|
pub struct Row {
|
||||||
pub hour_label: String,
|
pub hour_label: String,
|
||||||
pub cells: Vec<Cell>,
|
pub cells: Vec<Cell>,
|
||||||
|
/// Public view only: `true` when this row collapses a run of fully-free
|
||||||
|
/// hours into one compact strip. The admin grid never sets it.
|
||||||
|
pub free_group: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
@@ -107,6 +110,46 @@ fn week_monday(week: Option<&str>) -> NaiveDate {
|
|||||||
monday_of(base)
|
monday_of(base)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Collapses runs of fully-free hour rows into one compact strip so a court
|
||||||
|
/// that isn't booked solid doesn't waste vertical space. Rows holding at
|
||||||
|
/// least one booking are kept full-size so bookings stay easy to read.
|
||||||
|
/// Public calendar only — the admin grid always shows every hour.
|
||||||
|
fn group_free_rows(rows: Vec<Row>) -> Vec<Row> {
|
||||||
|
let mut out: Vec<Row> = Vec::new();
|
||||||
|
let mut run: Vec<Row> = Vec::new();
|
||||||
|
for row in rows {
|
||||||
|
if row.cells.iter().all(|c| !c.booked) {
|
||||||
|
run.push(row);
|
||||||
|
} else {
|
||||||
|
flush_free_run(&mut run, &mut out);
|
||||||
|
out.push(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flush_free_run(&mut run, &mut out);
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Drains a pending run of free rows into `out` as a single collapsed row.
|
||||||
|
fn flush_free_run(run: &mut Vec<Row>, out: &mut Vec<Row>) {
|
||||||
|
if run.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let hour_of = |r: &Row| r.cells.first().map_or(FIRST_HOUR, |c| c.hour);
|
||||||
|
let first = run.first().map_or(FIRST_HOUR, hour_of);
|
||||||
|
let last = run.last().map_or(first, hour_of);
|
||||||
|
let hour_label = if run.len() > 1 {
|
||||||
|
format!("{first:02}:00 – {:02}:00", last + 1)
|
||||||
|
} else {
|
||||||
|
format!("{first:02}:00")
|
||||||
|
};
|
||||||
|
out.push(Row {
|
||||||
|
hour_label,
|
||||||
|
cells: Vec::new(),
|
||||||
|
free_group: true,
|
||||||
|
});
|
||||||
|
run.clear();
|
||||||
|
}
|
||||||
|
|
||||||
/// Builds the calendar grid for the selected court and week.
|
/// Builds the calendar grid for the selected court and week.
|
||||||
pub async fn build_calendar(
|
pub async fn build_calendar(
|
||||||
ctx: &AppContext,
|
ctx: &AppContext,
|
||||||
@@ -201,10 +244,15 @@ pub async fn build_calendar(
|
|||||||
Row {
|
Row {
|
||||||
hour_label: format!("{hour:02}:00"),
|
hour_label: format!("{hour:02}:00"),
|
||||||
cells,
|
cells,
|
||||||
|
free_group: false,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
// The public calendar collapses empty stretches to stay compact; the admin
|
||||||
|
// grid always shows every hour so each slot stays individually editable.
|
||||||
|
let rows = if is_admin { rows } else { group_free_rows(rows) };
|
||||||
|
|
||||||
Ok(CalendarPage {
|
Ok(CalendarPage {
|
||||||
lang: lang.to_string(),
|
lang: lang.to_string(),
|
||||||
is_admin,
|
is_admin,
|
||||||
|
|||||||
Reference in New Issue
Block a user