feat: view style tabs at top of settings as per-view context

Move view style selector (Simple/Calendar/List/Kanban) to the top
of the general settings tab. Settings below now automatically apply
to the selected view. Replaces the previous per-view badge approach
with a cleaner tab-based UI: switch to a view tab, adjust its
settings — done.

Per-view settings: hour format, sub-hour labels, weather, checkboxes.

v1.50.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
mARTin 2026-03-18 17:23:18 +01:00
parent 3eff24143c
commit a26dc5efa6
2 changed files with 36 additions and 119 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "my-weekly-todo-list", "name": "my-weekly-todo-list",
"version": "1.49.1", "version": "1.50.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": {

View File

@ -8514,10 +8514,7 @@ export default function WeeklyView() {
onEndHourChange: (h: number) => { setEndHour(h); saveSetting("endHour", h); }, onEndHourChange: (h: number) => { setEndHour(h); saveSetting("endHour", h); },
}} }}
perView={{ perView={{
isPerView: isPerView as any,
togglePerView: togglePerView as any,
saveViewSetting: saveViewSetting as any, saveViewSetting: saveViewSetting as any,
viewLabel: viewStyle === "simple" ? (language === "de" ? "Einfach" : "Simple") : viewStyle === "calendar" ? (language === "de" ? "Kalender" : "Calendar") : viewStyle === "list" ? (language === "de" ? "Liste" : "List") : "Kanban",
}} }}
/> />
) )
@ -10055,10 +10052,7 @@ interface SettingsSidebarProps {
onEndHourChange: (h: number) => void; onEndHourChange: (h: number) => void;
}; };
perView: { perView: {
isPerView: (key: string) => boolean;
togglePerView: (key: string, globalVal: any) => void;
saveViewSetting: (key: string, value: any, perView: boolean) => void; saveViewSetting: (key: string, value: any, perView: boolean) => void;
viewLabel: string;
}; };
} }
// Notes Sidebar Component // Notes Sidebar Component
@ -10286,31 +10280,6 @@ function SettingsSidebar({
mobileActions, mobileActions,
perView, perView,
}: SettingsSidebarProps) { }: SettingsSidebarProps) {
// Per-view badge: shows which view a setting applies to, click to toggle
const PerViewBadge = ({ settingKey, globalVal }: { settingKey: string; globalVal: any }) => {
const isPV = perView.isPerView(settingKey);
return (
<button
onClick={() => perView.togglePerView(settingKey, globalVal)}
title={isPV ? (profile.language === "de" ? `Nur für ${perView.viewLabel} — klicken für alle Ansichten` : `Only for ${perView.viewLabel} — click for all views`) : (profile.language === "de" ? `Alle Ansichten — klicken für nur ${perView.viewLabel}` : `All views — click for ${perView.viewLabel} only`)}
style={{
fontSize: "0.55rem",
padding: "1px 5px",
borderRadius: "8px",
border: isPV ? "1px solid #6366f1" : "1px solid #d1d5db",
background: isPV ? "#eef2ff" : "transparent",
color: isPV ? "#6366f1" : "#9ca3af",
cursor: "pointer",
fontWeight: 600,
whiteSpace: "nowrap" as const,
marginLeft: "4px",
}}
>
{isPV ? perView.viewLabel : (profile.language === "de" ? "Alle" : "All")}
</button>
);
};
const [activeTab, setActiveTab] = useState< const [activeTab, setActiveTab] = useState<
"calendar" | "general" | "account" | "styling" | "motivation" | "about" | "localisation" "calendar" | "general" | "account" | "styling" | "motivation" | "about" | "localisation"
>(initialTab || "general"); >(initialTab || "general");
@ -10837,6 +10806,33 @@ function SettingsSidebar({
<div <div
style={{ display: "flex", flexDirection: "column", gap: "20px" }} style={{ display: "flex", flexDirection: "column", gap: "20px" }}
> >
{/* View Style Tabs — at top, acts as context for per-view settings */}
<div>
<label style={{ display: "block", fontSize: "0.9rem", fontWeight: 600, marginBottom: "8px" }}>
{t.viewStyle}
</label>
<div className="flex items-center gap-1 bg-gray-100 dark:bg-gray-800 rounded p-1" style={{ width: "fit-content" }}>
<button onClick={() => { setViewStyle("simple"); setShowTimeGrid(true); saveSetting("viewStyle", "simple"); saveSetting("showTimeGrid", true); }}
className={`px-4 py-2 text-sm rounded transition-colors ${showTimeGrid && viewStyle === "simple" ? "bg-white shadow-sm font-bold text-black dark:bg-gray-700 dark:text-white" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`}>
{t.simpleView}
</button>
<button onClick={() => { setViewStyle("calendar"); setShowTimeGrid(true); saveSetting("viewStyle", "calendar"); saveSetting("showTimeGrid", true); }}
className={`px-4 py-2 text-sm rounded transition-colors ${showTimeGrid && viewStyle === "calendar" ? "bg-white shadow-sm font-bold text-black dark:bg-gray-700 dark:text-white" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`}>
{t.calendarView}
</button>
<button onClick={() => { setViewStyle("list"); setShowTimeGrid(false); saveSetting("viewStyle", "list"); saveSetting("showTimeGrid", false); }}
className={`px-4 py-2 text-sm rounded transition-colors ${!showTimeGrid && viewStyle === "list" ? "bg-white shadow-sm font-bold text-black dark:bg-gray-700 dark:text-white" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`}>
{t.listView}
</button>
<button onClick={() => { setViewStyle("kanban"); saveSetting("viewStyle", "kanban"); }}
className={`px-4 py-2 text-sm rounded transition-colors ${viewStyle === "kanban" ? "bg-white shadow-sm font-bold text-black dark:bg-gray-700 dark:text-white" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`}>
{t.kanbanView}
</button>
</div>
<p style={{ fontSize: "0.7rem", color: "#9ca3af", marginTop: "4px" }}>
{profile.language === "de" ? "Einstellungen unten gelten für diese Ansicht" : "Settings below apply to this view"}
</p>
</div>
<div <div
style={{ display: "flex", alignItems: "center", gap: "8px" }} style={{ display: "flex", alignItems: "center", gap: "8px" }}
@ -10960,18 +10956,15 @@ function SettingsSidebar({
checked={profile.showTaskCheckboxes || false} checked={profile.showTaskCheckboxes || false}
onChange={(e) => { onChange={(e) => {
setProfile({ ...profile, showTaskCheckboxes: e.target.checked }); setProfile({ ...profile, showTaskCheckboxes: e.target.checked });
if (perView.isPerView("showTaskCheckboxes")) { perView.saveViewSetting("showTaskCheckboxes", e.target.checked, true);
perView.saveViewSetting("showTaskCheckboxes", e.target.checked, true);
}
}} }}
style={{ width: "16px", height: "16px" }} style={{ width: "16px", height: "16px" }}
/> />
<label <label
htmlFor="showTaskCheckboxes" htmlFor="showTaskCheckboxes"
style={{ fontSize: "0.9rem", fontWeight: 600, display: "flex", alignItems: "center" }} style={{ fontSize: "0.9rem", fontWeight: 600 }}
> >
{t.showTaskCheckboxes} {t.showTaskCheckboxes}
<PerViewBadge settingKey="showTaskCheckboxes" globalVal={profile.showTaskCheckboxes} />
</label> </label>
</div> </div>
<div <div
@ -11180,26 +11173,20 @@ function SettingsSidebar({
<div style={{ marginTop: "4px" }}> <div style={{ marginTop: "4px" }}>
<label <label
style={{ style={{
display: "flex", display: "block",
alignItems: "center",
fontSize: "0.9rem", fontSize: "0.9rem",
fontWeight: 600, fontWeight: 600,
marginBottom: "4px", marginBottom: "4px",
}} }}
> >
{t.hourLabelFormat} {t.hourLabelFormat}
<PerViewBadge settingKey="hourLabelFormat" globalVal={hourLabelFormat} />
</label> </label>
<select <select
value={hourLabelFormat} value={hourLabelFormat}
onChange={(e) => { onChange={(e) => {
const fmt = e.target.value as "short" | "full"; const fmt = e.target.value as "short" | "full";
setHourLabelFormat(fmt); setHourLabelFormat(fmt);
if (perView.isPerView("hourLabelFormat")) { perView.saveViewSetting("hourLabelFormat", fmt, true);
perView.saveViewSetting("hourLabelFormat", fmt, true);
} else {
saveSetting("hourLabelFormat", fmt);
}
}} }}
className="weekly-input" className="weekly-input"
style={{ style={{
@ -11224,20 +11211,15 @@ function SettingsSidebar({
checked={showSubHourSlots} checked={showSubHourSlots}
onChange={(e) => { onChange={(e) => {
setShowSubHourSlots(e.target.checked); setShowSubHourSlots(e.target.checked);
if (perView.isPerView("showSubHourSlots")) { perView.saveViewSetting("showSubHourSlots", e.target.checked, true);
perView.saveViewSetting("showSubHourSlots", e.target.checked, true);
} else {
saveSetting("showSubHourSlots", e.target.checked);
}
}} }}
style={{ width: "16px", height: "16px" }} style={{ width: "16px", height: "16px" }}
/> />
<label <label
htmlFor="showSubHourSlots" htmlFor="showSubHourSlots"
style={{ fontSize: "0.9rem", fontWeight: 600, display: "flex", alignItems: "center" }} style={{ fontSize: "0.9rem", fontWeight: 600 }}
> >
{t.showSubhourLabels} {t.showSubhourLabels}
<PerViewBadge settingKey="showSubHourSlots" globalVal={showSubHourSlots} />
</label> </label>
</div> </div>
</div> </div>
@ -11246,66 +11228,6 @@ function SettingsSidebar({
<div style={{ marginTop: "16px" }}>
<label
style={{
display: "block",
fontSize: "0.9rem",
fontWeight: 600,
marginBottom: "8px",
}}
>
{t.viewStyle}
</label>
<div
className="flex items-center gap-1 bg-gray-100 dark:bg-gray-800 rounded p-1"
style={{ width: "fit-content" }}
>
<button
onClick={() => {
setViewStyle("simple");
setShowTimeGrid(true);
saveSetting("viewStyle", "simple");
saveSetting("showTimeGrid", true);
}}
className={`px-4 py-2 text-sm rounded transition-colors ${showTimeGrid && viewStyle === "simple" ? "bg-white shadow-sm font-bold text-black" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`}
>
{t.simpleView}
</button>
<button
onClick={() => {
setViewStyle("calendar");
setShowTimeGrid(true);
saveSetting("viewStyle", "calendar");
saveSetting("showTimeGrid", true);
}}
className={`px-4 py-2 text-sm rounded transition-colors ${showTimeGrid && viewStyle === "calendar" ? "bg-white shadow-sm font-bold text-black" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`}
>
{t.calendarView}
</button>
<button
onClick={() => {
setViewStyle("list");
setShowTimeGrid(false);
saveSetting("viewStyle", "list");
saveSetting("showTimeGrid", false);
}}
className={`px-4 py-2 text-sm rounded transition-colors ${!showTimeGrid && viewStyle === "list" ? "bg-white shadow-sm font-bold text-black" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`}
>
{t.listView}
</button>
<button
onClick={() => {
setViewStyle("kanban");
saveSetting("viewStyle", "kanban");
}}
className={`px-4 py-2 text-sm rounded transition-colors ${viewStyle === "kanban" ? "bg-white shadow-sm font-bold text-black" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`}
>
{t.kanbanView}
</button>
</div>
</div>
{/* Header Display */} {/* Header Display */}
<div style={{ marginTop: "16px" }}> <div style={{ marginTop: "16px" }}>
<label style={{ fontSize: "0.85rem", fontWeight: 600, color: "var(--weekly-settings-label)" }}> <label style={{ fontSize: "0.85rem", fontWeight: 600, color: "var(--weekly-settings-label)" }}>
@ -11368,17 +11290,12 @@ function SettingsSidebar({
checked={profile.weatherEnabled || false} checked={profile.weatherEnabled || false}
onChange={(e) => { onChange={(e) => {
setProfile({ ...profile, weatherEnabled: e.target.checked }); setProfile({ ...profile, weatherEnabled: e.target.checked });
if (perView.isPerView("weatherEnabled")) { perView.saveViewSetting("weatherEnabled", e.target.checked, true);
perView.saveViewSetting("weatherEnabled", e.target.checked, true);
} else {
saveSetting("weatherEnabled", e.target.checked);
}
}} }}
style={{ width: "18px", height: "18px" }} style={{ width: "18px", height: "18px" }}
/> />
<span style={{ fontSize: "0.85rem", fontWeight: 500, display: "flex", alignItems: "center" }}> <span style={{ fontSize: "0.85rem", fontWeight: 500 }}>
{profile.language === "de" ? "Wetter anzeigen" : "Show weather"} {profile.language === "de" ? "Wetter anzeigen" : "Show weather"}
<PerViewBadge settingKey="weatherEnabled" globalVal={profile.weatherEnabled} />
</span> </span>
</label> </label>
{profile.weatherEnabled && ( {profile.weatherEnabled && (