proposal-system/mobile/src/hooks/useOfflineDraft.ts

101 lines
2.8 KiB
TypeScript
Raw Normal View History

import { useEffect, useState, useCallback, useRef } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import NetInfo from '@react-native-community/netinfo';
const DRAFT_KEY = 'proposal_draft';
const OFFLINE_QUEUE_KEY = 'proposal_offline_queue';
export function useOfflineDraft<T extends Record<string, unknown>>(
initialData: T,
) {
const [draft, setDraft] = useState<T>(initialData);
const [isLoaded, setIsLoaded] = useState(false);
const saveTimeout = useRef<ReturnType<typeof setTimeout>>();
useEffect(() => {
AsyncStorage.getItem(DRAFT_KEY).then((stored) => {
if (stored) {
try {
setDraft(JSON.parse(stored));
} catch {
// Corrupted draft, use initial data
}
}
setIsLoaded(true);
});
}, []);
const updateDraft = useCallback(
(updates: Partial<T>) => {
setDraft((prev) => {
const next = { ...prev, ...updates };
if (saveTimeout.current) clearTimeout(saveTimeout.current);
saveTimeout.current = setTimeout(() => {
AsyncStorage.setItem(DRAFT_KEY, JSON.stringify(next));
}, 1000);
return next;
});
},
[],
);
const clearDraft = useCallback(async () => {
if (saveTimeout.current) clearTimeout(saveTimeout.current);
await AsyncStorage.removeItem(DRAFT_KEY);
setDraft(initialData);
}, [initialData]);
return { draft, updateDraft, clearDraft, isLoaded };
}
interface QueuedSubmission {
data: unknown;
timestamp: string;
}
export async function queueOfflineSubmission(data: unknown): Promise<void> {
const stored = await AsyncStorage.getItem(OFFLINE_QUEUE_KEY);
const queue: QueuedSubmission[] = stored ? JSON.parse(stored) : [];
queue.push({ data, timestamp: new Date().toISOString() });
await AsyncStorage.setItem(OFFLINE_QUEUE_KEY, JSON.stringify(queue));
}
export async function processOfflineQueue(
submitFn: (data: unknown) => Promise<unknown>,
): Promise<number> {
const state = await NetInfo.fetch();
if (!state.isConnected) return 0;
const stored = await AsyncStorage.getItem(OFFLINE_QUEUE_KEY);
if (!stored) return 0;
const queue: QueuedSubmission[] = JSON.parse(stored);
let processed = 0;
for (const item of queue) {
try {
await submitFn(item.data);
processed++;
} catch {
break;
}
}
if (processed === queue.length) {
await AsyncStorage.removeItem(OFFLINE_QUEUE_KEY);
} else if (processed > 0) {
await AsyncStorage.setItem(
OFFLINE_QUEUE_KEY,
JSON.stringify(queue.slice(processed)),
);
}
return processed;
}
export async function getOfflineQueueCount(): Promise<number> {
const stored = await AsyncStorage.getItem(OFFLINE_QUEUE_KEY);
if (!stored) return 0;
return JSON.parse(stored).length;
}