@@ -598,62 +598,52 @@ <h1>Assignments</h1>
598598 const DAY_NAMES = [ 'Sunday' , 'Monday' , 'Tuesday' , 'Wednesday' , 'Thursday' , 'Friday' , 'Saturday' ] ;
599599
600600 // ─────────────── Date helpers ───────────────
601- const todayKey = ( ) => {
602- const d = new Date ( ) ;
603- return `${ d . getFullYear ( ) } -${ String ( d . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) } -${ String ( d . getDate ( ) ) . padStart ( 2 , '0' ) } ` ;
604- } ;
601+ const DAY_MS = 86400000 ;
602+ const dateKey = ( d ) => `${ d . getFullYear ( ) } -${ String ( d . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) } -${ String ( d . getDate ( ) ) . padStart ( 2 , '0' ) } ` ;
603+ const parseDateKey = ( s ) => { const [ y , m , d ] = s . split ( '-' ) . map ( Number ) ; return new Date ( y , m - 1 , d ) ; } ;
604+ const todayKey = ( ) => dateKey ( new Date ( ) ) ;
605+ const dateKeyFromTs = ( ts ) => dateKey ( new Date ( ts ) ) ;
606+ function yesterdayKey ( ) {
607+ const d = new Date ( ) ; d . setDate ( d . getDate ( ) - 1 ) ;
608+ return dateKey ( d ) ;
609+ }
605610 function dayDiff ( dateStr ) {
606- // days from today (negative = past). Date-only comparison.
607- const [ y , m , d ] = dateStr . split ( '-' ) . map ( Number ) ;
608- const target = new Date ( y , m - 1 , d ) ;
609- const today = new Date ( ) ;
610- today . setHours ( 0 , 0 , 0 , 0 ) ;
611- target . setHours ( 0 , 0 , 0 , 0 ) ;
612- return Math . round ( ( target - today ) / ( 24 * 60 * 60 * 1000 ) ) ;
611+ // Date-only comparison; negative = past.
612+ const target = parseDateKey ( dateStr ) ; target . setHours ( 0 , 0 , 0 , 0 ) ;
613+ const today = new Date ( ) ; today . setHours ( 0 , 0 , 0 , 0 ) ;
614+ return Math . round ( ( target - today ) / DAY_MS ) ;
613615 }
614616 function dueLabel ( dateStr ) {
615617 const diff = dayDiff ( dateStr ) ;
616618 if ( diff < 0 ) return `${ Math . abs ( diff ) } d overdue` ;
617619 if ( diff === 0 ) return 'Due today' ;
618620 if ( diff === 1 ) return 'Due tomorrow' ;
619621 if ( diff < 7 ) return `Due in ${ diff } days` ;
620- const [ y , m , d ] = dateStr . split ( '-' ) . map ( Number ) ;
621- const dt = new Date ( y , m - 1 , d ) ;
622- return `Due ${ dt . toLocaleDateString ( undefined , { month : 'short' , day : 'numeric' } ) } ` ;
623- }
624- function dateKeyFromTs ( ts ) {
625- const d = new Date ( ts ) ;
626- return `${ d . getFullYear ( ) } -${ String ( d . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) } -${ String ( d . getDate ( ) ) . padStart ( 2 , '0' ) } ` ;
627- }
628- function yesterdayKey ( ) {
629- const d = new Date ( ) ; d . setDate ( d . getDate ( ) - 1 ) ;
630- return dateKeyFromTs ( d . getTime ( ) ) ;
622+ return `Due ${ parseDateKey ( dateStr ) . toLocaleDateString ( undefined , { month : 'short' , day : 'numeric' } ) } ` ;
631623 }
632624 function completionRelative ( ts ) {
633625 if ( ! ts ) return 'Completed' ;
634626 const dk = dateKeyFromTs ( ts ) ;
635627 if ( dk === todayKey ( ) ) return 'Completed today' ;
636628 if ( dk === yesterdayKey ( ) ) return 'Completed yesterday' ;
637629 const d = new Date ( ts ) ;
638- const diffDays = Math . round ( ( Date . now ( ) - ts ) / 86400000 ) ;
630+ const diffDays = Math . round ( ( Date . now ( ) - ts ) / DAY_MS ) ;
639631 if ( diffDays < 7 ) return `Completed ${ d . toLocaleDateString ( undefined , { weekday : 'long' } ) } ` ;
640632 return `Completed ${ d . toLocaleDateString ( undefined , { month : 'short' , day : 'numeric' } ) } ` ;
641633 }
642634 function completionGroupLabel ( key ) {
643635 if ( key === 'earlier' ) return 'Earlier' ;
644636 if ( key === todayKey ( ) ) return 'Today' ;
645637 if ( key === yesterdayKey ( ) ) return 'Yesterday' ;
646- const [ Y , M , D ] = key . split ( '-' ) . map ( Number ) ;
647- const dt = new Date ( Y , M - 1 , D ) ;
648- const diffDays = Math . round ( ( Date . now ( ) - dt . getTime ( ) ) / 86400000 ) ;
638+ const dt = parseDateKey ( key ) ;
639+ const diffDays = Math . round ( ( Date . now ( ) - dt . getTime ( ) ) / DAY_MS ) ;
649640 if ( diffDays < 7 ) return dt . toLocaleDateString ( undefined , { weekday : 'long' } ) ;
650641 return dt . toLocaleDateString ( undefined , { month : 'long' , day : 'numeric' } ) ;
651642 }
652643
653644 function nextOccurrence ( rule , fromDateStr ) {
654645 if ( ! rule ) return null ;
655- const [ y , m , d ] = fromDateStr . split ( '-' ) . map ( Number ) ;
656- const cur = new Date ( y , m - 1 , d ) ;
646+ const cur = parseDateKey ( fromDateStr ) ;
657647 for ( let i = 1 ; i <= 366 ; i ++ ) {
658648 const next = new Date ( cur ) ;
659649 next . setDate ( cur . getDate ( ) + i ) ;
@@ -662,7 +652,7 @@ <h1>Assignments</h1>
662652 if ( rule . kind === 'daily' ) match = true ;
663653 else if ( rule . kind === 'weekly' && rule . days && rule . days . length ) match = rule . days . includes ( dow ) ;
664654 if ( ! match ) continue ;
665- const ds = ` ${ next . getFullYear ( ) } - ${ String ( next . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) } - ${ String ( next . getDate ( ) ) . padStart ( 2 , '0' ) } ` ;
655+ const ds = dateKey ( next ) ;
666656 if ( rule . until && ds > rule . until ) return null ;
667657 return ds ;
668658 }
@@ -759,7 +749,6 @@ <h1>Assignments</h1>
759749 classes . push ( { id : crypto . randomUUID ( ) . slice ( 0 , 8 ) , name : name . slice ( 0 , 40 ) , color : selectedSwatch } ) ;
760750 save ( K_CLASSES , classes ) ;
761751 elClassName . value = '' ;
762- // rotate to next color for convenience
763752 const next = PALETTE [ ( PALETTE . indexOf ( selectedSwatch ) + 1 ) % PALETTE . length ] ;
764753 selectedSwatch = next ;
765754 renderAll ( ) ;
@@ -790,7 +779,7 @@ <h1>Assignments</h1>
790779 b . title = DAY_NAMES [ i ] ;
791780 b . addEventListener ( 'click' , ( ) => {
792781 if ( formRepeatDays . includes ( i ) ) formRepeatDays = formRepeatDays . filter ( x => x !== i ) ;
793- else formRepeatDays = [ ...formRepeatDays , i ] . sort ( ) ;
782+ else formRepeatDays = [ ...formRepeatDays , i ] . sort ( ( a , b ) => a - b ) ;
794783 renderDayPills ( ) ;
795784 } ) ;
796785 elRepeatDays . appendChild ( b ) ;
@@ -801,8 +790,7 @@ <h1>Assignments</h1>
801790 elRepeatDays . style . display = v === 'weekly' ? '' : 'none' ;
802791 elRepeatUntilWrap . style . display = v !== 'none' ? '' : 'none' ;
803792 if ( v === 'weekly' && formRepeatDays . length === 0 && elAddDue . value ) {
804- const [ y , m , d ] = elAddDue . value . split ( '-' ) . map ( Number ) ;
805- if ( y ) formRepeatDays = [ new Date ( y , m - 1 , d ) . getDay ( ) ] ;
793+ formRepeatDays = [ parseDateKey ( elAddDue . value ) . getDay ( ) ] ;
806794 renderDayPills ( ) ;
807795 }
808796 }
@@ -825,8 +813,10 @@ <h1>Assignments</h1>
825813 elAddSubmit . disabled = false ;
826814 }
827815 }
828- // default due date = today
829816 elAddDue . value = todayKey ( ) ;
817+ elAddDue . addEventListener ( 'focus' , ( ) => {
818+ if ( ! editingId && ! elAddDue . value ) elAddDue . value = todayKey ( ) ;
819+ } ) ;
830820
831821 function resetForm ( ) {
832822 editingId = null ;
@@ -921,7 +911,6 @@ <h1>Assignments</h1>
921911 c . addEventListener ( 'click' , ( ) => { filter . status = key ; save ( K_FILTER , filter ) ; renderAll ( ) ; } ) ;
922912 elFilters . appendChild ( c ) ;
923913 } ) ;
924- // divider via spacing
925914 if ( classes . length ) {
926915 const sep = document . createElement ( 'span' ) ; sep . style . width = '1px' ; sep . style . alignSelf = 'stretch' ; sep . style . background = 'var(--line)' ; sep . style . margin = '0 .25rem' ;
927916 elFilters . appendChild ( sep ) ;
@@ -966,6 +955,7 @@ <h1>Assignments</h1>
966955
967956 function renderList ( ) {
968957 const xs = filteredItems ( ) ;
958+ const classMap = new Map ( classes . map ( c => [ c . id , c ] ) ) ;
969959 elList . innerHTML = '' ;
970960 if ( xs . length === 0 ) {
971961 const e = document . createElement ( 'div' ) ;
@@ -991,21 +981,19 @@ <h1>Assignments</h1>
991981 header . className = 'group-header' ;
992982 header . textContent = completionGroupLabel ( k ) ;
993983 elList . appendChild ( header ) ;
994- groups . get ( k ) . forEach ( it => elList . appendChild ( renderItem ( it ) ) ) ;
984+ groups . get ( k ) . forEach ( it => elList . appendChild ( renderItem ( it , classMap ) ) ) ;
995985 } ) ;
996986 } else {
997- xs . forEach ( it => elList . appendChild ( renderItem ( it ) ) ) ;
987+ xs . forEach ( it => elList . appendChild ( renderItem ( it , classMap ) ) ) ;
998988 }
999989 elListMeta . textContent = xs . length ? `${ xs . length } shown` : '' ;
1000990 }
1001991
1002- function renderItem ( it ) {
1003- const cls = classes . find ( c => c . id === it . classId ) ;
992+ function renderItem ( it , classMap ) {
993+ const cls = classMap . get ( it . classId ) ;
1004994 const u = urgencyClass ( it ) ;
1005995 const row = document . createElement ( 'div' ) ;
1006996 row . className = 'item ' + u ;
1007- if ( cls ) row . style . setProperty ( '--class-color' , cls . color ) ;
1008-
1009997 const check = document . createElement ( 'button' ) ;
1010998 check . className = 'item-check' ;
1011999 check . title = it . status === 'done' ? 'Mark as not done' : 'Mark done' ;
@@ -1111,9 +1099,9 @@ <h1>Assignments</h1>
11111099 del . textContent = '×' ;
11121100 del . title = 'Delete' ;
11131101 del . addEventListener ( 'click' , ( ) => {
1114- const snapshot = JSON . parse ( JSON . stringify ( items ) ) ;
1102+ const index = items . indexOf ( it ) ;
11151103 const wasRecurring = ! ! it . recurrence && ! it . spawnedChild ;
1116- items = items . filter ( x => x . id !== it . id ) ;
1104+ items . splice ( index , 1 ) ;
11171105 save ( K_ITEMS , items ) ;
11181106 renderAll ( ) ;
11191107 const msg = wasRecurring
@@ -1122,7 +1110,7 @@ <h1>Assignments</h1>
11221110 showToast ( msg , {
11231111 duration : 5000 ,
11241112 action : { label : 'Undo' , onClick : ( ) => {
1125- items = snapshot ;
1113+ items . splice ( Math . min ( index , items . length ) , 0 , it ) ;
11261114 save ( K_ITEMS , items ) ;
11271115 renderAll ( ) ;
11281116 } }
@@ -1146,11 +1134,14 @@ <h1>Assignments</h1>
11461134 const elStats = document . getElementById ( 'stats-row' ) ;
11471135 const elDashSub = document . getElementById ( 'dash-sub' ) ;
11481136 function renderStats ( ) {
1149- const open = items . filter ( i => i . status !== 'done' ) ;
1150- const overdue = open . filter ( i => dayDiff ( i . due ) < 0 ) . length ;
1151- const today = open . filter ( i => dayDiff ( i . due ) === 0 ) . length ;
1152- const week = open . filter ( i => { const d = dayDiff ( i . due ) ; return d > 0 && d < 7 ; } ) . length ;
1153- const doneEver = items . filter ( i => i . status === 'done' ) . length ;
1137+ let overdue = 0 , today = 0 , week = 0 , doneEver = 0 ;
1138+ for ( const i of items ) {
1139+ if ( i . status === 'done' ) { doneEver ++ ; continue ; }
1140+ const d = dayDiff ( i . due ) ;
1141+ if ( d < 0 ) overdue ++ ;
1142+ else if ( d === 0 ) today ++ ;
1143+ else if ( d < 7 ) week ++ ;
1144+ }
11541145 elStats . innerHTML = '' ;
11551146 [
11561147 [ overdue , 'Overdue' , 'overdue' ] ,
@@ -1168,10 +1159,8 @@ <h1>Assignments</h1>
11681159
11691160 // ─────────────── Pomodoro handoff ───────────────
11701161 function sendToPomodoro ( it ) {
1171- // load pomo state (or start a fresh one for today)
11721162 const today = todayKey ( ) ;
1173- let pomo ;
1174- try { pomo = JSON . parse ( localStorage . getItem ( K_POMO ) || 'null' ) ; } catch { pomo = null ; }
1163+ let pomo = load ( K_POMO , null ) ;
11751164 if ( ! pomo || pomo . date !== today ) {
11761165 pomo = {
11771166 date : today , queue : [ ] , currentIdx : 0 , phase : 'idle' ,
@@ -1188,9 +1177,8 @@ <h1>Assignments</h1>
11881177 plannedMins : mins ,
11891178 sourceAssignmentId : it . id
11901179 } ) ;
1191- localStorage . setItem ( K_POMO , JSON . stringify ( pomo ) ) ;
1180+ save ( K_POMO , pomo ) ;
11921181
1193- // mark as in progress
11941182 if ( it . status === 'todo' ) {
11951183 it . status = 'doing' ;
11961184 save ( K_ITEMS , items ) ;
0 commit comments