chore: release v1.17.0 - Reorganized settings, added Localisation tab, and refined UI layout
This commit is contained in:
parent
2ec09fdcca
commit
77daa6e30b
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "my-weekly-todo-list",
|
||||
"version": "1.16.1",
|
||||
"version": "1.17.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": {
|
||||
|
||||
@ -383,9 +383,18 @@ export async function GET(req: NextRequest) {
|
||||
// Create new local tasks from remote
|
||||
const somedayListInfo = synoListIdToSomedayList.get(listId);
|
||||
if (somedayListInfo) {
|
||||
// Also check ALL synology tasks in DB (not just this list) to avoid duplicates
|
||||
const allSynoIds = await prisma.task.findMany({
|
||||
where: { userId: user.id, externalProvider: 'synology', externalId: { not: null }, deletedAt: null },
|
||||
select: { externalId: true }
|
||||
});
|
||||
const allExistingIds = new Set(allSynoIds.map(t => t.externalId!.replace(/^synology::/, '')));
|
||||
// Merge with the per-list set
|
||||
for (const id of existingExternalIds) { if (id) allExistingIds.add(id); }
|
||||
|
||||
const newRemoteTasks = remoteTasks.filter(rt => {
|
||||
const cleanId = rt.id.replace(/^synology::/, '');
|
||||
return !existingExternalIds.has(cleanId);
|
||||
return !allExistingIds.has(cleanId);
|
||||
});
|
||||
|
||||
for (const remote of newRemoteTasks) {
|
||||
|
||||
@ -126,7 +126,7 @@ export default function SimpleDatePicker({ selected, onSelect, onClose, language
|
||||
borderRadius: '0.5rem',
|
||||
boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
|
||||
padding: '1rem',
|
||||
zIndex: 50,
|
||||
zIndex: 2000,
|
||||
width: '18rem',
|
||||
border: '1px solid #f3f4f6',
|
||||
animation: 'fadeIn 0.15s ease-out'
|
||||
|
||||
@ -58,6 +58,7 @@ import {
|
||||
X,
|
||||
Cable,
|
||||
Link,
|
||||
Globe,
|
||||
} from "lucide-react";
|
||||
|
||||
// Types
|
||||
@ -222,6 +223,7 @@ const translations: Record<string, any> = {
|
||||
settings: "Settings",
|
||||
general: "General",
|
||||
calendar: "Connections",
|
||||
localisation: "Localisation",
|
||||
account: "Account",
|
||||
runningList: "Running List (Auto-roll tasks to today)",
|
||||
protectEventTimes: "Protect Event Times",
|
||||
@ -303,11 +305,19 @@ const translations: Record<string, any> = {
|
||||
noProjects: "No projects yet",
|
||||
assignProject: "Assign project",
|
||||
removeProject: "Remove project",
|
||||
weekdayFormat: "Weekday Format",
|
||||
weekdayFormatFull: "Full Name (Monday)",
|
||||
weekdayFormatShort: "Short (Mon)",
|
||||
weekdayFormatNarrow: "Narrow (M)",
|
||||
weekdayFormatCustom: "Custom",
|
||||
customWeekdayNamesMon: "Mo; Tu; We; Th; Fr; Sa; Su",
|
||||
customWeekdayNamesSun: "Su; Mo; Tu; We; Th; Fr; Sa",
|
||||
},
|
||||
de: {
|
||||
settings: "Einstellungen",
|
||||
general: "Allgemein",
|
||||
calendar: "Verbindungen",
|
||||
localisation: "Lokalisierung",
|
||||
account: "Konto",
|
||||
runningList: "Laufende Liste (Aufgaben automatisch auf heute verschieben)",
|
||||
protectEventTimes: "Ereigniszeiten schützen",
|
||||
@ -390,6 +400,13 @@ const translations: Record<string, any> = {
|
||||
noProjects: "Noch keine Projekte",
|
||||
assignProject: "Projekt zuweisen",
|
||||
removeProject: "Projekt entfernen",
|
||||
weekdayFormat: "Wochentag-Format",
|
||||
weekdayFormatFull: "Vollständiger Name (Montag)",
|
||||
weekdayFormatShort: "Kurz (Mo)",
|
||||
weekdayFormatNarrow: "Schmal (M)",
|
||||
weekdayFormatCustom: "Benutzerdefiniert",
|
||||
customWeekdayNamesMon: "Mo; Di; Mi; Do; Fr; Sa; So",
|
||||
customWeekdayNamesSun: "So; Mo; Di; Mi; Do; Fr; Sa",
|
||||
},
|
||||
};
|
||||
|
||||
@ -421,8 +438,23 @@ function formatDateHeader(date: Date, locale: string = "en-US"): string {
|
||||
return date.toLocaleDateString(locale, { day: "numeric", month: "short" }); // e.g. 12. Feb.
|
||||
}
|
||||
|
||||
function getDayName(date: Date, locale: string = "en-US"): string {
|
||||
return date.toLocaleDateString(locale, { weekday: "long" }).toUpperCase();
|
||||
function getDayName(date: Date, locale: string = "en-US", format?: string, customNames?: string, weekStartDay: number = 0): string {
|
||||
if (format === "custom" && customNames) {
|
||||
// Split by comma or semicolon to allow spaces in names
|
||||
const names = customNames.split(/[,;]+/).map(s => s.trim()).filter(Boolean);
|
||||
if (names.length === 7) {
|
||||
// Adjust index based on weekStartDay (0=Sun, 1=Mon)
|
||||
const index = (date.getDay() - weekStartDay + 7) % 7;
|
||||
return names[index].toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
const weekdayOption = format === "narrow" ? "narrow" : (format === "short" ? "short" : "long");
|
||||
try {
|
||||
return date.toLocaleDateString(locale, { weekday: weekdayOption }).toUpperCase();
|
||||
} catch (e) {
|
||||
return date.toLocaleDateString("en-US", { weekday: weekdayOption }).toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
function isSameDay(d1: Date, d2: Date): boolean {
|
||||
@ -761,6 +793,8 @@ export default function WeeklyView() {
|
||||
showTaskCheckboxes?: boolean;
|
||||
quoteSourceUrls?: string[];
|
||||
startDayOffset?: number;
|
||||
weekdayFormat?: "long" | "short" | "narrow" | "custom";
|
||||
customWeekdayNames?: string;
|
||||
}>({
|
||||
name: session?.user?.name || "",
|
||||
email: session?.user?.email || "",
|
||||
@ -874,6 +908,8 @@ export default function WeeklyView() {
|
||||
const [showFocusMode, setShowFocusMode] = useState(false);
|
||||
const [showSchedule, setShowSchedule] = useState(true);
|
||||
const [focusBreakDuration, setFocusBreakDuration] = useState(5);
|
||||
const [weekdayFormat, setWeekdayFormat] = useState<"long" | "short" | "narrow" | "custom">("long");
|
||||
const [customWeekdayNames, setCustomWeekdayNames] = useState("");
|
||||
|
||||
// New UI State
|
||||
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
||||
@ -1545,6 +1581,8 @@ export default function WeeklyView() {
|
||||
setShowSomeday(newSettings.showSomeday);
|
||||
setShowAllDay(newSettings.showAllDayEvents);
|
||||
setShowSchedule(newSettings.showSchedule);
|
||||
if (newSettings.weekdayFormat) setWeekdayFormat(newSettings.weekdayFormat);
|
||||
if (newSettings.customWeekdayNames !== undefined) setCustomWeekdayNames(newSettings.customWeekdayNames);
|
||||
setHeadlineFont(newSettings.headlineFont);
|
||||
setHeadlineFontSize(newSettings.headlineFontSize);
|
||||
setHeadlineFontWeight(newSettings.headlineFontWeight);
|
||||
@ -1626,6 +1664,12 @@ export default function WeeklyView() {
|
||||
else if (width <= 1024) setViewDays(Math.min(v, 5));
|
||||
else setViewDays(v);
|
||||
}
|
||||
if (data.user.weekdayFormat) {
|
||||
setProfile((prev: any) => ({ ...prev, weekdayFormat: data.user.weekdayFormat }));
|
||||
}
|
||||
if (data.user.customWeekdayNames) {
|
||||
setProfile((prev: any) => ({ ...prev, customWeekdayNames: data.user.customWeekdayNames }));
|
||||
}
|
||||
const cookieCellDuration = getCookie("setting_cellDuration");
|
||||
if (cookieCellDuration) setCellDuration(Number(cookieCellDuration) as CellDuration);
|
||||
const cookieStartHour = getCookie("setting_startHour");
|
||||
@ -1643,6 +1687,10 @@ export default function WeeklyView() {
|
||||
setShowAllDay(data.user.showAllDayEvents);
|
||||
if (data.user.showSchedule !== undefined)
|
||||
setShowSchedule(data.user.showSchedule);
|
||||
if (data.user.weekdayFormat)
|
||||
setWeekdayFormat(data.user.weekdayFormat as any);
|
||||
if (data.user.customWeekdayNames)
|
||||
setCustomWeekdayNames(data.user.customWeekdayNames);
|
||||
if (data.user.hourLabelFormat)
|
||||
setHourLabelFormat(data.user.hourLabelFormat as "short" | "full");
|
||||
if (data.user.showSubHourSlots !== undefined)
|
||||
@ -4748,11 +4796,12 @@ export default function WeeklyView() {
|
||||
style={{
|
||||
visibility: "hidden", pointerEvents: "none",
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
alignItems:
|
||||
activeDateLayout === "above" ||
|
||||
activeDateLayout === "below"
|
||||
? (profile.dateAlignment === "left" ? "flex-start" : profile.dateAlignment === "right" ? "flex-end" : "center")
|
||||
: "baseline",
|
||||
: "center",
|
||||
justifyContent:
|
||||
profile.dateAlignment === "left"
|
||||
? "flex-start"
|
||||
@ -4836,11 +4885,12 @@ export default function WeeklyView() {
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
alignItems:
|
||||
activeDateLayout === "above" ||
|
||||
activeDateLayout === "below"
|
||||
? (profile.dateAlignment === "left" ? "flex-start" : profile.dateAlignment === "right" ? "flex-end" : "center")
|
||||
: "baseline",
|
||||
: "center",
|
||||
justifyContent:
|
||||
profile.dateAlignment === "left"
|
||||
? "flex-start"
|
||||
@ -4857,21 +4907,21 @@ export default function WeeklyView() {
|
||||
}}
|
||||
>
|
||||
{activeDateLayout === "left" && (
|
||||
<span className="weekly-day-date">
|
||||
<span className="weekly-day-date" style={{ flexShrink: 0 }}>
|
||||
{formatDateHeader(date, language)}
|
||||
</span>
|
||||
)}
|
||||
<h3
|
||||
className={`weekly-day-name ${isSameDay(date, new Date()) ? "is-today" : ""}`}
|
||||
style={{ marginBottom: 0 }}
|
||||
style={{ marginBottom: 0, flexShrink: 0 }}
|
||||
>
|
||||
{getDayName(date, language)}
|
||||
{getDayName(date, language, weekdayFormat, customWeekdayNames, weekStartDay)}
|
||||
</h3>
|
||||
{(activeDateLayout === "right" ||
|
||||
activeDateLayout === "above" ||
|
||||
activeDateLayout === "below" ||
|
||||
activeDateLayout === undefined) && (
|
||||
<span className="weekly-day-date">
|
||||
<span className="weekly-day-date" style={{ flexShrink: 0 }}>
|
||||
{formatDateHeader(date, language)}
|
||||
</span>
|
||||
)}
|
||||
@ -7007,6 +7057,12 @@ function TaskItem({
|
||||
>
|
||||
{task.title}
|
||||
</span>
|
||||
{/* DEBUG: always show provider info */}
|
||||
{!isSomeday && (
|
||||
<span style={{ color: task.externalProvider ? "red" : "orange", fontWeight: "bold", fontSize: "11px", marginLeft: "4px", flexShrink: 0 }}>
|
||||
[{task.externalProvider || "no-ext"}]
|
||||
</span>
|
||||
)}
|
||||
{task.externalProvider && !isSomeday && (
|
||||
<span
|
||||
className="flex-shrink-0"
|
||||
@ -7641,6 +7697,8 @@ interface SettingsSidebarProps {
|
||||
setShowSchedule: (show: boolean) => void;
|
||||
dateLayout?: "above" | "below" | "left" | "right" | "hidden";
|
||||
mobileDateLayout?: "above" | "below" | "left" | "right" | "hidden";
|
||||
weekdayFormat?: "long" | "short" | "narrow" | "custom";
|
||||
customWeekdayNames?: string;
|
||||
dateAlignment?: "left" | "center" | "right" | "tight";
|
||||
hourLabelFormat: "short" | "full";
|
||||
setHourLabelFormat: (fmt: "short" | "full") => void;
|
||||
@ -7880,7 +7938,7 @@ function SettingsSidebar({
|
||||
onProjectsChanged,
|
||||
}: SettingsSidebarProps) {
|
||||
const [activeTab, setActiveTab] = useState<
|
||||
"calendar" | "general" | "account" | "styling" | "motivation" | "about"
|
||||
"calendar" | "general" | "account" | "styling" | "motivation" | "about" | "localisation"
|
||||
>(initialTab || "general");
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isSyncing, setIsSyncing] = useState(false);
|
||||
@ -8013,6 +8071,8 @@ function SettingsSidebar({
|
||||
quoteSourceUrls?: string[];
|
||||
startDayOffset?: number;
|
||||
id?: string;
|
||||
weekdayFormat?: "long" | "short" | "narrow" | "custom";
|
||||
customWeekdayNames?: string;
|
||||
}>({
|
||||
name: "",
|
||||
email: "",
|
||||
@ -8066,6 +8126,9 @@ function SettingsSidebar({
|
||||
dateColor: "#888888",
|
||||
taskColor: "#333333",
|
||||
todayHighlightColor: "#f0fafa",
|
||||
pastDayColor: "#a6a6a7",
|
||||
weekdayFormat: "long",
|
||||
customWeekdayNames: "",
|
||||
});
|
||||
|
||||
const t = translations[profile.language || "en"] || translations["en"];
|
||||
@ -8175,6 +8238,8 @@ function SettingsSidebar({
|
||||
dayHeaderGap: profile.dayHeaderGap,
|
||||
showTaskCheckboxes: profile.showTaskCheckboxes,
|
||||
startDayOffset: profile.startDayOffset,
|
||||
weekdayFormat: profile.weekdayFormat,
|
||||
customWeekdayNames: profile.customWeekdayNames,
|
||||
} as any);
|
||||
}, [profile, showTimeGrid, cellDuration, viewStyle, fontSize, showNextTask, showSomeday, showAllDay, showSchedule]);
|
||||
|
||||
@ -8211,6 +8276,8 @@ function SettingsSidebar({
|
||||
? data.user.showTimeGrid
|
||||
: true,
|
||||
cellDuration: data.user.cellDuration || 30,
|
||||
weekdayFormat: data.user.weekdayFormat || "long",
|
||||
customWeekdayNames: data.user.customWeekdayNames || "",
|
||||
viewStyle: data.user.viewStyle || "list",
|
||||
fontSize: data.user.fontSize || "M",
|
||||
headlineFont: data.user.headlineFont || "Inter",
|
||||
@ -8671,6 +8738,7 @@ function SettingsSidebar({
|
||||
>
|
||||
{([
|
||||
{ key: "general", icon: <Settings size={18} />, label: t.general },
|
||||
{ key: "localisation", icon: <Globe size={18} />, label: t.localisation },
|
||||
{ key: "calendar", icon: <Link size={18} />, label: t.calendar },
|
||||
{ key: "account", icon: <User size={18} />, label: t.account },
|
||||
{ key: "styling", icon: <Palette size={18} />, label: "Styling" },
|
||||
@ -8999,88 +9067,64 @@ function SettingsSidebar({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hour Label Format */}
|
||||
<div style={{ marginTop: "4px" }}>
|
||||
<label
|
||||
style={{
|
||||
display: "block",
|
||||
fontSize: "0.9rem",
|
||||
fontWeight: 600,
|
||||
marginBottom: "4px",
|
||||
}}
|
||||
>
|
||||
Hour Label Format
|
||||
</label>
|
||||
<select
|
||||
value={hourLabelFormat}
|
||||
onChange={(e) => {
|
||||
const fmt = e.target.value as "short" | "full";
|
||||
setHourLabelFormat(fmt);
|
||||
saveSetting("hourLabelFormat", fmt);
|
||||
}}
|
||||
className="weekly-input"
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: "8px",
|
||||
border: "1px solid #ddd",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
>
|
||||
<option value="short">Short (8, 9, 10)</option>
|
||||
<option value="full">Full (8:00, 9:00, 10:00)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Sub-hour Slot Labels */}
|
||||
<div
|
||||
style={{ display: "flex", alignItems: "center", gap: "8px", marginTop: "8px" }}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="showSubHourSlots"
|
||||
checked={showSubHourSlots}
|
||||
onChange={(e) => {
|
||||
setShowSubHourSlots(e.target.checked);
|
||||
saveSetting("showSubHourSlots", e.target.checked);
|
||||
}}
|
||||
style={{ width: "16px", height: "16px" }}
|
||||
/>
|
||||
<label
|
||||
htmlFor="showSubHourSlots"
|
||||
style={{ fontSize: "0.9rem", fontWeight: 600 }}
|
||||
>
|
||||
Show Sub-hour Labels (:15, :30, :45)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
style={{
|
||||
borderTop: "1px solid #eee",
|
||||
marginTop: "16px",
|
||||
paddingTop: "16px",
|
||||
}}
|
||||
></div>
|
||||
<h4 style={{ fontSize: "1rem", fontWeight: 600, margin: 0 }}>
|
||||
{t.localization}
|
||||
</h4>
|
||||
|
||||
{/* Start Week Setting + Start View On */}
|
||||
<div style={{ display: "flex", gap: "24px", flexWrap: "wrap" }}>
|
||||
<div>
|
||||
<label
|
||||
style={{
|
||||
display: "block",
|
||||
fontSize: "0.9rem",
|
||||
fontWeight: 600,
|
||||
marginBottom: "4px",
|
||||
}}
|
||||
>
|
||||
{profile.language === "de" ? "Woche beginnt am" : "Start week on"}
|
||||
</label>
|
||||
<div style={{ display: "flex", gap: "8px" }}>
|
||||
<button
|
||||
className={`px-3 py-2 rounded text-sm ${weekStartDay === 1 ? "bg-blue-600 text-white" : "bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300"}`}
|
||||
onClick={() => setWeekStartDay(1)}
|
||||
>
|
||||
{profile.language === "de" ? "Montag" : "Monday"}
|
||||
</button>
|
||||
<button
|
||||
className={`px-3 py-2 rounded text-sm ${weekStartDay === 0 ? "bg-blue-600 text-white" : "bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300"}`}
|
||||
onClick={() => setWeekStartDay(0)}
|
||||
>
|
||||
{profile.language === "de" ? "Sonntag" : "Sunday"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
style={{
|
||||
display: "block",
|
||||
fontSize: "0.9rem",
|
||||
fontWeight: 600,
|
||||
marginBottom: "4px",
|
||||
}}
|
||||
>
|
||||
{profile.language === "de" ? "Ansicht beginnt mit" : "Start view on"}
|
||||
</label>
|
||||
<div style={{ display: "flex", gap: "8px" }}>
|
||||
<button
|
||||
className={`px-3 py-2 rounded text-sm ${(profile.startDayOffset || 0) === 0 ? "bg-blue-600 text-white" : "bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300"}`}
|
||||
onClick={() => {
|
||||
setProfile({ ...profile, startDayOffset: 0 });
|
||||
saveSetting("startDayOffset", 0);
|
||||
const d = new Date();
|
||||
d.setHours(0, 0, 0, 0);
|
||||
setCurrentWeekStart(d);
|
||||
}}
|
||||
>
|
||||
{profile.language === "de" ? "Heute" : "Today"}
|
||||
</button>
|
||||
<button
|
||||
className={`px-3 py-2 rounded text-sm ${profile.startDayOffset === -1 ? "bg-blue-600 text-white" : "bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300"}`}
|
||||
onClick={() => {
|
||||
setProfile({ ...profile, startDayOffset: -1 });
|
||||
saveSetting("startDayOffset", -1);
|
||||
const d = new Date();
|
||||
d.setHours(0, 0, 0, 0);
|
||||
d.setDate(d.getDate() - 1);
|
||||
setCurrentWeekStart(d);
|
||||
}}
|
||||
>
|
||||
{profile.language === "de" ? "Gestern" : "Yesterday"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div style={{ marginTop: "16px" }}>
|
||||
@ -9135,150 +9179,6 @@ function SettingsSidebar({
|
||||
</div>
|
||||
|
||||
|
||||
<div style={{ marginTop: "16px" }}>
|
||||
<label
|
||||
style={{
|
||||
display: "block",
|
||||
fontSize: "0.9rem",
|
||||
fontWeight: 600,
|
||||
marginBottom: "4px",
|
||||
}}
|
||||
>
|
||||
{t.language}
|
||||
</label>
|
||||
<select
|
||||
value={profile.language}
|
||||
onChange={(e) =>
|
||||
setProfile({ ...profile, language: e.target.value })
|
||||
}
|
||||
className="weekly-input"
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: "8px",
|
||||
border: "1px solid #ddd",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
>
|
||||
<option value="en">English</option>
|
||||
<option value="de">German</option>
|
||||
<option value="fr">French</option>
|
||||
<option value="es">Spanish</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
style={{
|
||||
display: "block",
|
||||
fontSize: "0.9rem",
|
||||
fontWeight: 600,
|
||||
marginBottom: "4px",
|
||||
}}
|
||||
>
|
||||
{t.dateFormat}
|
||||
</label>
|
||||
<select
|
||||
value={profile.dateFormat}
|
||||
onChange={(e) =>
|
||||
setProfile({ ...profile, dateFormat: e.target.value })
|
||||
}
|
||||
className="weekly-input"
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: "8px",
|
||||
border: "1px solid #ddd",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
>
|
||||
<option value="MM/dd/yyyy">MM/DD/YYYY</option>
|
||||
<option value="dd/MM/yyyy">DD/MM/YYYY</option>
|
||||
<option value="yyyy-MM-dd">YYYY-MM-DD</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
style={{
|
||||
display: "block",
|
||||
fontSize: "0.9rem",
|
||||
fontWeight: 600,
|
||||
marginBottom: "4px",
|
||||
}}
|
||||
>
|
||||
{t.timeFormat}
|
||||
</label>
|
||||
<select
|
||||
value={profile.timeFormat}
|
||||
onChange={(e) =>
|
||||
setProfile({ ...profile, timeFormat: e.target.value })
|
||||
}
|
||||
className="weekly-input"
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: "8px",
|
||||
border: "1px solid #ddd",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
>
|
||||
<option value="12h">12h AM/PM</option>
|
||||
<option value="24h">24H</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Hour Label Format */}
|
||||
<div>
|
||||
<label
|
||||
style={{
|
||||
display: "block",
|
||||
fontSize: "0.9rem",
|
||||
fontWeight: 600,
|
||||
marginBottom: "4px",
|
||||
}}
|
||||
>
|
||||
Hour Label Format
|
||||
</label>
|
||||
<select
|
||||
value={hourLabelFormat}
|
||||
onChange={(e) => {
|
||||
const fmt = e.target.value as "short" | "full";
|
||||
setHourLabelFormat(fmt);
|
||||
saveSetting("hourLabelFormat", fmt);
|
||||
}}
|
||||
className="weekly-input"
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: "8px",
|
||||
border: "1px solid #ddd",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
>
|
||||
<option value="short">Short (8, 9, 10)</option>
|
||||
<option value="full">Full (8:00, 9:00, 10:00)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Sub-hour Slot Labels */}
|
||||
<div
|
||||
style={{ display: "flex", alignItems: "center", gap: "8px" }}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="showSubHourSlots"
|
||||
checked={showSubHourSlots}
|
||||
onChange={(e) => {
|
||||
setShowSubHourSlots(e.target.checked);
|
||||
saveSetting("showSubHourSlots", e.target.checked);
|
||||
}}
|
||||
style={{ width: "16px", height: "16px" }}
|
||||
/>
|
||||
<label
|
||||
htmlFor="showSubHourSlots"
|
||||
style={{ fontSize: "0.9rem", fontWeight: 600 }}
|
||||
>
|
||||
Show Sub-hour Labels (:15, :30, :45)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Projects Section */}
|
||||
<div style={{ marginTop: "24px", borderTop: "1px solid var(--border-color, #e5e7eb)", paddingTop: "16px" }}>
|
||||
<h4 style={{ fontSize: "0.95rem", fontWeight: 700, marginBottom: "8px", display: "flex", alignItems: "center", gap: "6px" }}>
|
||||
@ -9429,6 +9329,222 @@ function SettingsSidebar({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : activeTab === "localisation" ? (
|
||||
<div
|
||||
style={{ display: "flex", flexDirection: "column", gap: "20px" }}
|
||||
>
|
||||
<h4 style={{ fontSize: "1rem", fontWeight: 600, margin: 0 }}>
|
||||
{t.localisation || "Localisation"}
|
||||
</h4>
|
||||
|
||||
{/* Start Week Setting + Start View On */}
|
||||
<div style={{ display: "flex", gap: "24px", flexWrap: "wrap" }}>
|
||||
<div>
|
||||
<label
|
||||
style={{
|
||||
display: "block",
|
||||
fontSize: "0.9rem",
|
||||
fontWeight: 600,
|
||||
marginBottom: "4px",
|
||||
}}
|
||||
>
|
||||
{profile.language === "de" ? "Woche beginnt am" : "Start week on"}
|
||||
</label>
|
||||
<div style={{ display: "flex", gap: "8px" }}>
|
||||
<button
|
||||
className={`px-3 py-2 rounded text-sm ${weekStartDay === 1 ? "bg-blue-600 text-white" : "bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300"}`}
|
||||
onClick={() => setWeekStartDay(1)}
|
||||
>
|
||||
{profile.language === "de" ? "Montag" : "Monday"}
|
||||
</button>
|
||||
<button
|
||||
className={`px-3 py-2 rounded text-sm ${weekStartDay === 0 ? "bg-blue-600 text-white" : "bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300"}`}
|
||||
onClick={() => setWeekStartDay(0)}
|
||||
>
|
||||
{profile.language === "de" ? "Sonntag" : "Sunday"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
style={{
|
||||
display: "block",
|
||||
fontSize: "0.9rem",
|
||||
fontWeight: 600,
|
||||
marginBottom: "4px",
|
||||
}}
|
||||
>
|
||||
{profile.language === "de" ? "Ansicht beginnt mit" : "Start view on"}
|
||||
</label>
|
||||
<div style={{ display: "flex", gap: "8px" }}>
|
||||
<button
|
||||
className={`px-3 py-2 rounded text-sm ${(profile.startDayOffset || 0) === 0 ? "bg-blue-600 text-white" : "bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300"}`}
|
||||
onClick={() => {
|
||||
setProfile({ ...profile, startDayOffset: 0 });
|
||||
saveSetting("startDayOffset", 0);
|
||||
const d = new Date();
|
||||
d.setHours(0, 0, 0, 0);
|
||||
setCurrentWeekStart(d);
|
||||
}}
|
||||
>
|
||||
{profile.language === "de" ? "Heute" : "Today"}
|
||||
</button>
|
||||
<button
|
||||
className={`px-3 py-2 rounded text-sm ${profile.startDayOffset === -1 ? "bg-blue-600 text-white" : "bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300"}`}
|
||||
onClick={() => {
|
||||
setProfile({ ...profile, startDayOffset: -1 });
|
||||
saveSetting("startDayOffset", -1);
|
||||
const d = new Date();
|
||||
d.setHours(0, 0, 0, 0);
|
||||
d.setDate(d.getDate() - 1);
|
||||
setCurrentWeekStart(d);
|
||||
}}
|
||||
>
|
||||
{profile.language === "de" ? "Gestern" : "Yesterday"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Weekday Format */}
|
||||
<div style={{ marginTop: "8px" }}>
|
||||
<label
|
||||
style={{
|
||||
display: "block",
|
||||
fontSize: "0.9rem",
|
||||
fontWeight: 600,
|
||||
marginBottom: "8px",
|
||||
}}
|
||||
>
|
||||
{t.weekdayFormat || translations["en"].weekdayFormat}
|
||||
</label>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
||||
<select
|
||||
value={profile.weekdayFormat || "long"}
|
||||
onChange={(e) =>
|
||||
setProfile({
|
||||
...profile,
|
||||
weekdayFormat: e.target.value as any,
|
||||
})
|
||||
}
|
||||
className="weekly-input"
|
||||
style={{ width: "100%", padding: "8px", fontSize: "0.9rem", border: "1px solid var(--weekly-settings-input-border)", borderRadius: "4px", background: "var(--weekly-settings-input-bg)", color: "var(--weekly-settings-text)" }}
|
||||
>
|
||||
<option value="long">{t.weekdayFormatFull || translations["en"].weekdayFormatFull}</option>
|
||||
<option value="short">{t.weekdayFormatShort || translations["en"].weekdayFormatShort}</option>
|
||||
<option value="narrow">{t.weekdayFormatNarrow || translations["en"].weekdayFormatNarrow}</option>
|
||||
<option value="custom">{t.weekdayFormatCustom || translations["en"].weekdayFormatCustom}</option>
|
||||
</select>
|
||||
|
||||
{profile.weekdayFormat === "custom" && (
|
||||
<input
|
||||
type="text"
|
||||
value={profile.customWeekdayNames || ""}
|
||||
onChange={(e) => setProfile({ ...profile, customWeekdayNames: e.target.value })}
|
||||
placeholder={
|
||||
weekStartDay === 1
|
||||
? (t.customWeekdayNamesMon || translations["en"].customWeekdayNamesMon)
|
||||
: (t.customWeekdayNamesSun || translations["en"].customWeekdayNamesSun)
|
||||
}
|
||||
className="weekly-input"
|
||||
style={{ width: "100%", padding: "8px", fontSize: "0.85rem", border: "1px solid var(--weekly-settings-input-border)", borderRadius: "4px", background: "var(--weekly-settings-input-bg)", color: "var(--weekly-settings-text)" }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: "8px" }}>
|
||||
<label
|
||||
style={{
|
||||
display: "block",
|
||||
fontSize: "0.9rem",
|
||||
fontWeight: 600,
|
||||
marginBottom: "4px",
|
||||
}}
|
||||
>
|
||||
{t.language}
|
||||
</label>
|
||||
<select
|
||||
value={profile.language}
|
||||
onChange={(e) =>
|
||||
setProfile({ ...profile, language: e.target.value })
|
||||
}
|
||||
className="weekly-input"
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: "8px",
|
||||
border: "1px solid #ddd",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
>
|
||||
<option value="en">English</option>
|
||||
<option value="de">German</option>
|
||||
<option value="fr">French</option>
|
||||
<option value="es">Spanish</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
style={{
|
||||
display: "block",
|
||||
fontSize: "0.9rem",
|
||||
fontWeight: 600,
|
||||
marginBottom: "4px",
|
||||
}}
|
||||
>
|
||||
{t.dateFormat}
|
||||
</label>
|
||||
<select
|
||||
value={profile.dateFormat}
|
||||
onChange={(e) =>
|
||||
setProfile({ ...profile, dateFormat: e.target.value })
|
||||
}
|
||||
className="weekly-input"
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: "8px",
|
||||
border: "1px solid #ddd",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
>
|
||||
<option value="MM/dd/yyyy">MM/DD/YYYY</option>
|
||||
<option value="dd/MM/yyyy">DD/MM/YYYY</option>
|
||||
<option value="yyyy-MM-dd">YYYY-MM-DD</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
style={{
|
||||
display: "block",
|
||||
fontSize: "0.9rem",
|
||||
fontWeight: 600,
|
||||
marginBottom: "4px",
|
||||
}}
|
||||
>
|
||||
{t.timeFormat}
|
||||
</label>
|
||||
<select
|
||||
value={profile.timeFormat}
|
||||
onChange={(e) =>
|
||||
setProfile({ ...profile, timeFormat: e.target.value })
|
||||
}
|
||||
className="weekly-input"
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: "8px",
|
||||
border: "1px solid #ddd",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
>
|
||||
<option value="12h">12h AM/PM</option>
|
||||
<option value="24h">24H</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div />
|
||||
</div>
|
||||
) : activeTab === "calendar" ? (
|
||||
isLoading ? (
|
||||
<p>Loading connections...</p>
|
||||
@ -10190,9 +10306,9 @@ function SettingsSidebar({
|
||||
type="text"
|
||||
value={profile.headlineFontSize || "1.25rem"}
|
||||
onChange={(e) => setProfile({ ...profile, headlineFontSize: e.target.value })}
|
||||
placeholder="1.25rem"
|
||||
placeholder="Font size (e.g. 1.25rem)"
|
||||
className="weekly-input"
|
||||
style={{ flex: 1, padding: "8px", fontSize: "0.9rem", border: "1px solid var(--weekly-settings-input-border)", borderRadius: "4px", background: "var(--weekly-settings-input-bg)", color: "var(--weekly-settings-text)" }}
|
||||
style={{ flex: 1, padding: "8px", fontSize: "0.85rem", border: "1px solid var(--weekly-settings-input-border)", borderRadius: "4px", background: "var(--weekly-settings-input-bg)", color: "var(--weekly-settings-text)" }}
|
||||
/>
|
||||
<select
|
||||
value={profile.headlineFontWeight || "900"}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user