feat: Refactor view styles (Simple, Calendar, List) and global renaming

- Implemented Simple view (no borders, added spacing).
- Updated Calendar view to Google Calendar style.
- Renamed TeuxDeux view to List.
- Replaced 'teuxdeux' branding globally.
- Fixed horizontal scrollbars in Simple view.
- Updated global border color.
This commit is contained in:
mARTin 2026-02-16 11:39:19 +01:00
parent 72e62ead4a
commit 3e7b87428a
4 changed files with 200 additions and 44 deletions

View File

@ -449,7 +449,7 @@ h3 {
--weekly-bg: #ffffff; --weekly-bg: #ffffff;
--weekly-text: #000000; --weekly-text: #000000;
--weekly-text-light: #a6a6a7; --weekly-text-light: #a6a6a7;
--weekly-border: #eeeeee; --weekly-border: #cccccc;
--weekly-completed: #cccccc; --weekly-completed: #cccccc;
--weekly-font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; --weekly-font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
@ -635,6 +635,7 @@ h3 {
min-height: 400px; min-height: 400px;
max-height: calc(100vh - 200px); max-height: calc(100vh - 200px);
overflow-y: auto; overflow-y: auto;
overflow-x: hidden;
} }
.weekly-day-column:first-child { .weekly-day-column:first-child {
@ -1726,6 +1727,7 @@ h3 {
.time-grid-wrapper .time-slots-container { .time-grid-wrapper .time-slots-container {
overflow-y: auto; overflow-y: auto;
overflow-x: hidden;
} }
/* All-Day Events Section */ /* All-Day Events Section */
@ -2579,3 +2581,150 @@ h3 {
font-size: 0.7rem; font-size: 0.7rem;
opacity: 0.9; opacity: 0.9;
} }
/* Header Icons */
.weekly-btn-icon {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
padding: 0;
border: 1px solid #e0e0e0;
background-color: white;
color: #555;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
}
.weekly-btn-icon:hover {
background-color: #f3f4f6;
border-color: #d1d5db;
color: #111;
}
.weekly-btn-icon.active {
background-color: #e5e7eb;
border-color: #9ca3af;
color: #000;
}
.dark-mode .weekly-btn-icon {
background-color: #1f2937;
border-color: #374151;
color: #9ca3af;
}
.dark-mode .weekly-btn-icon:hover {
background-color: #374151;
color: #e5e7eb;
}
.dark-mode .weekly-btn-icon.active {
background-color: #4b5563;
color: #fff;
}
/* Header Hover Reveal */
.group:hover .transition-opacity {
opacity: 1;
}
.group .transition-opacity {
opacity: 0;
}
/* List View (formerly Grid/App View - no vertical lines) */
.list-view.time-grid-off .weekly-day-column {
border-right: none !important;
}
/* Calendar View (Google Calendar Style - vertical lines) */
.calendar-view.time-grid-on .weekly-day-column {
border-right: 1px solid #e0e0e0;
margin: 0 !important;
}
.dark-mode .calendar-view.time-grid-on .weekly-day-column {
border-right: 1px solid #333;
}
/* Simple View (Original Style with Spacing) */
.simple-view .weekly-day-column {
border-left: 0px !important;
margin: 0 10px !important;
overflow-x: hidden !important;
}
.simple-view .time-column {
border-right: 0px !important;
}
.simple-view .time-slot-event {
left: 0 !important;
right: 0 !important;
}
/* Scrollbar Hiding for All-Day Events */
.all-day-events-column {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
}
.all-day-events-column::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera */
}
/* Responsive Design */
@media (max-width: 768px) {
/* Mobile/Tablet adjustment */
.weekly-header {
flex-direction: column;
height: auto;
padding: 10px;
gap: 10px;
}
.weekly-header > div {
width: 100%;
justify-content: center;
}
/* Fix overlap on mobile by removing absolute positioning */
.weekly-header .absolute {
position: static !important;
transform: none !important;
left: auto !important;
margin: 5px 0;
}
.weekly-btn-icon {
width: 36px;
height: 36px;
}
/* Stack days vertically on mobile */
.list-view .weekly-days-grid {
grid-template-columns: 1fr !important;
overflow-y: auto;
}
.time-grid-wrapper {
flex-direction: column;
height: calc(100vh - 120px); /* Adjust for header */
}
.weekly-day-column {
min-height: auto;
border-right: none !important;
border-bottom: 1px solid #ccc;
}
/* Ensure sidebar is scrollable and accessible */
.weekly-settings-sidebar {
width: 100%;
max-width: 100vw;
}
}

View File

@ -193,7 +193,7 @@ export default function DatePicker({ selected, onSelect, onClose, language = 'en
margin-top: 8px; margin-top: 8px;
background: white; background: white;
border-radius: 8px; border-radius: 8px;
border-bottom: 5px solid #00ceb0; /* TeuxDeuxish Teal/Green */ border-bottom: 5px solid #00ceb0; /* Weekly Teal/Green */
box-shadow: 0 10px 25px rgba(0,0,0,0.1); box-shadow: 0 10px 25px rgba(0,0,0,0.1);
padding: 20px; padding: 20px;
z-index: 1000; z-index: 1000;

View File

@ -92,7 +92,7 @@ export default function TaskItem({ task, onToggleComplete, onDelete, onEdit, onT
<div className={containerClass}> <div className={containerClass}>
<div className={`flex items-start ${variant === 'minimal' ? 'w-full' : ''}`}> <div className={`flex items-start ${variant === 'minimal' ? 'w-full' : ''}`}>
<div className={`flex items-center mr-3 ${variant === 'minimal' ? '' : 'mt-1'} space-x-2`}> <div className={`flex items-center mr-3 ${variant === 'minimal' ? '' : 'mt-1'} space-x-2`}>
{/* Rolling Toggle - hide in minimal unless hovered or active? Or keep it? TeuxDeux usually has verify simple. Keep it for now. */} {/* Rolling Toggle - hide in minimal unless hovered or active? Or keep it? App usually has verify simple. Keep it for now. */}
<button <button
onClick={() => onToggleRolling(task.id)} onClick={() => onToggleRolling(task.id)}
className={`p-1 rounded-full hover:bg-gray-100 transition-colors ${task.isRolling ? 'text-blue-600' : 'text-gray-300'}`} className={`p-1 rounded-full hover:bg-gray-100 transition-colors ${task.isRolling ? 'text-blue-600' : 'text-gray-300'}`}

View File

@ -22,7 +22,9 @@ import {
Sun, Sun,
Moon, Moon,
Repeat, Repeat,
GripVertical GripVertical,
Play,
Zap
} from 'lucide-react'; } from 'lucide-react';
// Types // Types
@ -31,6 +33,8 @@ import SearchModal from './SearchModal';
import SimpleDatePicker from './SimpleDatePicker'; import SimpleDatePicker from './SimpleDatePicker';
import RecurringTasksManager from './RecurringTasksManager'; import RecurringTasksManager from './RecurringTasksManager';
export type ViewStyle = 'simple' | 'calendar' | 'list';
interface Task { interface Task {
id: string; id: string;
title: string; title: string;
@ -132,8 +136,9 @@ const translations: Record<string, any> = {
showTimeGrid: 'Show Time Grid', showTimeGrid: 'Show Time Grid',
timeSlotDuration: 'Time Slot Duration', timeSlotDuration: 'Time Slot Duration',
viewStyle: 'View Style', viewStyle: 'View Style',
gridView: 'Grid View', simpleView: 'Simple',
listView: 'List View', calendarView: 'Calendar',
listView: 'List',
language: 'Language', language: 'Language',
dateFormat: 'Date Format', dateFormat: 'Date Format',
timeFormat: 'Time Format', timeFormat: 'Time Format',
@ -179,8 +184,9 @@ const translations: Record<string, any> = {
showTimeGrid: 'Zeitplan anzeigen', showTimeGrid: 'Zeitplan anzeigen',
timeSlotDuration: 'Zeitfensterdauer', timeSlotDuration: 'Zeitfensterdauer',
viewStyle: 'Ansichtsstil', viewStyle: 'Ansichtsstil',
gridView: 'Rasteransicht', simpleView: 'Einfach',
listView: 'Listenansicht', calendarView: 'Kalender',
listView: 'Liste',
language: 'Sprache', language: 'Sprache',
dateFormat: 'Datumsformat', dateFormat: 'Datumsformat',
timeFormat: 'Zeitformat', timeFormat: 'Zeitformat',
@ -483,7 +489,7 @@ export default function WeeklyView() {
const [selectedTaskForNotes, setSelectedTaskForNotes] = useState<Task | null>(null); const [selectedTaskForNotes, setSelectedTaskForNotes] = useState<Task | null>(null);
const [currentTime, setCurrentTime] = useState(new Date()); const [currentTime, setCurrentTime] = useState(new Date());
const [dropPreview, setDropPreview] = useState<{ day: number; slot: string } | null>(null); const [dropPreview, setDropPreview] = useState<{ day: number; slot: string } | null>(null);
const [viewStyle, setViewStyle] = useState<'grid' | 'list'>('list'); const [viewStyle, setViewStyle] = useState<ViewStyle>('simple');
const [protectEventTimes, setProtectEventTimes] = useState(true); const [protectEventTimes, setProtectEventTimes] = useState(true);
const [unlockedEvents, setUnlockedEvents] = useState<Set<string>>(new Set()); const [unlockedEvents, setUnlockedEvents] = useState<Set<string>>(new Set());
@ -853,7 +859,7 @@ export default function WeeklyView() {
if (data.user.startHour !== undefined) setStartHour(data.user.startHour); if (data.user.startHour !== undefined) setStartHour(data.user.startHour);
if (data.user.endHour !== undefined) setEndHour(data.user.endHour); if (data.user.endHour !== undefined) setEndHour(data.user.endHour);
if (data.user.viewStyle !== undefined) { if (data.user.viewStyle !== undefined) {
setViewStyle(data.user.viewStyle as 'grid' | 'list'); setViewStyle(data.user.viewStyle as ViewStyle);
setShowTimeGrid(data.user.showTimeGrid ?? true); setShowTimeGrid(data.user.showTimeGrid ?? true);
} }
if (data.user.viewDays !== undefined) setViewDays(data.user.viewDays); if (data.user.viewDays !== undefined) setViewDays(data.user.viewDays);
@ -900,7 +906,7 @@ export default function WeeklyView() {
if (data.user.focusBreakDuration) setFocusBreakDuration(data.user.focusBreakDuration); if (data.user.focusBreakDuration) setFocusBreakDuration(data.user.focusBreakDuration);
if (data.user.showTimeGrid !== undefined) setShowTimeGrid(data.user.showTimeGrid); if (data.user.showTimeGrid !== undefined) setShowTimeGrid(data.user.showTimeGrid);
if (data.user.cellDuration) setCellDuration(data.user.cellDuration as CellDuration); if (data.user.cellDuration) setCellDuration(data.user.cellDuration as CellDuration);
if (data.user.viewStyle) setViewStyle(data.user.viewStyle as 'grid' | 'list'); if (data.user.viewStyle) setViewStyle(data.user.viewStyle as ViewStyle);
} }
} }
} catch (e) { } catch (e) {
@ -1846,7 +1852,7 @@ export default function WeeklyView() {
} }
return ( return (
<div className={`weekly-container ${darkMode ? 'dark-mode' : ''} font-size-${fontSize.toLowerCase()} ${viewStyle}-view`} style={containerStyle}> <div className={`weekly-container ${darkMode ? 'dark-mode' : ''} font-size-${fontSize.toLowerCase()} ${viewStyle}-view ${showTimeGrid ? 'time-grid-on' : 'time-grid-off'}`} style={containerStyle}>
{/* View Transitions Style Block */} {/* View Transitions Style Block */}
<style dangerouslySetInnerHTML={{ <style dangerouslySetInnerHTML={{
__html: (() => { __html: (() => {
@ -1871,9 +1877,9 @@ export default function WeeklyView() {
}} /> }} />
{/* Refactored Header: Left, Center, Right */} {/* Refactored Header: Left, Center, Right */}
<header className="flex items-center justify-between w-full px-4 py-2 border-b border-gray-200 bg-white dark:bg-gray-900 dark:border-gray-700 dark:text-white transition-colors duration-200"> <header className="group flex items-center justify-between w-full px-4 py-2 border-b border-gray-200 bg-white dark:bg-gray-900 dark:border-gray-700 dark:text-white transition-colors duration-200">
{/* LEFT SECTION: Slot Duration & Days to Show */} {/* LEFT SECTION: Slot Duration & Days to Show */}
<div className="flex items-center gap-4"> <div className="flex items-center gap-4 transition-opacity duration-300 opacity-0 group-hover:opacity-100">
{/* Slot Duration */} {/* Slot Duration */}
{showTimeGrid && ( {showTimeGrid && (
<div className="flex items-center gap-1 bg-gray-100 dark:bg-gray-800 rounded p-1" title="Slot Duration"> <div className="flex items-center gap-1 bg-gray-100 dark:bg-gray-800 rounded p-1" title="Slot Duration">
@ -1948,32 +1954,32 @@ export default function WeeklyView() {
<div className="flex items-center gap-1 bg-gray-100 dark:bg-gray-800 rounded p-1" title="View Style"> <div className="flex items-center gap-1 bg-gray-100 dark:bg-gray-800 rounded p-1" title="View Style">
<button <button
onClick={() => { onClick={() => {
const newVal = !showTimeGrid; setViewStyle('simple');
setShowTimeGrid(newVal); setShowTimeGrid(true);
if (newVal) setViewStyle('list'); // Default to list if grid is enabled saveSetting('viewStyle', 'simple');
saveSetting('showTimeGrid', newVal); saveSetting('showTimeGrid', true);
}} }}
className={`px-2 py-0.5 text-xs rounded transition-colors ${showTimeGrid ? 'bg-white shadow-sm font-bold text-black' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700'}`} className={`px-2 py-0.5 text-xs 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'}`}
> >
Time Grid Simple
</button> </button>
<button <button
onClick={() => { onClick={() => {
setViewStyle('grid'); setViewStyle('calendar');
setShowTimeGrid(false); setShowTimeGrid(true);
saveSetting('viewStyle', 'grid'); saveSetting('viewStyle', 'calendar');
saveSetting('showTimeGrid', false); saveSetting('showTimeGrid', true);
}} }}
className={`px-2 py-0.5 text-xs rounded transition-colors ${!showTimeGrid && viewStyle === 'grid' ? 'bg-white shadow-sm font-bold text-black' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700'}`} className={`px-2 py-0.5 text-xs 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'}`}
> >
Grid Calendar
</button> </button>
<button <button
onClick={() => { onClick={() => {
setViewStyle('list'); setViewStyle('list');
if (!showTimeGrid) { setShowTimeGrid(false);
saveSetting('viewStyle', 'list'); saveSetting('viewStyle', 'list');
} saveSetting('showTimeGrid', false);
}} }}
className={`px-2 py-0.5 text-xs 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'}`} className={`px-2 py-0.5 text-xs 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'}`}
> >
@ -1982,7 +1988,7 @@ export default function WeeklyView() {
</div> </div>
</div> </div>
{/* CENTER SECTION: Week/Year, Motto, Focus Mode */} {/* CENTER SECTION: Week/Year, Motto, Focus Mode - Reveal on Hover */}
<div className="flex items-center justify-center gap-6 absolute left-1/2 transform -translate-x-1/2"> <div className="flex items-center justify-center gap-6 absolute left-1/2 transform -translate-x-1/2">
{/* Week & Year */} {/* Week & Year */}
<div className="text-lg font-bold whitespace-nowrap"> <div className="text-lg font-bold whitespace-nowrap">
@ -2036,28 +2042,26 @@ export default function WeeklyView() {
</div> </div>
{/* RIGHT SECTION: Navigation & Tools */} {/* RIGHT SECTION: Navigation & Tools */}
<div className="flex items-center gap-3"> <div className="flex items-center gap-3 transition-opacity duration-300 opacity-0 group-hover:opacity-100">
<button <button
onClick={() => { onClick={() => {
const newVal = !showNextTask; const newVal = !showNextTask;
setShowNextTask(newVal); setShowNextTask(newVal);
saveSetting('showNextTask', newVal); saveSetting('showNextTask', newVal);
}} }}
className={`weekly-btn-secondary ${showNextTask ? 'active' : ''}`} className={`weekly-btn-icon ${showNextTask ? 'active' : ''}`}
title={showNextTask ? "Showing Next Task" : "Showing Motto"} title={showNextTask ? "Showing Next Task" : "Showing Motto"}
> >
<Target size={14} className={showNextTask ? 'text-teal-500' : 'text-gray-400'} /> {showNextTask ? <Play size={18} className="text-teal-600" /> : <Target size={18} className="text-gray-400" />}
Now DO THIS
</button> </button>
{/* Focus Mode Toggle */} {/* Focus Mode Toggle */}
<button <button
onClick={() => setShowFocusMode(true)} onClick={() => setShowFocusMode(true)}
className="weekly-btn-secondary" className="weekly-btn-icon"
title="Enter Focus Mode" title="Enter Focus Mode"
> >
<Target size={14} /> <Zap size={18} className="text-gray-600 hover:text-yellow-500 transition-colors" />
<span>Focus Mode</span>
</button> </button>
{/* Day/Night Mode Switch */} {/* Day/Night Mode Switch */}
@ -3675,8 +3679,8 @@ interface SettingsSidebarProps {
setCellDuration: (duration: CellDuration) => void; setCellDuration: (duration: CellDuration) => void;
weekStartDay: number; weekStartDay: number;
setWeekStartDay: (day: number) => void; setWeekStartDay: (day: number) => void;
viewStyle: 'grid' | 'list'; viewStyle: ViewStyle;
setViewStyle: (style: 'grid' | 'list') => void; setViewStyle: (style: ViewStyle) => void;
showSomeday: boolean; showSomeday: boolean;
setShowSomeday: (show: boolean) => void; setShowSomeday: (show: boolean) => void;
showAllDay: boolean; showAllDay: boolean;
@ -3933,7 +3937,7 @@ function SettingsSidebar({
if (data.user.showTimeGrid !== undefined) setShowTimeGrid(data.user.showTimeGrid); if (data.user.showTimeGrid !== undefined) setShowTimeGrid(data.user.showTimeGrid);
if (data.user.cellDuration) setCellDuration(data.user.cellDuration as CellDuration); if (data.user.cellDuration) setCellDuration(data.user.cellDuration as CellDuration);
if (data.user.viewStyle) setViewStyle(data.user.viewStyle as 'grid' | 'list'); if (data.user.viewStyle) setViewStyle(data.user.viewStyle as ViewStyle);
if (data.user.fontSize) setFontSize(data.user.fontSize as 'S' | 'M' | 'L'); if (data.user.fontSize) setFontSize(data.user.fontSize as 'S' | 'M' | 'L');
if (data.user.focusTimerDuration) setFocusTimerDuration(data.user.focusTimerDuration); if (data.user.focusTimerDuration) setFocusTimerDuration(data.user.focusTimerDuration);
if (data.user.showSchedule !== undefined) setShowSchedule(data.user.showSchedule); if (data.user.showSchedule !== undefined) setShowSchedule(data.user.showSchedule);
@ -4020,8 +4024,9 @@ function SettingsSidebar({
showSchedule: showSchedule, showSchedule: showSchedule,
// The following will be taken from profile if present, // The following will be taken from profile if present,
// ensuring edited state is saved. // ensuring edited state is saved.
focusBreakDuration: profile.focusBreakDuration || focusBreakDuration, // Validate numeric fields to avoid NaN
focusTimerDuration: profile.focusTimerDuration || focusTimerDuration, focusBreakDuration: !isNaN(Number(profile.focusBreakDuration)) ? Number(profile.focusBreakDuration) : (focusBreakDuration || 5),
focusTimerDuration: !isNaN(Number(profile.focusTimerDuration)) ? Number(profile.focusTimerDuration) : (focusTimerDuration || 25),
password: (passwords.new && passwords.new.trim() !== "") ? passwords.new : undefined password: (passwords.new && passwords.new.trim() !== "") ? passwords.new : undefined
}) })
}); });
@ -4088,6 +4093,7 @@ function SettingsSidebar({
// Temporary success message // Temporary success message
setTimeout(() => setAccountMsg(''), 3000); setTimeout(() => setAccountMsg(''), 3000);
} else { } else {
console.error('Failed to update profile:', data);
setAccountMsg(data.error || 'Failed to update profile'); setAccountMsg(data.error || 'Failed to update profile');
if (data.details) console.error('Update profile details:', data.details); if (data.details) console.error('Update profile details:', data.details);
} }
@ -4264,11 +4270,12 @@ function SettingsSidebar({
<label style={{ display: 'block', fontSize: '0.9rem', fontWeight: 600, marginBottom: '4px' }}>{t.viewStyle}</label> <label style={{ display: 'block', fontSize: '0.9rem', fontWeight: 600, marginBottom: '4px' }}>{t.viewStyle}</label>
<select <select
value={viewStyle} value={viewStyle}
onChange={(e) => setViewStyle(e.target.value as 'grid' | 'list')} onChange={(e) => setViewStyle(e.target.value as ViewStyle)}
className="weekly-input" className="weekly-input"
style={{ width: '100%', padding: '8px', border: '1px solid #ddd', borderRadius: '4px' }} style={{ width: '100%', padding: '8px', border: '1px solid #ddd', borderRadius: '4px' }}
> >
<option value="grid">{t.gridView}</option> <option value="simple">{t.simpleView}</option>
<option value="calendar">{t.calendarView}</option>
<option value="list">{t.listView}</option> <option value="list">{t.listView}</option>
</select> </select>
</div> </div>