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",
"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",
"main": "index.js",
"scripts": {

View File

@ -8514,10 +8514,7 @@ export default function WeeklyView() {
onEndHourChange: (h: number) => { setEndHour(h); saveSetting("endHour", h); },
}}
perView={{
isPerView: isPerView as any,
togglePerView: togglePerView 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;
};
perView: {
isPerView: (key: string) => boolean;
togglePerView: (key: string, globalVal: any) => void;
saveViewSetting: (key: string, value: any, perView: boolean) => void;
viewLabel: string;
};
}
// Notes Sidebar Component
@ -10286,31 +10280,6 @@ function SettingsSidebar({
mobileActions,
perView,
}: 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<
"calendar" | "general" | "account" | "styling" | "motivation" | "about" | "localisation"
>(initialTab || "general");
@ -10837,6 +10806,33 @@ function SettingsSidebar({
<div
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
style={{ display: "flex", alignItems: "center", gap: "8px" }}
@ -10960,18 +10956,15 @@ function SettingsSidebar({
checked={profile.showTaskCheckboxes || false}
onChange={(e) => {
setProfile({ ...profile, showTaskCheckboxes: e.target.checked });
if (perView.isPerView("showTaskCheckboxes")) {
perView.saveViewSetting("showTaskCheckboxes", e.target.checked, true);
}
}}
style={{ width: "16px", height: "16px" }}
/>
<label
htmlFor="showTaskCheckboxes"
style={{ fontSize: "0.9rem", fontWeight: 600, display: "flex", alignItems: "center" }}
style={{ fontSize: "0.9rem", fontWeight: 600 }}
>
{t.showTaskCheckboxes}
<PerViewBadge settingKey="showTaskCheckboxes" globalVal={profile.showTaskCheckboxes} />
</label>
</div>
<div
@ -11180,26 +11173,20 @@ function SettingsSidebar({
<div style={{ marginTop: "4px" }}>
<label
style={{
display: "flex",
alignItems: "center",
display: "block",
fontSize: "0.9rem",
fontWeight: 600,
marginBottom: "4px",
}}
>
{t.hourLabelFormat}
<PerViewBadge settingKey="hourLabelFormat" globalVal={hourLabelFormat} />
</label>
<select
value={hourLabelFormat}
onChange={(e) => {
const fmt = e.target.value as "short" | "full";
setHourLabelFormat(fmt);
if (perView.isPerView("hourLabelFormat")) {
perView.saveViewSetting("hourLabelFormat", fmt, true);
} else {
saveSetting("hourLabelFormat", fmt);
}
}}
className="weekly-input"
style={{
@ -11224,20 +11211,15 @@ function SettingsSidebar({
checked={showSubHourSlots}
onChange={(e) => {
setShowSubHourSlots(e.target.checked);
if (perView.isPerView("showSubHourSlots")) {
perView.saveViewSetting("showSubHourSlots", e.target.checked, true);
} else {
saveSetting("showSubHourSlots", e.target.checked);
}
}}
style={{ width: "16px", height: "16px" }}
/>
<label
htmlFor="showSubHourSlots"
style={{ fontSize: "0.9rem", fontWeight: 600, display: "flex", alignItems: "center" }}
style={{ fontSize: "0.9rem", fontWeight: 600 }}
>
{t.showSubhourLabels}
<PerViewBadge settingKey="showSubHourSlots" globalVal={showSubHourSlots} />
</label>
</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 */}
<div style={{ marginTop: "16px" }}>
<label style={{ fontSize: "0.85rem", fontWeight: 600, color: "var(--weekly-settings-label)" }}>
@ -11368,17 +11290,12 @@ function SettingsSidebar({
checked={profile.weatherEnabled || false}
onChange={(e) => {
setProfile({ ...profile, weatherEnabled: e.target.checked });
if (perView.isPerView("weatherEnabled")) {
perView.saveViewSetting("weatherEnabled", e.target.checked, true);
} else {
saveSetting("weatherEnabled", e.target.checked);
}
}}
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"}
<PerViewBadge settingKey="weatherEnabled" globalVal={profile.weatherEnabled} />
</span>
</label>
{profile.weatherEnabled && (