Skip to content

Conversation

@OneChanceLance
Copy link
Contributor

Description

Fixed the name edits with per-field debounced saves, overhauled the visit list to track drafts and flag blanks or duplicate weeks with warning messages, and fixed week changing so the picker no longer flickers.

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • Any dependent changes have been merged and published in downstream modules

@rhahao
Copy link
Member

rhahao commented Oct 4, 2025

@vercel
Copy link

vercel bot commented Oct 4, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Updated (UTC)
staging-organized-app Ready Ready Preview Nov 11, 2025 2:17am
test-organized-app Ready Ready Preview Nov 11, 2025 2:17am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 4, 2025

Walkthrough

Adds nullable DatePicker value handling plus error/helperText props; updates toolbar to handle null dates. Refactors Circuit Overseer to per-field debounced saves with editing-state sync. Reworks weeks list to manage drafts, per-item validation (required/duplicate), item-level change/delete handlers. Adds Vite /api proxy and sets VITE_APP_MODE to "TEST".

Changes

Cohort / File(s) Summary
Env & Build Config
\.env.example, vite.config.ts
Set VITE_APP_MODE to "TEST". Replace simple server config with a full server object (port 4050, host: true) and add a proxy '/api'http://localhost:8000 (changeOrigin: true, secure: false).
DatePicker Nullability & UI Props
src/components/date_picker/index.tsx, src/components/date_picker/index.types.ts, src/components/date_picker/slots/toolbar.tsx
DatePicker accepts `value: Date
Circuit Overseer: Debounced Field Saves
src/features/congregation/settings/circuit_overseer/useCircuitOverseer.tsx
Replace single timer with per-field saveTimers, add editing state, scheduleSave/clearTimer helpers, and per-field DB update handlers. Sync external settings only for non-editing fields; cleanup timers on unmount. Exposes displayNameEnabled.
CO Week Item Enhancements
src/features/congregation/settings/circuit_overseer/week_item/index.tsx, .../index.types.ts, .../useWeekItem.tsx
WeekItem props extended with error, helperText, onWeekChange, onDelete. Adds optimistic DatePicker change flow (revert on error), delete handler, week-of computation, and guards in useWeekItem to avoid unnecessary week-type updates.
CO Weeks List: Drafts & Validation
src/features/congregation/settings/circuit_overseer/weeks_list/index.tsx, .../useWeeksList.tsx
Introduce WeekRecord/WeekErrorType, compute per-item errors (empty, duplicate), manage draft rows, and expose errors, handleVisitDeleted, handleWeekChange alongside handleAddVisit and weeks. UI wires errors and handlers into WeekItem props.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant WeeksList
  participant WeekItem
  participant WeekHook as useWeekItem
  participant WeeksHook as useWeeksList

  User->>WeekItem: change DatePicker (Date|null)
  WeekItem->>WeekHook: handleDateChange(Date|null)
  Note over WeekHook: compute nextWeek / prevWeek<br/>guard no-op if unchanged
  WeekHook->>WeekHook: conditional reset of prev week type (if unused)
  WeekHook->>WeekHook: set next week type -> CO_VISIT (if set)
  WeekHook-->>WeekItem: resolve (success) / throw (error)
  WeekItem->>WeeksHook: onWeekChange(id, nextWeekOf)
  WeeksHook->>WeeksHook: updateWeeks() & computeErrors()
  WeeksHook-->>WeeksList: new weeks + errors
  WeeksList-->>WeekItem: pass `error` / `helperText`
Loading
sequenceDiagram
  autonumber
  actor User
  participant UI
  participant Hook as useCircuitOverseer
  participant Timer as saveTimers[field]
  participant DB as dbAppSettingsUpdate

  User->>UI: edit firstname/lastname/displayname
  UI->>Hook: onChange(field, value)
  Hook->>Hook: markEditing(field)
  Hook->>Timer: scheduleSave(field, debounce)
  Note over Timer: debounce timer per-field
  Timer-->>Hook: timer fires
  Hook->>DB: update nested field path (specific key)
  DB-->>Hook: success
  Hook->>Hook: clearTimer(field), update local state
  Hook-->>UI: reflect saved value
  Note over Hook: external updates sync only for non-editing fields
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Files/areas to inspect closely:

  • src/features/congregation/settings/circuit_overseer/useCircuitOverseer.tsx — per-field debounce, timer lifecycle, and DB update paths.
  • src/features/congregation/settings/circuit_overseer/weeks_list/useWeeksList.tsx and useWeekItem.tsx — correctness of duplicate/empty detection and conditional week-type resets.
  • src/components/date_picker/index.tsx and toolbar — null handling, height calc, and slot prop propagation (error/helperText).

Possibly related PRs

Suggested labels

released

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: field validation improvements and debounced saving for settings. It directly relates to the primary objectives of fixing name edits, visit list validation, and preventing date picker flicker.
Description check ✅ Passed The description clearly relates to the changeset, detailing the three main fixes: per-field debounced saves for name edits, draft tracking with validation for visit lists, and date picker flicker prevention.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Oct 4, 2025

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/features/congregation/settings/circuit_overseer/useCircuitOverseer.tsx (1)

37-51: Handle save errors to reset editing state.

If the async fn() on Line 44 rejects (Dexie hiccup, offline, etc.), the timeout exits before reaching the setEditing block, leaving the field stuck in “editing” mode and surfacing an unhandled rejection. Wrapping the await in a try/finally keeps the UI state consistent even on failures.

-    saveTimers.current[key] = setTimeout(async () => {
-      saveTimers.current[key] = undefined;
-      await fn();
-      setEditing((prev) => {
-        const next = { ...prev };
-        for (const field of markCompleteKeys) {
-          next[field] = false;
-        }
-        return next;
-      });
-    }, 1000);
+    saveTimers.current[key] = setTimeout(async () => {
+      saveTimers.current[key] = undefined;
+      try {
+        await fn();
+      } finally {
+        setEditing((prev) => {
+          const next = { ...prev };
+          for (const field of markCompleteKeys) {
+            next[field] = false;
+          }
+          return next;
+        });
+      }
+    }, 1000);
src/components/date_picker/slots/toolbar.tsx (1)

7-7: Null-safe toolbar formatting

Handling null with a guard is correct and avoids invalid-date formatting.

Consider replacing '***' with a localized placeholder via t(...) for consistency.

Also applies to: 13-17

src/components/date_picker/index.tsx (1)

34-36: Null-aware DatePicker and error forwarding look solid

State syncing, null handling, and text field error/helperText propagation are correct.

Type onMonthChange param and avoid re-wrapping:

-  const changeHeight = (event) => {
-    if (getWeeksInMonth(new Date(event), { weekStartsOn: 0 }) === 6) {
+  const changeHeight = (date: Date) => {
+    if (getWeeksInMonth(date, { weekStartsOn: 0 }) === 6) {
       setHeight(290);
     } else {
       setHeight(240);
     }
   };

Also applies to: 45-46, 61-67, 69-79, 81-96, 119-121, 161-171

src/features/congregation/settings/circuit_overseer/weeks_list/useWeeksList.tsx (1)

16-46: Edge case: multiple empties via edits are not flagged consistently

computeErrors only preserves previously flagged 'empty'. Clearing another week to empty won’t flag it, allowing multiple empties without warning. To consistently “allow only one empty,” mark all additional empties as errors and prefer keeping the previously flagged one (or first) as allowed.

Apply this refactor:

-  const computeErrors = (
-    list: WeekRecord[],
-    previous: Record<string, WeekErrorType>
-  ) => {
-    const nextErrors: Record<string, WeekErrorType> = {};
-
-    for (const record of list) {
-      if (record.weekOf.length === 0 && previous?.[record.id] === 'empty') {
-        nextErrors[record.id] = 'empty';
-      }
-    }
-
-    const byWeek = new Map<string, string[]>();
-
-    for (const record of list) {
-      if (record.weekOf.length === 0) continue;
-      const collection = byWeek.get(record.weekOf) ?? [];
-      collection.push(record.id);
-      byWeek.set(record.weekOf, collection);
-    }
-
-    byWeek.forEach((ids) => {
-      if (ids.length > 1) {
-        ids.forEach((id) => {
-          nextErrors[id] = 'duplicate';
-        });
-      }
-    });
-
-    return nextErrors;
-  };
+  const computeErrors = (
+    list: WeekRecord[],
+    previous: Record<string, WeekErrorType>
+  ) => {
+    const nextErrors: Record<string, WeekErrorType> = {};
+
+    // Determine which empty (if any) is "allowed"
+    const emptyIds = list.filter((r) => r.weekOf.length === 0).map((r) => r.id);
+    const preserved = emptyIds.find((id) => previous?.[id] === 'empty');
+    const allowedEmptyId = preserved ?? emptyIds[0];
+    // Flag all additional empties beyond the allowed one
+    for (const id of emptyIds) {
+      if (id !== allowedEmptyId) nextErrors[id] = 'empty';
+    }
+
+    // Flag duplicates (non-empty)
+    const byWeek = new Map<string, string[]>();
+    for (const record of list) {
+      if (record.weekOf.length === 0) continue;
+      const collection = byWeek.get(record.weekOf) ?? [];
+      collection.push(record.id);
+      byWeek.set(record.weekOf, collection);
+    }
+    byWeek.forEach((ids) => {
+      if (ids.length > 1) {
+        ids.forEach((id) => {
+          nextErrors[id] = 'duplicate';
+        });
+      }
+    });
+
+    return nextErrors;
+  };

Does the product requirement intend to block creating a second empty via edits, or only warn? If blocking is desired, we can add a guard in handleWeekChange to revert clears when another empty exists.

Also applies to: 59-76, 78-90

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a79dad and e3a7265.

⛔ Files ignored due to path filters (1)
  • src/locales/en/general.json is excluded by !**/*.json
📒 Files selected for processing (11)
  • .env.example (1 hunks)
  • src/components/date_picker/index.tsx (5 hunks)
  • src/components/date_picker/index.types.ts (3 hunks)
  • src/components/date_picker/slots/toolbar.tsx (1 hunks)
  • src/features/congregation/settings/circuit_overseer/useCircuitOverseer.tsx (3 hunks)
  • src/features/congregation/settings/circuit_overseer/week_item/index.tsx (2 hunks)
  • src/features/congregation/settings/circuit_overseer/week_item/index.types.ts (1 hunks)
  • src/features/congregation/settings/circuit_overseer/week_item/useWeekItem.tsx (4 hunks)
  • src/features/congregation/settings/circuit_overseer/weeks_list/index.tsx (2 hunks)
  • src/features/congregation/settings/circuit_overseer/weeks_list/useWeeksList.tsx (1 hunks)
  • vite.config.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
src/features/congregation/settings/circuit_overseer/week_item/useWeekItem.tsx (2)
src/definition/settings.ts (1)
  • CircuitOverseerVisitType (28-33)
src/services/dexie/settings.ts (1)
  • dbAppSettingsUpdate (24-58)
src/components/date_picker/index.tsx (1)
src/utils/date.ts (1)
  • isValidDate (411-413)
src/features/congregation/settings/circuit_overseer/useCircuitOverseer.tsx (1)
src/services/dexie/settings.ts (1)
  • dbAppSettingsUpdate (24-58)
src/features/congregation/settings/circuit_overseer/weeks_list/useWeeksList.tsx (3)
src/definition/settings.ts (1)
  • CircuitOverseerVisitType (28-33)
src/states/settings.ts (1)
  • settingsState (19-19)
src/utils/date.ts (2)
  • formatDate (20-22)
  • getWeekDate (73-79)
src/features/congregation/settings/circuit_overseer/week_item/index.tsx (2)
src/features/congregation/settings/circuit_overseer/week_item/index.types.ts (1)
  • WeekItemType (3-9)
src/utils/date.ts (2)
  • formatDate (20-22)
  • getWeekDate (73-79)
🪛 dotenv-linter (3.3.0)
.env.example

[warning] 5-5: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)


[warning] 5-5: [UnorderedKey] The VITE_APP_MODE key should go before the VITE_FIREBASE_APIKEY key

(UnorderedKey)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (3)
src/features/congregation/settings/circuit_overseer/week_item/useWeekItem.tsx (1)

79-93: Nice guard against stale week types.

The previousWeek check plus hasAnotherVisitWithWeek keeps us from downgrading schedules when another visit still references the same week. This closes the gap where a single edit could reset the wrong week type.

src/features/congregation/settings/circuit_overseer/week_item/index.types.ts (1)

5-8: Props surface extension looks good

New props (error, helperText, onWeekChange, onDelete) are coherent with the updated flows.

src/features/congregation/settings/circuit_overseer/weeks_list/index.tsx (1)

13-18: Per-item error wiring is correct

Good mapping of errors to WeekItem’s error/helperText and forwarding onWeekChange/onDelete.

Confirm translation keys exist: tr_fillRequiredField and tr_dateAlreadyExists.

Also applies to: 28-47

@OneChanceLance OneChanceLance changed the title fix: allow only one empty CO field fix(circuit_overseer): details and visiting date field validation Oct 4, 2025
@OneChanceLance OneChanceLance changed the title fix(circuit_overseer): details and visiting date field validation fix(settings): details and visiting date field validation Oct 6, 2025
# Description

Fixed the C.O. name resetting when out of focus or changing focus. Fixed the UI flickering when swapping dates.

Please delete options that are not relevant.

- [X] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update

# Checklist:

- [X] My code follows the style guidelines of this project
- [X] I have performed a self-review of my own code
- [X] I have commented my code, particularly in hard-to-understand areas
- [X] I have made corresponding changes to the documentation
- [X] My changes generate no new warnings
- [X] Any dependent changes have been merged and published in downstream modules
@rhahao rhahao force-pushed the CU-86c1dwh1e_FIX-Allow-only-one-empty-CO-visit-field-Congregation-settings_Jake-Maragos branch from e3a7265 to 183c261 Compare November 11, 2025 02:15
@sonarqubecloud
Copy link

sonarqubecloud bot commented Nov 11, 2025

Quality Gate Passed Quality Gate passed

Issues
0 New issues
5 Accepted issues

Measures
0 Security Hotspots
No data about Coverage
0.0% Duplication on New Code

See analysis details on SonarQube Cloud

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/features/congregation/settings/circuit_overseer/week_item/index.tsx (2)

17-23: Fix: Clone date before passing to getWeekDate to prevent mutation

getWeekDate mutates the passed Date object via setDate. Passing the picker's date directly can cause unintended side effects and incorrect week calculations.

Apply this diff:

   const computeWeekOf = (date: Date | null) => {
     if (date === null) {
       return '';
     }
 
-    return formatDate(getWeekDate(date), 'yyyy/MM/dd');
+    // Clone to avoid mutating the original Date via getWeekDate
+    return formatDate(getWeekDate(new Date(date)), 'yyyy/MM/dd');
   };

60-60: Fix: Use format-aware date parser for Safari compatibility

new Date('yyyy/MM/dd') is implementation-dependent and fails on Safari. The date string must be parsed with an explicit format parser.

Apply this diff:

+import { parse } from 'date-fns';
+import { isValidDate } from '@utils/date';
 
 // ... inside component ...
 
       <DatePicker
         disablePast
         label={t('tr_coNextVisitWeek')}
-        value={visit.weekOf === '' ? null : new Date(visit.weekOf)}
+        value={
+          visit.weekOf === ''
+            ? null
+            : (() => {
+                const parsed = parse(visit.weekOf, 'yyyy/MM/dd', new Date());
+                return isValidDate(parsed) ? parsed : null;
+              })()
+        }
         onChange={handlePickerChange}
         readOnly={!isAdmin}
         error={error}
         helperText={helperText}
       />
🧹 Nitpick comments (2)
.env.example (1)

5-5: Remove unnecessary quotes and consider reordering for linter compliance.

The static analysis tools flag two style issues:

  1. The value "TEST" has unnecessary quotes—simple alphanumeric values in .env files don't need quoting.
  2. The key should be ordered alphabetically before VITE_FIREBASE_APIKEY per dotenv-linter rules.

Apply this diff if your project enforces dotenv-linter:

-VITE_FIREBASE_APIKEY=organized-local-api-key
-VITE_FIREBASE_AUTHDOMAIN=localhost
-VITE_FIREBASE_PROJECTID=organized-local
-VITE_FIREBASE_APPID=organized-local-app-id
-VITE_APP_MODE="TEST"
+VITE_APP_MODE=TEST
+VITE_FIREBASE_APIKEY=organized-local-api-key
+VITE_FIREBASE_AUTHDOMAIN=localhost
+VITE_FIREBASE_PROJECTID=organized-local
+VITE_FIREBASE_APPID=organized-local-app-id

If grouping Firebase variables together is intentional, you may suppress the linter warnings instead.

vite.config.ts (1)

49-59: LGTM! Proxy configuration looks good for local development.

The proxy setup correctly routes /api calls to the backend service with appropriate CORS handling. The secure: false setting is fine for local development.

Optional improvement: Use environment variables for flexibility.

Consider making the backend target configurable via environment variables to support different development environments:

  server: {
    port: 4050,
    host: true,
    proxy: {
      '/api': {
-       target: 'http://localhost:8000',
+       target: process.env.VITE_API_TARGET || 'http://localhost:8000',
        changeOrigin: true,
        secure: false,
      },
    },
  },

Additionally, a brief inline comment explaining the proxy purpose would help future maintainers.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e3a7265 and 183c261.

⛔ Files ignored due to path filters (1)
  • src/locales/en/general.json is excluded by !**/*.json
📒 Files selected for processing (11)
  • .env.example (1 hunks)
  • src/components/date_picker/index.tsx (5 hunks)
  • src/components/date_picker/index.types.ts (3 hunks)
  • src/components/date_picker/slots/toolbar.tsx (1 hunks)
  • src/features/congregation/settings/circuit_overseer/useCircuitOverseer.tsx (3 hunks)
  • src/features/congregation/settings/circuit_overseer/week_item/index.tsx (2 hunks)
  • src/features/congregation/settings/circuit_overseer/week_item/index.types.ts (1 hunks)
  • src/features/congregation/settings/circuit_overseer/week_item/useWeekItem.tsx (4 hunks)
  • src/features/congregation/settings/circuit_overseer/weeks_list/index.tsx (2 hunks)
  • src/features/congregation/settings/circuit_overseer/weeks_list/useWeeksList.tsx (1 hunks)
  • vite.config.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/components/date_picker/slots/toolbar.tsx
  • src/components/date_picker/index.types.ts
  • src/components/date_picker/index.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-09T14:24:33.861Z
Learnt from: nobodyzero1
Repo: sws2apps/organized-app PR: 4326
File: src/utils/enrollments.ts:20-25
Timestamp: 2025-07-09T14:24:33.861Z
Learning: In src/utils/enrollments.ts, the enrollmentStartDateChange function already includes a null check for the current variable to handle cases where the find operation returns undefined.

Applied to files:

  • src/features/congregation/settings/circuit_overseer/week_item/useWeekItem.tsx
🧬 Code graph analysis (4)
src/features/congregation/settings/circuit_overseer/week_item/useWeekItem.tsx (2)
src/definition/settings.ts (1)
  • CircuitOverseerVisitType (28-33)
src/services/dexie/settings.ts (1)
  • dbAppSettingsUpdate (24-58)
src/features/congregation/settings/circuit_overseer/weeks_list/useWeeksList.tsx (3)
src/definition/settings.ts (1)
  • CircuitOverseerVisitType (28-33)
src/states/settings.ts (1)
  • settingsState (19-19)
src/utils/date.ts (2)
  • formatDate (20-22)
  • getWeekDate (73-79)
src/features/congregation/settings/circuit_overseer/useCircuitOverseer.tsx (1)
src/services/dexie/settings.ts (1)
  • dbAppSettingsUpdate (24-58)
src/features/congregation/settings/circuit_overseer/week_item/index.tsx (2)
src/features/congregation/settings/circuit_overseer/week_item/index.types.ts (1)
  • WeekItemType (3-9)
src/utils/date.ts (2)
  • formatDate (20-22)
  • getWeekDate (73-79)
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 5-5: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)


[warning] 5-5: [UnorderedKey] The VITE_APP_MODE key should go before the VITE_FIREBASE_APIKEY key

(UnorderedKey)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: dependency-review
  • GitHub Check: Code QL
  • GitHub Check: Summary
🔇 Additional comments (16)
src/features/congregation/settings/circuit_overseer/useCircuitOverseer.tsx (2)

149-168: LGTM: Effects handle syncing and cleanup correctly.

The sync effect properly respects the editing state to prevent external updates from overwriting user input. The cleanup effect correctly clears all pending timers on unmount, preventing memory leaks and stale updates.


96-114: No polyfill needed—the project targets ESNext with native structuredClone support.

The codebase explicitly targets ESNext in tsconfig.json (target: "ESNext", lib: ["ESNext", "DOM"]) and uses Vite without explicit legacy browser targeting. This means structuredClone is fully supported in all target browsers (Chrome 98+, Firefox 94+, Safari 15.4+).

However, if future requirements expand browser support to legacy environments, core-js's structured-clone polyfill or MessageChannel-based fallback patterns should be added.

src/features/congregation/settings/circuit_overseer/week_item/index.types.ts (1)

5-8: LGTM!

The type extensions cleanly support error display and per-item event handling, aligning well with the enhanced validation flow in the parent components.

src/features/congregation/settings/circuit_overseer/week_item/useWeekItem.tsx (4)

14-16: LGTM!

The early return guard prevents unnecessary processing and database updates when weekOf is empty or null.


41-51: LGTM!

The helper correctly prevents resetting week types when multiple visits share the same week, avoiding data corruption.


53-100: LGTM!

The enhanced date change logic correctly:

  • Handles nullable dates
  • Preserves previous week types when other visits reference them
  • Only updates week types when appropriate
  • Returns the computed date for potential chaining

The conditional cleanup using hasAnotherVisitWithWeek is a critical improvement that prevents data corruption.


102-131: LGTM!

The deletion logic mirrors the date change improvements, ensuring week types are only reset to NORMAL when no other visits reference the same week. This maintains data consistency across deletions.

src/features/congregation/settings/circuit_overseer/week_item/index.tsx (2)

25-45: Good implementation of optimistic updates

The optimistic update pattern improves perceived responsiveness. The error handling correctly reverts to the original value if the async operation fails, and reconciles any mismatch between optimistic and actual values.


47-53: LGTM!

The delete handler correctly awaits the async operation and invokes the callback, maintaining proper control flow.

src/features/congregation/settings/circuit_overseer/weeks_list/index.tsx (2)

13-17: LGTM!

Clean integration with the expanded hook API and appropriate translation strings for error messaging.


28-47: LGTM!

The per-item error handling correctly maps error types to appropriate messages and passes validation state to each WeekItem. The logic cleanly handles undefined error states.

src/features/congregation/settings/circuit_overseer/weeks_list/useWeeksList.tsx (5)

7-8: LGTM!

The type definitions appropriately model draft state and validation error types for the week management flow.


16-46: Clever validation pattern for progressive error disclosure

The computeErrors logic implements a progressive error pattern:

  • 'empty' errors persist only after being explicitly set (via handleAddVisit), preventing false positives on initial render
  • Duplicate detection correctly groups by weekOf and flags all conflicting entries
  • Empty weeks are appropriately excluded from duplicate checks

48-57: LGTM!

The updateWeeks helper cleanly couples state updates with error recomputation, ensuring validation stays synchronized with data changes.


59-94: LGTM!

The handlers implement the correct flow:

  • handleAddVisit prevents multiple empty drafts by validating before adding
  • handleWeekChange appropriately clears draft status when a date is set
  • handleVisitDeleted cleanly removes items from the list

The use of crypto.randomUUID() for client-side IDs is appropriate.


96-117: LGTM!

The synchronization effect correctly:

  • Filters upcoming visits from persisted settings
  • Preserves local drafts that haven't been persisted yet
  • Combines both sources into the working list
  • Recomputes errors to maintain validation consistency

The draft preservation logic ensures a smooth editing experience when settings update from external sources.

Comment on lines +37 to +51
const scheduleSave = (key: FieldKey, fn: () => Promise<void>, markCompleteKeys: FieldKey[]) => {
clearTimer(key);

saveTimers.current[key] = setTimeout(async () => {
saveTimers.current[key] = undefined;
await fn();
setEditing((prev) => {
const next = { ...prev };
for (const field of markCompleteKeys) {
next[field] = false;
}
return next;
});
}, 1000);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Race condition when firstname and lastname are edited in quick succession.

When a user edits firstname and then lastname before the firstname timer fires:

  1. Both fields mark displayname as editing
  2. Firstname timer completes after 1s, marks firstname and displayname as not editing
  3. At this point, displayname is no longer protected from external updates (line 154), even though lastname is still pending
  4. The useEffect may sync displayname from settings, potentially overwriting the lastname-derived value
  5. Lastname timer completes, but the derived displayname may have been lost

Consider tracking whether any name field is being edited before clearing displayname's editing state:

-  const scheduleSave = (key: FieldKey, fn: () => Promise<void>, markCompleteKeys: FieldKey[]) => {
+  const scheduleSave = (key: FieldKey, fn: () => Promise<void>) => {
     clearTimer(key);

     saveTimers.current[key] = setTimeout(async () => {
       saveTimers.current[key] = undefined;
       await fn();
       setEditing((prev) => {
         const next = { ...prev };
-        for (const field of markCompleteKeys) {
-          next[field] = false;
-        }
+        next[key] = false;
+        // Only mark displayname as not editing if neither firstname nor lastname is still editing
+        if (key === 'firstname' || key === 'lastname') {
+          if (!next.firstname && !next.lastname) {
+            next.displayname = false;
+          }
+        }
         return next;
       });
     }, 1000);
   };

Then update the call sites to remove the markCompleteKeys parameter (lines 85, 89, 93).

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/features/congregation/settings/circuit_overseer/useCircuitOverseer.tsx
around lines 37 to 51, the scheduleSave helper currently accepts
markCompleteKeys and unconditionally clears those editing flags when each timer
fires, which causes a race where firstname finishing can clear displayname while
lastname is still pending; change scheduleSave to only clear individual field
keys it directly saved and to avoid clearing shared/derived keys like
displayname unless no other related name timers remain (e.g., track active
name-field timers or an activeNames set/count and check it before clearing
displayname), and remove the now-unused markCompleteKeys parameter from the call
sites on lines 85, 89, and 93 so callers no longer rely on scheduleSave to clear
derived editing state.

server: {
port: 4050,
host: true,
proxy: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@OneChanceLance: since accessing the backend host is handled differently, can we please remove this? Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants