@@ -430,6 +430,7 @@ <h1>Timesheet</h1>
430430const SPREADSHEET_ID = "1FTzC7-1Eqj7G-y7fiG6Hq8EIk24EwCC_IQIo0qFZglY" ;
431431const SHEET_NAME = "Data" ; // <-- set to your tab name exactly
432432const SCOPES = "https://www.googleapis.com/auth/spreadsheets" ; // read+write
433+ const SHEET_HEADER = [ "month" , "date" , "start" , "end" , "duty" , "lunch" , "hours" , "updatedAt" ] ;
433434
434435let accessToken = null ;
435436let tokenClient = null ;
@@ -484,6 +485,21 @@ <h1>Timesheet</h1>
484485 return res . json ( ) ;
485486}
486487
488+ function hasHeaderRow ( values ) {
489+ const firstRow = values [ 0 ] || [ ] ;
490+ return SHEET_HEADER . every ( ( h , i ) => String ( firstRow [ i ] || "" ) . toLowerCase ( ) === h ) ;
491+ }
492+
493+ async function ensureHeaderRow ( values ) {
494+ if ( values . length ) return values ;
495+ const headerRange = encodeURIComponent ( `${ SHEET_NAME } !A1:H1` ) ;
496+ await sheetsFetch ( `values/${ headerRange } ?valueInputOption=RAW` , {
497+ method : "PUT" ,
498+ body : JSON . stringify ( { values : [ SHEET_HEADER ] } )
499+ } ) ;
500+ return [ SHEET_HEADER ] ;
501+ }
502+
487503// ====== YOUR APP HOOKS ======
488504// Implement these two functions in your app:
489505// - getMonthDataForSync(monthKey) -> array of row objects for that month
@@ -493,12 +509,47 @@ <h1>Timesheet</h1>
493509// { month:"2026-03", date:"2026-03-01", start:"08:15", end:"14:45", duty:"Kitchen", lunch:"0.5", hours:"5.75" }
494510
495511function getMonthDataForSync ( monthKey ) {
496- // TODO: return your local month rows
497- return [ ] ;
512+ const month = store . months [ monthKey ] ;
513+ if ( ! month ?. rows ) return [ ] ;
514+ return month . rows
515+ . filter ( r => r . date )
516+ . map ( r => ( {
517+ month : monthKey ,
518+ date : r . date || "" ,
519+ start : r . start || "" ,
520+ end : r . end || "" ,
521+ duty : r . duty || "" ,
522+ lunch : r . lunch ?? "" ,
523+ hours : fmt2 ( calcHours ( r . start , r . end , r . lunch ) )
524+ } ) ) ;
498525}
499526
500527function applyMonthDataFromSync ( monthKey , rows ) {
501- // TODO: write rows into local storage + re-render
528+ if ( ! store . months [ monthKey ] ) {
529+ store . months [ monthKey ] = makeBlankMonth ( monthKey ) ;
530+ if ( ! store . monthOrder . includes ( monthKey ) ) store . monthOrder . push ( monthKey ) ;
531+ }
532+
533+ const month = store . months [ monthKey ] ;
534+ const rowByDate = new Map ( rows . map ( r => [ r . date , r ] ) ) ;
535+
536+ month . rows = month . rows . map ( localRow => {
537+ const incoming = rowByDate . get ( localRow . date ) ;
538+ if ( ! incoming ) return localRow ;
539+ return {
540+ ...localRow ,
541+ start : incoming . start || "" ,
542+ end : incoming . end || "" ,
543+ duty : incoming . duty || "" ,
544+ lunch : incoming . lunch ?? localRow . lunch ?? "0.5" ,
545+ day : weekdayShort ( new Date ( localRow . date + "T00:00:00" ) )
546+ } ;
547+ } ) ;
548+
549+ saveStore ( store ) ;
550+ renderMonthOptions ( store , monthKey ) ;
551+ currentMonth = monthKey ;
552+ renderTable ( store , monthKey ) ;
502553}
503554
504555// ====== SYNC DOWN: load month from sheet ======
@@ -510,9 +561,7 @@ <h1>Timesheet</h1>
510561 const range = encodeURIComponent ( `${ SHEET_NAME } !A:H` ) ;
511562 const data = await sheetsFetch ( `values/${ range } ` ) ;
512563 const values = data . values || [ ] ;
513-
514- // Expect header in row 1
515- const body = values . slice ( 1 ) ;
564+ const body = hasHeaderRow ( values ) ? values . slice ( 1 ) : values ;
516565 const filtered = body
517566 . filter ( r => ( r [ 0 ] || "" ) === monthKey )
518567 . map ( r => ( {
@@ -542,20 +591,25 @@ <h1>Timesheet</h1>
542591 // 1) Read entire sheet to find existing rows for that month+date
543592 const rangeAll = encodeURIComponent ( `${ SHEET_NAME } !A:H` ) ;
544593 const data = await sheetsFetch ( `values/${ rangeAll } ` ) ;
545- const values = data . values || [ ] ;
546- const header = values [ 0 ] || [ ] ;
547- const body = values . slice ( 1 ) ;
594+ const values = await ensureHeaderRow ( data . values || [ ] ) ;
595+ const headerExists = hasHeaderRow ( values ) ;
596+ const body = headerExists ? values . slice ( 1 ) : values ;
548597
549598 // Map existing row -> sheet row index (1-based in Sheets; header is row 1)
550599 const indexByKey = new Map ( ) ;
600+ const firstDataRowIndex = headerExists ? 2 : 1 ;
551601 body . forEach ( ( r , i ) => {
552602 const m = r [ 0 ] || "" ;
553603 const d = r [ 1 ] || "" ;
554- if ( m && d ) indexByKey . set ( `${ m } |${ d } ` , i + 2 ) ; // +2 because header row + 1-based
604+ if ( m && d ) indexByKey . set ( `${ m } |${ d } ` , i + firstDataRowIndex ) ;
555605 } ) ;
556606
557607 // 2) Upsert each local row
558608 const localRows = getMonthDataForSync ( monthKey ) ;
609+ if ( ! localRows . length ) {
610+ setSyncStatus ( "No rows to save for this month" ) ;
611+ return ;
612+ }
559613 const now = new Date ( ) . toISOString ( ) ;
560614
561615 for ( const row of localRows ) {
@@ -591,11 +645,11 @@ <h1>Timesheet</h1>
591645 }
592646 }
593647
594- setSyncStatus ( " Saved ✅" ) ;
648+ setSyncStatus ( ` Saved ✅ ( ${ localRows . length } rows)` ) ;
595649 } catch ( e ) {
596650 setSyncStatus ( String ( e . message || e ) ) ;
597651 }
598652} ) ;
599653</ script >
600654</ body >
601- </ html >
655+ </ html >
0 commit comments