fix: react-doctor → 64/100 (Bugs 122, Perf 286, A11y 613, Maint 436)

- CartContext: lazy initializers replace mount-only useEffect
  hydration; remove 8 no-initialize-state warnings
- Toast/AdminSearchInput: React 19 useContext/use + drop
  forwardRef (3 no-react19-deprecated-apis)
- ProductFormModal: lazy initializers + useSyncExternalStore
  for mount; parent adds key=editingProduct.id
- InstallPrompt: useReducer for prompt state (no-cascading-set-state)
- QRScanModal: ref-based latest-callback pattern replaces
  useEffectEvent deps mistake
- OnboardingFlow: functional setState (rerender-functional-setstate)
- UsersPage/StopsCalendar/FeaturesAndStats: lazy initializers
  (rerender-lazy-state-init)
- FAQClientPage: server-side brand settings fetch via getBrandSettingsPublic
  in layout; remove supabase import
- LandingPageWrapper: href='#' → href='#top' (anchor-is-valid)
- TuxedoVideoHero: replace animate-bounce with ease-out-expo
  (no-inline-bounce-easing)
- ProductTableClient: useCallback for handleDeleted
  (jsx-no-new-function-as-prop)
- excel-parser: pre-compile delimiter regexes (js-hoist-regexp)
- water-log/settings: Promise.all for parallel DB calls
  (async-parallel)
- ToastNotification: extract toast store to separate file
  (only-export-components)
- WholesaleClient: inline <WholesaleIcon/> instead of hoisting to
  const (rendering-hoist-jsx)
This commit is contained in:
Nora
2026-06-26 02:41:56 -06:00
parent 8e011da521
commit 29d9d23a26
88 changed files with 1399 additions and 1015 deletions
+28 -18
View File
@@ -133,6 +133,8 @@ export default function SettingsSections({ brandId, workersOnly, tasksOnly }: Pr
const [taskSortOrder, setTaskSortOrder] = useState(0);
const [taskError, setTaskError] = useState<string | null>(null);
// Load all data for the brand. Called from the mount effect below
// and from mutation handlers that need to refresh after a save.
const load = useCallback(async () => {
setLoading(true);
const [w, t, s] = await Promise.all([
@@ -142,29 +144,37 @@ export default function SettingsSections({ brandId, workersOnly, tasksOnly }: Pr
]);
setWorkers(w);
setTasks(t);
if (s) {
setSettings(s);
setPayPeriodStartDay(s.pay_period_start_day);
setPayPeriodLength(s.pay_period_length_days);
setDailyOvertimeThreshold(s.daily_overtime_threshold);
setWeeklyOvertimeThreshold(s.weekly_overtime_threshold);
setOvertimeMultiplier(s.overtime_multiplier);
setOvertimeNotifications(s.overtime_notifications);
setNotificationEmails(s.notification_emails ?? []);
setNotificationSmsNumbers(s.notification_sms_numbers ?? []);
setEnableDailyAlerts(s.enable_daily_alerts ?? true);
setEnableWeeklyAlerts(s.enable_weekly_alerts ?? true);
setDailyAlertThreshold(Math.round((s.daily_alert_threshold ?? 0.80) * 100));
setWeeklyAlertThreshold(Math.round((s.weekly_alert_threshold ?? 0.80) * 100));
setSendEndOfPeriodSummary(s.send_end_of_period_summary ?? true);
setBrandName(s.brand_name ?? "Farm");
}
setSettings(s);
const log = await getTimeTrackingNotificationLog(brandId, 50);
setNotificationLog(log);
setLoading(false);
}, [brandId]);
useEffect(() => { load(); }, [load]);
// Track the previous settings object so we can sync form state
// inline during render when fresh data arrives from the server
// (avoids the stale-frame window a useEffect would introduce).
const [prevSettings, setPrevSettings] = useState<TimeTrackingSettings | null>(null);
if (settings && settings !== prevSettings) {
setPrevSettings(settings);
setPayPeriodStartDay(settings.pay_period_start_day);
setPayPeriodLength(settings.pay_period_length_days);
setDailyOvertimeThreshold(settings.daily_overtime_threshold);
setWeeklyOvertimeThreshold(settings.weekly_overtime_threshold);
setOvertimeMultiplier(settings.overtime_multiplier);
setOvertimeNotifications(settings.overtime_notifications);
setNotificationEmails(settings.notification_emails ?? []);
setNotificationSmsNumbers(settings.notification_sms_numbers ?? []);
setEnableDailyAlerts(settings.enable_daily_alerts ?? true);
setEnableWeeklyAlerts(settings.enable_weekly_alerts ?? true);
setDailyAlertThreshold(Math.round((settings.daily_alert_threshold ?? 0.80) * 100));
setWeeklyAlertThreshold(Math.round((settings.weekly_alert_threshold ?? 0.80) * 100));
setSendEndOfPeriodSummary(settings.send_end_of_period_summary ?? true);
setBrandName(settings.brand_name ?? "Farm");
}
useEffect(() => {
void load();
}, [load]);
const handleSaveNotifications = async () => {
setSettingsSaving(true);