feat: custom day count for the week view
Some checks are pending
Playwright Tests / test (push) Waiting to run
Some checks are pending
Playwright Tests / test (push) Waiting to run
Add a "Custom" option next to the 1/2/3/5/7 presets where any number of days from 1 to 31 can be typed. The seven duplicated preset button rows are replaced by a shared DaysSelector component with panel/header/menu variants. The day grid and all-day grid only had CSS for cols-1/2/3/5/7, so any other count silently fell back to five columns; both now derive grid-template-columns from the actual day count. v1.115.0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
2028ba80e0
commit
224fdba5c6
13
CHANGELOG.md
13
CHANGELOG.md
@ -2,6 +2,19 @@
|
|||||||
|
|
||||||
All notable changes to this project are documented in this file.
|
All notable changes to this project are documented in this file.
|
||||||
|
|
||||||
|
## [1.115.0] - 2026-09-03
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Day columns: a "Custom" option next to the 1/2/3/5/7 presets, where any number of days from
|
||||||
|
1 to 31 can be typed (e.g. 14, 21, 28). The chosen value shows up as its own chip beside the
|
||||||
|
presets and is available in every place the day count can be set — sidebar, both settings
|
||||||
|
flyouts, the desktop toolbar and the header overflow menu.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Day grid and all-day grid only had CSS for `cols-1/2/3/5/7`, so any other day count silently
|
||||||
|
fell back to a 5-column layout. Both grids now derive `grid-template-columns` from the actual
|
||||||
|
day count.
|
||||||
|
|
||||||
## [1.114.2] - 2026-09-01
|
## [1.114.2] - 2026-09-01
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "my-weekly-todo-list",
|
"name": "my-weekly-todo-list",
|
||||||
"version": "1.114.2",
|
"version": "1.115.0",
|
||||||
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
183
src/components/DaysSelector.tsx
Normal file
183
src/components/DaysSelector.tsx
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
|
export const DAY_PRESETS = [1, 2, 3, 5, 7];
|
||||||
|
export const MAX_VIEW_DAYS = 31;
|
||||||
|
|
||||||
|
export function clampViewDays(n: number): number {
|
||||||
|
if (!Number.isFinite(n)) return 7;
|
||||||
|
return Math.min(MAX_VIEW_DAYS, Math.max(1, Math.round(n)));
|
||||||
|
}
|
||||||
|
|
||||||
|
type Variant = "panel" | "header" | "menu";
|
||||||
|
|
||||||
|
interface DaysSelectorProps {
|
||||||
|
viewDays: number;
|
||||||
|
onChange: (days: number) => void;
|
||||||
|
darkMode?: boolean;
|
||||||
|
language?: string;
|
||||||
|
variant?: Variant;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Day-column count picker: the fixed presets plus a "Custom" field where the
|
||||||
|
* user types any number of days (1–31), e.g. 14, 21, 28.
|
||||||
|
*/
|
||||||
|
export default function DaysSelector({
|
||||||
|
viewDays,
|
||||||
|
onChange,
|
||||||
|
darkMode = false,
|
||||||
|
language = "en",
|
||||||
|
variant = "panel",
|
||||||
|
}: DaysSelectorProps) {
|
||||||
|
const isCustom = !DAY_PRESETS.includes(viewDays);
|
||||||
|
const [editing, setEditing] = useState(false);
|
||||||
|
const [draft, setDraft] = useState(String(viewDays));
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (editing) {
|
||||||
|
setDraft(String(viewDays));
|
||||||
|
inputRef.current?.focus();
|
||||||
|
inputRef.current?.select();
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [editing]);
|
||||||
|
|
||||||
|
const commit = () => {
|
||||||
|
const parsed = parseInt(draft, 10);
|
||||||
|
if (!isNaN(parsed)) onChange(clampViewDays(parsed));
|
||||||
|
setEditing(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isDE = language === "de";
|
||||||
|
const customLabel = isDE ? "Eigene Anzahl Tage" : "Custom number of days";
|
||||||
|
|
||||||
|
const buttons = isCustom ? [...DAY_PRESETS, viewDays] : DAY_PRESETS;
|
||||||
|
|
||||||
|
if (variant === "header") {
|
||||||
|
const cls = (active: boolean) =>
|
||||||
|
`px-2 py-0.5 text-xs rounded transition-colors ${active ? "bg-white shadow-sm font-bold text-black" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{buttons.map((num) => (
|
||||||
|
<button key={num} onClick={() => onChange(num)} className={cls(viewDays === num)}>
|
||||||
|
{num}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
{editing ? (
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
max={MAX_VIEW_DAYS}
|
||||||
|
value={draft}
|
||||||
|
onChange={(e) => setDraft(e.target.value)}
|
||||||
|
onBlur={commit}
|
||||||
|
onKeyDown={(e) => { if (e.key === "Enter") commit(); if (e.key === "Escape") setEditing(false); }}
|
||||||
|
className="w-12 px-1 py-0.5 text-xs rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 text-black dark:text-white"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<button onClick={() => setEditing(true)} className={cls(false)} title={customLabel}>…</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variant === "menu") {
|
||||||
|
const btnStyle = (active: boolean): React.CSSProperties => ({
|
||||||
|
padding: "2px 8px",
|
||||||
|
fontSize: "12px",
|
||||||
|
borderRadius: "4px",
|
||||||
|
border: "none",
|
||||||
|
cursor: "pointer",
|
||||||
|
fontWeight: active ? 700 : 400,
|
||||||
|
background: active ? "var(--bg-secondary, #e5e7eb)" : "transparent",
|
||||||
|
color: "inherit",
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{buttons.map((num) => (
|
||||||
|
<button key={num} onClick={() => onChange(num)} style={btnStyle(viewDays === num)}>
|
||||||
|
{num}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
{editing ? (
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
max={MAX_VIEW_DAYS}
|
||||||
|
value={draft}
|
||||||
|
onChange={(e) => setDraft(e.target.value)}
|
||||||
|
onBlur={commit}
|
||||||
|
onKeyDown={(e) => { if (e.key === "Enter") commit(); if (e.key === "Escape") setEditing(false); }}
|
||||||
|
style={{ width: "48px", padding: "2px 4px", fontSize: "12px", borderRadius: "4px", border: "1px solid var(--line, #d1d5db)", background: "transparent", color: "inherit" }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<button onClick={() => setEditing(true)} style={btnStyle(false)} title={customLabel}>…</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// "panel" — sidebar / flyout chip row
|
||||||
|
const chipStyle = (active: boolean): React.CSSProperties => ({
|
||||||
|
flex: 1,
|
||||||
|
padding: "5px 0",
|
||||||
|
fontSize: "0.8rem",
|
||||||
|
borderRadius: "6px",
|
||||||
|
border: "none",
|
||||||
|
cursor: "pointer",
|
||||||
|
fontWeight: active ? 700 : 400,
|
||||||
|
background: active ? (darkMode ? "#374151" : "var(--ink)") : (darkMode ? "#1f2937" : "#e5e7eb"),
|
||||||
|
color: active ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)"),
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: "4px" }}>
|
||||||
|
<div style={{ display: "flex", gap: "4px" }}>
|
||||||
|
{buttons.map((num) => (
|
||||||
|
<button key={num} onClick={() => onChange(num)} style={chipStyle(viewDays === num)}>
|
||||||
|
{num}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
<button
|
||||||
|
onClick={() => setEditing((v) => !v)}
|
||||||
|
style={chipStyle(false)}
|
||||||
|
title={customLabel}
|
||||||
|
>
|
||||||
|
{isDE ? "Eigene" : "Custom"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{editing && (
|
||||||
|
<div style={{ display: "flex", gap: "4px", alignItems: "center" }}>
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
max={MAX_VIEW_DAYS}
|
||||||
|
value={draft}
|
||||||
|
onChange={(e) => setDraft(e.target.value)}
|
||||||
|
onKeyDown={(e) => { if (e.key === "Enter") commit(); if (e.key === "Escape") setEditing(false); }}
|
||||||
|
placeholder={`1-${MAX_VIEW_DAYS}`}
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 0,
|
||||||
|
padding: "5px 6px",
|
||||||
|
fontSize: "0.8rem",
|
||||||
|
borderRadius: "6px",
|
||||||
|
border: `1px solid ${darkMode ? "#374151" : "var(--line, #d1d5db)"}`,
|
||||||
|
background: darkMode ? "#111827" : "#fff",
|
||||||
|
color: darkMode ? "#e5e7eb" : "var(--ink)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button onClick={commit} style={{ ...chipStyle(true), flex: "0 0 auto", padding: "5px 10px" }}>
|
||||||
|
OK
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -31,6 +31,7 @@ import {
|
|||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
import FocusModeOverlay from "./FocusModeOverlay";
|
import FocusModeOverlay from "./FocusModeOverlay";
|
||||||
|
import DaysSelector, { clampViewDays } from "./DaysSelector";
|
||||||
import {
|
import {
|
||||||
LayoutGrid,
|
LayoutGrid,
|
||||||
LayoutPanelLeft,
|
LayoutPanelLeft,
|
||||||
@ -2455,6 +2456,13 @@ export default function WeeklyView() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleViewDaysChange = (num: number) => {
|
||||||
|
const days = clampViewDays(num);
|
||||||
|
setViewDays(days);
|
||||||
|
savedViewDaysRef.current = days;
|
||||||
|
saveSetting("viewDays", days);
|
||||||
|
};
|
||||||
|
|
||||||
const handleSettingsChanged = (newSettings: any) => {
|
const handleSettingsChanged = (newSettings: any) => {
|
||||||
setShowTimeGrid(newSettings.showTimeGrid);
|
setShowTimeGrid(newSettings.showTimeGrid);
|
||||||
setCellDuration(newSettings.cellDuration);
|
setCellDuration(newSettings.cellDuration);
|
||||||
@ -5645,7 +5653,7 @@ export default function WeeklyView() {
|
|||||||
{isAllDayExpanded && (
|
{isAllDayExpanded && (
|
||||||
<div
|
<div
|
||||||
className={`all-day-events-grid cols-${viewDays}`}
|
className={`all-day-events-grid cols-${viewDays}`}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1, gridTemplateColumns: `repeat(${viewDays}, minmax(0, 1fr))` }}
|
||||||
>
|
>
|
||||||
{getVisibleDays().map((date) => {
|
{getVisibleDays().map((date) => {
|
||||||
const dayEvents = getAllDayEventsForDate(date);
|
const dayEvents = getAllDayEventsForDate(date);
|
||||||
@ -5788,7 +5796,7 @@ export default function WeeklyView() {
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
|
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
|
||||||
<span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "var(--ink-3)" }}>{profile.language === "de" ? "Spalten" : "Days"}</span>
|
<span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "var(--ink-3)" }}>{profile.language === "de" ? "Spalten" : "Days"}</span>
|
||||||
<div style={{ display: "flex", gap: "4px" }}>{[1, 2, 3, 5, 7].map((num) => (<button key={num} onClick={() => { setViewDays(num); savedViewDaysRef.current = num; saveSetting("viewDays", num); }} style={{ flex: 1, padding: "5px 0", fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: viewDays === num ? 700 : 400, background: viewDays === num ? (darkMode ? "#374151" : "var(--ink)") : (darkMode ? "#1f2937" : "#e5e7eb"), color: viewDays === num ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>{num}</button>))}</div>
|
<DaysSelector viewDays={viewDays} onChange={handleViewDaysChange} darkMode={darkMode} language={profile.language} />
|
||||||
</div>
|
</div>
|
||||||
{profile.showTimeGrid && (<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}><span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "var(--ink-3)" }}>{profile.language === "de" ? "Zeitfenster" : "Slot"}</span><div style={{ display: "flex", gap: "4px" }}>{(([15, 20, 30, 60] as CellDuration[])).map((d) => (<button key={d} onClick={() => { setCellDuration(d); saveSetting("cellDuration", d); saveViewSetting("cellDuration", d, true); }} style={{ flex: 1, padding: "5px 0", fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: effectiveCellDuration === d ? 700 : 400, background: effectiveCellDuration === d ? (darkMode ? "#374151" : "var(--ink)") : (darkMode ? "#1f2937" : "#e5e7eb"), color: effectiveCellDuration === d ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>{d}m</button>))}</div></div>)}
|
{profile.showTimeGrid && (<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}><span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "var(--ink-3)" }}>{profile.language === "de" ? "Zeitfenster" : "Slot"}</span><div style={{ display: "flex", gap: "4px" }}>{(([15, 20, 30, 60] as CellDuration[])).map((d) => (<button key={d} onClick={() => { setCellDuration(d); saveSetting("cellDuration", d); saveViewSetting("cellDuration", d, true); }} style={{ flex: 1, padding: "5px 0", fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: effectiveCellDuration === d ? 700 : 400, background: effectiveCellDuration === d ? (darkMode ? "#374151" : "var(--ink)") : (darkMode ? "#1f2937" : "#e5e7eb"), color: effectiveCellDuration === d ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>{d}m</button>))}</div></div>)}
|
||||||
{profile.showTimeGrid && (<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}><span style={{ fontSize: "0.75rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>:15/:30/:45</span><button onClick={() => { saveViewSetting("showSubHourSlots", !effectiveShowSubHourSlots, true); }} style={{ background: "none", border: "none", cursor: "pointer", padding: "2px", color: darkMode ? "#9ca3af" : "#6b7280" }}>{effectiveShowSubHourSlots ? <Eye size={16} /> : <EyeOff size={16} />}</button></div>)}
|
{profile.showTimeGrid && (<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}><span style={{ fontSize: "0.75rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>:15/:30/:45</span><button onClick={() => { saveViewSetting("showSubHourSlots", !effectiveShowSubHourSlots, true); }} style={{ background: "none", border: "none", cursor: "pointer", padding: "2px", color: darkMode ? "#9ca3af" : "#6b7280" }}>{effectiveShowSubHourSlots ? <Eye size={16} /> : <EyeOff size={16} />}</button></div>)}
|
||||||
@ -5847,7 +5855,7 @@ export default function WeeklyView() {
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ borderTop: "1px solid var(--line)", paddingTop: "8px" }} />
|
<div style={{ borderTop: "1px solid var(--line)", paddingTop: "8px" }} />
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}><span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "var(--ink-3)" }}>{profile.language === "de" ? "Ansicht" : "View"}</span><div style={{ display: "flex", gap: "4px" }}>{[{ key: "simple", icon: <Calendar size={13} /> }, { key: "calendar", icon: <CalendarDays size={13} /> }, { key: "list", icon: <ListTodo size={13} /> }, { key: "kanban", icon: <Kanban size={13} /> }, { key: "priority", icon: <Target size={13} /> }].map((v) => (<button key={v.key} onClick={() => { setViewStyle(v.key as any); saveSetting("viewStyle", v.key); if (v.key === "list") { setShowTimeGrid(false); saveSetting("showTimeGrid", false); } if (v.key === "simple" || v.key === "calendar") { setShowTimeGrid(true); saveSetting("showTimeGrid", true); } if (v.key === "priority" || v.key === "kanban") { setShowTimeGrid(false); saveSetting("showTimeGrid", false); } }} style={{ flex: 1, padding: "5px 0", borderRadius: "6px", border: "none", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", fontWeight: profile.viewStyle === v.key ? 700 : 400, background: profile.viewStyle === v.key ? "var(--accent)" : (darkMode ? "#1f2937" : "#e5e7eb"), color: profile.viewStyle === v.key ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>{v.icon}</button>))}</div></div>
|
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}><span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "var(--ink-3)" }}>{profile.language === "de" ? "Ansicht" : "View"}</span><div style={{ display: "flex", gap: "4px" }}>{[{ key: "simple", icon: <Calendar size={13} /> }, { key: "calendar", icon: <CalendarDays size={13} /> }, { key: "list", icon: <ListTodo size={13} /> }, { key: "kanban", icon: <Kanban size={13} /> }, { key: "priority", icon: <Target size={13} /> }].map((v) => (<button key={v.key} onClick={() => { setViewStyle(v.key as any); saveSetting("viewStyle", v.key); if (v.key === "list") { setShowTimeGrid(false); saveSetting("showTimeGrid", false); } if (v.key === "simple" || v.key === "calendar") { setShowTimeGrid(true); saveSetting("showTimeGrid", true); } if (v.key === "priority" || v.key === "kanban") { setShowTimeGrid(false); saveSetting("showTimeGrid", false); } }} style={{ flex: 1, padding: "5px 0", borderRadius: "6px", border: "none", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", fontWeight: profile.viewStyle === v.key ? 700 : 400, background: profile.viewStyle === v.key ? "var(--accent)" : (darkMode ? "#1f2937" : "#e5e7eb"), color: profile.viewStyle === v.key ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>{v.icon}</button>))}</div></div>
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}><span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "var(--ink-3)" }}>{profile.language === "de" ? "Spalten" : "Days"}</span><div style={{ display: "flex", gap: "4px" }}>{[1, 2, 3, 5, 7].map((num) => (<button key={num} onClick={() => { setViewDays(num); savedViewDaysRef.current = num; saveSetting("viewDays", num); }} style={{ flex: 1, padding: "5px 0", fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: viewDays === num ? 700 : 400, background: viewDays === num ? (darkMode ? "#374151" : "var(--ink)") : (darkMode ? "#1f2937" : "#e5e7eb"), color: viewDays === num ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>{num}</button>))}</div></div>
|
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}><span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "var(--ink-3)" }}>{profile.language === "de" ? "Spalten" : "Days"}</span><DaysSelector viewDays={viewDays} onChange={handleViewDaysChange} darkMode={darkMode} language={profile.language} /></div>
|
||||||
{profile.showTimeGrid && (<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}><span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "var(--ink-3)" }}>{profile.language === "de" ? "Zeitfenster" : "Slot"}</span><div style={{ display: "flex", gap: "4px" }}>{(([15, 20, 30, 60] as CellDuration[])).map((d) => (<button key={d} onClick={() => { setCellDuration(d); saveSetting("cellDuration", d); saveViewSetting("cellDuration", d, true); }} style={{ flex: 1, padding: "5px 0", fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: effectiveCellDuration === d ? 700 : 400, background: effectiveCellDuration === d ? (darkMode ? "#374151" : "var(--ink)") : (darkMode ? "#1f2937" : "#e5e7eb"), color: effectiveCellDuration === d ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>{d}m</button>))}</div></div>)}
|
{profile.showTimeGrid && (<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}><span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "var(--ink-3)" }}>{profile.language === "de" ? "Zeitfenster" : "Slot"}</span><div style={{ display: "flex", gap: "4px" }}>{(([15, 20, 30, 60] as CellDuration[])).map((d) => (<button key={d} onClick={() => { setCellDuration(d); saveSetting("cellDuration", d); saveViewSetting("cellDuration", d, true); }} style={{ flex: 1, padding: "5px 0", fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: effectiveCellDuration === d ? 700 : 400, background: effectiveCellDuration === d ? (darkMode ? "#374151" : "var(--ink)") : (darkMode ? "#1f2937" : "#e5e7eb"), color: effectiveCellDuration === d ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>{d}m</button>))}</div></div>)}
|
||||||
{profile.showTimeGrid && (<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}><span style={{ fontSize: "0.75rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>:15/:30/:45</span><button onClick={() => { saveViewSetting("showSubHourSlots", !effectiveShowSubHourSlots, true); }} style={{ background: "none", border: "none", cursor: "pointer", padding: "2px", color: darkMode ? "#9ca3af" : "#6b7280" }}>{effectiveShowSubHourSlots ? <Eye size={16} /> : <EyeOff size={16} />}</button></div>)}
|
{profile.showTimeGrid && (<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}><span style={{ fontSize: "0.75rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>:15/:30/:45</span><button onClick={() => { saveViewSetting("showSubHourSlots", !effectiveShowSubHourSlots, true); }} style={{ background: "none", border: "none", cursor: "pointer", padding: "2px", color: darkMode ? "#9ca3af" : "#6b7280" }}>{effectiveShowSubHourSlots ? <Eye size={16} /> : <EyeOff size={16} />}</button></div>)}
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}><span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>{profile.language === "de" ? "Textgröße" : "Text size"}</span><div style={{ display: "flex", gap: "4px" }}>{(["S", "M", "L"] as const).map((size) => (<button key={size} onClick={() => { setFontSize(size); saveSetting("fontSize", size); }} style={{ padding: "4px 10px", fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: profile.fontSize === size ? 700 : 400, background: profile.fontSize === size ? (darkMode ? "#374151" : "#333") : (darkMode ? "#1f2937" : "#e5e7eb"), color: profile.fontSize === size ? "#fff" : (darkMode ? "#9ca3af" : "#6b7280") }}>{size}</button>))}</div></div>
|
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}><span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>{profile.language === "de" ? "Textgröße" : "Text size"}</span><div style={{ display: "flex", gap: "4px" }}>{(["S", "M", "L"] as const).map((size) => (<button key={size} onClick={() => { setFontSize(size); saveSetting("fontSize", size); }} style={{ padding: "4px 10px", fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: profile.fontSize === size ? 700 : 400, background: profile.fontSize === size ? (darkMode ? "#374151" : "#333") : (darkMode ? "#1f2937" : "#e5e7eb"), color: profile.fontSize === size ? "#fff" : (darkMode ? "#9ca3af" : "#6b7280") }}>{size}</button>))}</div></div>
|
||||||
@ -5919,7 +5927,7 @@ export default function WeeklyView() {
|
|||||||
{/* Columns / Days */}
|
{/* Columns / Days */}
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
|
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
|
||||||
<span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "var(--ink-3)" }}>{profile.language === "de" ? "Spalten" : "Days"}</span>
|
<span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "var(--ink-3)" }}>{profile.language === "de" ? "Spalten" : "Days"}</span>
|
||||||
<div style={{ display: "flex", gap: "4px" }}>{[1, 2, 3, 5, 7].map((num) => (<button key={num} onClick={() => { setViewDays(num); savedViewDaysRef.current = num; saveSetting("viewDays", num); }} style={{ flex: 1, padding: "5px 0", fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: viewDays === num ? 700 : 400, background: viewDays === num ? (darkMode ? "#374151" : "var(--ink)") : (darkMode ? "#1f2937" : "#e5e7eb"), color: viewDays === num ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>{num}</button>))}</div>
|
<DaysSelector viewDays={viewDays} onChange={handleViewDaysChange} darkMode={darkMode} language={profile.language} />
|
||||||
</div>
|
</div>
|
||||||
{/* Slot Duration */}
|
{/* Slot Duration */}
|
||||||
{profile.showTimeGrid && (<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}><span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "var(--ink-3)" }}>{profile.language === "de" ? "Zeitfenster" : "Slot"}</span><div style={{ display: "flex", gap: "4px" }}>{(([15, 20, 30, 60] as CellDuration[])).map((d) => (<button key={d} onClick={() => { setCellDuration(d); saveSetting("cellDuration", d); saveViewSetting("cellDuration", d, true); }} style={{ flex: 1, padding: "5px 0", fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: effectiveCellDuration === d ? 700 : 400, background: effectiveCellDuration === d ? (darkMode ? "#374151" : "var(--ink)") : (darkMode ? "#1f2937" : "#e5e7eb"), color: effectiveCellDuration === d ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>{d}m</button>))}</div></div>)}
|
{profile.showTimeGrid && (<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}><span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "var(--ink-3)" }}>{profile.language === "de" ? "Zeitfenster" : "Slot"}</span><div style={{ display: "flex", gap: "4px" }}>{(([15, 20, 30, 60] as CellDuration[])).map((d) => (<button key={d} onClick={() => { setCellDuration(d); saveSetting("cellDuration", d); saveViewSetting("cellDuration", d, true); }} style={{ flex: 1, padding: "5px 0", fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: effectiveCellDuration === d ? 700 : 400, background: effectiveCellDuration === d ? (darkMode ? "#374151" : "var(--ink)") : (darkMode ? "#1f2937" : "#e5e7eb"), color: effectiveCellDuration === d ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>{d}m</button>))}</div></div>)}
|
||||||
@ -6046,7 +6054,7 @@ export default function WeeklyView() {
|
|||||||
{flyoutSection === "columns" && (
|
{flyoutSection === "columns" && (
|
||||||
<>
|
<>
|
||||||
<div style={flyoutLabelStyle}>{profile.language === "de" ? "Spalten" : "Days"}</div>
|
<div style={flyoutLabelStyle}>{profile.language === "de" ? "Spalten" : "Days"}</div>
|
||||||
<div style={{ display: "flex", gap: "4px" }}>{[1, 2, 3, 5, 7].map((num) => (<button key={num} onClick={() => { setViewDays(num); savedViewDaysRef.current = num; saveSetting("viewDays", num); }} style={{ flex: 1, padding: "5px 0", fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: viewDays === num ? 700 : 400, background: viewDays === num ? (darkMode ? "#374151" : "var(--ink)") : (darkMode ? "#1f2937" : "#e5e7eb"), color: viewDays === num ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>{num}</button>))}</div>
|
<DaysSelector viewDays={viewDays} onChange={handleViewDaysChange} darkMode={darkMode} language={profile.language} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{flyoutSection === "slot" && (
|
{flyoutSection === "slot" && (
|
||||||
@ -6259,7 +6267,7 @@ export default function WeeklyView() {
|
|||||||
{flyoutSection === "columns" && (
|
{flyoutSection === "columns" && (
|
||||||
<>
|
<>
|
||||||
<div style={flyoutLabelStyle}>{profile.language === "de" ? "Spalten" : "Days"}</div>
|
<div style={flyoutLabelStyle}>{profile.language === "de" ? "Spalten" : "Days"}</div>
|
||||||
<div style={{ display: "flex", gap: "4px" }}>{[1, 2, 3, 5, 7].map((num) => (<button key={num} onClick={() => { setViewDays(num); savedViewDaysRef.current = num; saveSetting("viewDays", num); }} style={{ flex: 1, padding: "5px 0", fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: viewDays === num ? 700 : 400, background: viewDays === num ? (darkMode ? "#374151" : "var(--ink)") : (darkMode ? "#1f2937" : "#e5e7eb"), color: viewDays === num ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>{num}</button>))}</div>
|
<DaysSelector viewDays={viewDays} onChange={handleViewDaysChange} darkMode={darkMode} language={profile.language} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{flyoutSection === "slot" && (
|
{flyoutSection === "slot" && (
|
||||||
@ -6344,19 +6352,7 @@ export default function WeeklyView() {
|
|||||||
title="Days to show"
|
title="Days to show"
|
||||||
>
|
>
|
||||||
<LayoutGrid size={16} className="text-gray-500 mr-1" />
|
<LayoutGrid size={16} className="text-gray-500 mr-1" />
|
||||||
{[1, 2, 3, 5, 7].map((num) => (
|
<DaysSelector viewDays={viewDays} onChange={handleViewDaysChange} darkMode={darkMode} language={profile.language} variant="header" />
|
||||||
<button
|
|
||||||
key={num}
|
|
||||||
onClick={() => {
|
|
||||||
setViewDays(num);
|
|
||||||
savedViewDaysRef.current = num;
|
|
||||||
saveSetting("viewDays", num);
|
|
||||||
}}
|
|
||||||
className={`px-2 py-0.5 text-xs rounded transition-colors ${viewDays === num ? "bg-white shadow-sm font-bold text-black" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`}
|
|
||||||
>
|
|
||||||
{num}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Slot Duration — desktop only (available in sidebar on tablet) */}
|
{/* Slot Duration — desktop only (available in sidebar on tablet) */}
|
||||||
@ -6634,15 +6630,7 @@ export default function WeeklyView() {
|
|||||||
<div style={{ borderTop: "1px solid var(--border-color, #e5e7eb)", margin: "2px 0" }} />
|
<div style={{ borderTop: "1px solid var(--border-color, #e5e7eb)", margin: "2px 0" }} />
|
||||||
<div style={{ padding: "6px 12px", display: "flex", alignItems: "center", gap: "6px", flexWrap: "wrap" }}>
|
<div style={{ padding: "6px 12px", display: "flex", alignItems: "center", gap: "6px", flexWrap: "wrap" }}>
|
||||||
<span style={{ fontSize: "12px", color: "#888", marginRight: "4px" }}>{profile.language === "de" ? "Tage" : "Days"}:</span>
|
<span style={{ fontSize: "12px", color: "#888", marginRight: "4px" }}>{profile.language === "de" ? "Tage" : "Days"}:</span>
|
||||||
{[1, 2, 3, 5, 7].map((num) => (
|
<DaysSelector viewDays={viewDays} onChange={handleViewDaysChange} darkMode={darkMode} language={profile.language} variant="menu" />
|
||||||
<button
|
|
||||||
key={num}
|
|
||||||
onClick={() => { setViewDays(num); savedViewDaysRef.current = num; saveSetting("viewDays", num); }}
|
|
||||||
style={{ padding: "2px 8px", fontSize: "12px", borderRadius: "4px", border: "none", cursor: "pointer", fontWeight: viewDays === num ? 700 : 400, background: viewDays === num ? "var(--bg-secondary, #e5e7eb)" : "transparent", color: "inherit" }}
|
|
||||||
>
|
|
||||||
{num}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
{profile.showTimeGrid && (
|
{profile.showTimeGrid && (
|
||||||
<div style={{ padding: "6px 12px", display: "flex", alignItems: "center", gap: "6px" }}>
|
<div style={{ padding: "6px 12px", display: "flex", alignItems: "center", gap: "6px" }}>
|
||||||
@ -7404,10 +7392,12 @@ export default function WeeklyView() {
|
|||||||
data-slide-direction={slideDirection}
|
data-slide-direction={slideDirection}
|
||||||
data-nav-type={viewDays > 1 ? "week" : "day"}
|
data-nav-type={viewDays > 1 ? "week" : "day"}
|
||||||
style={profile.showTimeGrid ? {
|
style={profile.showTimeGrid ? {
|
||||||
|
gridTemplateColumns: `repeat(${viewDays}, minmax(0, 1fr))`,
|
||||||
height: `${24 * (60 / effectiveCellDuration) * getSlotHeight(effectiveCellDuration) + getHeaderHeight(effectiveCellDuration)}px`,
|
height: `${24 * (60 / effectiveCellDuration) * getSlotHeight(effectiveCellDuration) + getHeaderHeight(effectiveCellDuration)}px`,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
alignSelf: "flex-start",
|
alignSelf: "flex-start",
|
||||||
} : {
|
} : {
|
||||||
|
gridTemplateColumns: `repeat(${viewDays}, minmax(0, 1fr))`,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user