Fix config save state sync — local config now re-syncs after save
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m46s
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m46s
The save bar persisted forever because Zod.parse() adds defaults for new fields, making the server config differ from local state. After save, the sync effect now picks up the server value. Uses savingRef to prevent overwriting local edits during the save roundtrip. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -237,16 +237,24 @@ export default function RoundDetailPage() {
|
|||||||
{ enabled: round?.roundType === 'FILTERING', refetchInterval: 5_000 },
|
{ enabled: round?.roundType === 'FILTERING', refetchInterval: 5_000 },
|
||||||
)
|
)
|
||||||
|
|
||||||
// Initialize config from server once on load (or when round changes externally)
|
// Initialize config from server on load; re-sync after saves
|
||||||
const serverConfig = useMemo(() => (round?.configJson as Record<string, unknown>) ?? {}, [round?.configJson])
|
const serverConfig = useMemo(() => (round?.configJson as Record<string, unknown>) ?? {}, [round?.configJson])
|
||||||
const configInitialized = useRef(false)
|
const configInitialized = useRef(false)
|
||||||
if (round && !configInitialized.current) {
|
const savingRef = useRef(false)
|
||||||
const roundConfig = (round.configJson as Record<string, unknown>) ?? {}
|
|
||||||
if (Object.keys(config).length === 0 || JSON.stringify(roundConfig) !== JSON.stringify(config)) {
|
// Sync local config with server: on initial load AND whenever serverConfig
|
||||||
setConfig(roundConfig)
|
// changes after a save completes (so Zod-applied defaults get picked up)
|
||||||
}
|
useEffect(() => {
|
||||||
|
if (!round) return
|
||||||
|
if (!configInitialized.current) {
|
||||||
configInitialized.current = true
|
configInitialized.current = true
|
||||||
|
setConfig(serverConfig)
|
||||||
|
} else if (!savingRef.current) {
|
||||||
|
// Server changed (e.g. after save invalidation) — re-sync
|
||||||
|
setConfig(serverConfig)
|
||||||
}
|
}
|
||||||
|
}, [serverConfig, round])
|
||||||
|
|
||||||
const hasUnsavedConfig = useMemo(
|
const hasUnsavedConfig = useMemo(
|
||||||
() => configInitialized.current && JSON.stringify(config) !== JSON.stringify(serverConfig),
|
() => configInitialized.current && JSON.stringify(config) !== JSON.stringify(serverConfig),
|
||||||
[config, serverConfig],
|
[config, serverConfig],
|
||||||
@@ -255,11 +263,13 @@ export default function RoundDetailPage() {
|
|||||||
// ── Mutations ──────────────────────────────────────────────────────────
|
// ── Mutations ──────────────────────────────────────────────────────────
|
||||||
const updateMutation = trpc.round.update.useMutation({
|
const updateMutation = trpc.round.update.useMutation({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
savingRef.current = false
|
||||||
utils.round.getById.invalidate({ id: roundId })
|
utils.round.getById.invalidate({ id: roundId })
|
||||||
setAutosaveStatus('saved')
|
setAutosaveStatus('saved')
|
||||||
setTimeout(() => setAutosaveStatus('idle'), 2000)
|
setTimeout(() => setAutosaveStatus('idle'), 2000)
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
|
savingRef.current = false
|
||||||
setAutosaveStatus('error')
|
setAutosaveStatus('error')
|
||||||
toast.error(err.message)
|
toast.error(err.message)
|
||||||
},
|
},
|
||||||
@@ -393,23 +403,27 @@ export default function RoundDetailPage() {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const saveConfig = useCallback(() => {
|
const saveConfig = useCallback(() => {
|
||||||
|
savingRef.current = true
|
||||||
setAutosaveStatus('saving')
|
setAutosaveStatus('saving')
|
||||||
updateMutation.mutate({ id: roundId, configJson: config })
|
updateMutation.mutate({ id: roundId, configJson: config })
|
||||||
}, [config, roundId, updateMutation])
|
}, [config, roundId, updateMutation])
|
||||||
|
|
||||||
// ── Auto-save: debounce config changes and save automatically ────────
|
// ── Auto-save: debounce config changes and save automatically ────────
|
||||||
|
const configJson = JSON.stringify(config)
|
||||||
|
const serverJson = JSON.stringify(serverConfig)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!configInitialized.current) return
|
if (!configInitialized.current) return
|
||||||
if (JSON.stringify(config) === JSON.stringify(serverConfig)) return
|
if (configJson === serverJson) return
|
||||||
|
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
|
savingRef.current = true
|
||||||
setAutosaveStatus('saving')
|
setAutosaveStatus('saving')
|
||||||
updateMutation.mutate({ id: roundId, configJson: config })
|
updateMutation.mutate({ id: roundId, configJson: config })
|
||||||
}, 800)
|
}, 800)
|
||||||
|
|
||||||
return () => clearTimeout(timer)
|
return () => clearTimeout(timer)
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [config])
|
}, [configJson])
|
||||||
|
|
||||||
// ── Computed values ────────────────────────────────────────────────────
|
// ── Computed values ────────────────────────────────────────────────────
|
||||||
const projectCount = round?._count?.projectRoundStates ?? 0
|
const projectCount = round?._count?.projectRoundStates ?? 0
|
||||||
|
|||||||
Reference in New Issue
Block a user