diff --git a/apps/web/src/app/api/worksheets/settings/route.ts b/apps/web/src/app/api/worksheets/settings/route.ts new file mode 100644 index 00000000..699987bd --- /dev/null +++ b/apps/web/src/app/api/worksheets/settings/route.ts @@ -0,0 +1,155 @@ +import { eq, and } from 'drizzle-orm' +import { type NextRequest, NextResponse } from 'next/server' +import { db, schema } from '@/db' +import { getViewerId } from '@/lib/viewer' +import { + parseAdditionConfig, + serializeAdditionConfig, + defaultAdditionConfig, + type AdditionConfigV1, +} from '@/app/create/worksheets/config-schemas' + +/** + * GET /api/worksheets/settings?type=addition + * Load user's saved worksheet settings + * + * Query params: + * - type: 'addition' | 'subtraction' | etc. + * + * Returns: + * - config: Parsed and validated config (latest version) + * - exists: boolean (true if user has saved settings) + */ +export async function GET(req: NextRequest) { + try { + const viewerId = await getViewerId() + const { searchParams } = new URL(req.url) + const worksheetType = searchParams.get('type') + + if (!worksheetType) { + return NextResponse.json({ error: 'Missing type parameter' }, { status: 400 }) + } + + // Only 'addition' is supported for now + if (worksheetType !== 'addition') { + return NextResponse.json({ error: `Unsupported worksheet type: ${worksheetType}` }, { status: 400 }) + } + + // Look up user's saved settings + const [row] = await db + .select() + .from(schema.worksheetSettings) + .where( + and( + eq(schema.worksheetSettings.userId, viewerId), + eq(schema.worksheetSettings.worksheetType, worksheetType) + ) + ) + .limit(1) + + if (!row) { + // No saved settings, return defaults + return NextResponse.json({ + config: defaultAdditionConfig, + exists: false, + }) + } + + // Parse and validate config (auto-migrates to latest version) + const config = parseAdditionConfig(row.config) + + return NextResponse.json({ + config, + exists: true, + }) + } catch (error: any) { + console.error('Failed to load worksheet settings:', error) + return NextResponse.json({ error: 'Failed to load worksheet settings' }, { status: 500 }) + } +} + +/** + * POST /api/worksheets/settings + * Save user's worksheet settings + * + * Body: + * - type: 'addition' | 'subtraction' | etc. + * - config: Config object (version will be added automatically) + * + * Returns: + * - success: boolean + * - id: string (worksheet_settings row id) + */ +export async function POST(req: NextRequest) { + try { + const viewerId = await getViewerId() + const body = await req.json() + + const { type: worksheetType, config } = body + + if (!worksheetType) { + return NextResponse.json({ error: 'Missing type field' }, { status: 400 }) + } + + if (!config) { + return NextResponse.json({ error: 'Missing config field' }, { status: 400 }) + } + + // Only 'addition' is supported for now + if (worksheetType !== 'addition') { + return NextResponse.json({ error: `Unsupported worksheet type: ${worksheetType}` }, { status: 400 }) + } + + // Serialize config (adds version automatically) + const configJson = serializeAdditionConfig(config) + + // Check if user already has settings for this type + const [existing] = await db + .select() + .from(schema.worksheetSettings) + .where( + and( + eq(schema.worksheetSettings.userId, viewerId), + eq(schema.worksheetSettings.worksheetType, worksheetType) + ) + ) + .limit(1) + + const now = new Date() + + if (existing) { + // Update existing row + await db + .update(schema.worksheetSettings) + .set({ + config: configJson, + updatedAt: now, + }) + .where(eq(schema.worksheetSettings.id, existing.id)) + + return NextResponse.json({ + success: true, + id: existing.id, + }) + } else { + // Insert new row + const id = crypto.randomUUID() + await db.insert(schema.worksheetSettings).values({ + id, + userId: viewerId, + worksheetType, + config: configJson, + createdAt: now, + updatedAt: now, + }) + + return NextResponse.json({ + success: true, + id, + }) + } + } catch (error: any) { + console.error('Failed to save worksheet settings:', error) + return NextResponse.json({ error: 'Failed to save worksheet settings' }, { status: 500 }) + } +} diff --git a/apps/web/src/app/create/worksheets/addition/page.tsx b/apps/web/src/app/create/worksheets/addition/page.tsx index f56e6b99..4a977bad 100644 --- a/apps/web/src/app/create/worksheets/addition/page.tsx +++ b/apps/web/src/app/create/worksheets/addition/page.tsx @@ -28,6 +28,9 @@ export default function AdditionWorksheetPage() { const t = useTranslations('create.worksheets.addition') const [generationStatus, setGenerationStatus] = useState('idle') const [error, setError] = useState(null) + const [settingsLoaded, setSettingsLoaded] = useState(false) + const [lastSaved, setLastSaved] = useState(null) + const [isSaving, setIsSaving] = useState(false) // Immediate form state (for controls - updates instantly) // PRIMARY state: problemsPerPage, cols, pages (what user controls) @@ -70,6 +73,104 @@ export default function AdditionWorksheetPage() { return () => clearTimeout(timer) }, [formState]) + // Load saved settings on mount + useEffect(() => { + async function loadSettings() { + try { + const response = await fetch('/api/worksheets/settings?type=addition') + if (!response.ok) throw new Error('Failed to load settings') + + const data = await response.json() + if (data.exists && data.config) { + // Load saved config, but preserve derived state + const rows = Math.ceil((data.config.problemsPerPage * data.config.pages) / data.config.cols) + const total = data.config.problemsPerPage * data.config.pages + + setFormState({ + ...data.config, + rows, + total, + date: '', // Always start with empty date + seed: Date.now() % 2147483647, // Generate new seed + }) + } + } catch (error) { + console.error('Failed to load worksheet settings:', error) + // Continue with defaults + } finally { + setSettingsLoaded(true) + } + } + + loadSettings() + }, []) + + // Auto-save settings when they change (debounced, only after initial load) + useEffect(() => { + if (!settingsLoaded) return // Don't save until we've loaded initial settings + + const timer = setTimeout(async () => { + setIsSaving(true) + try { + // Extract only the fields we want to persist (exclude date, seed, derived state) + const { + problemsPerPage, + cols, + pages, + orientation, + name, + pAnyStart, + pAllStart, + interpolate, + showCarryBoxes, + showAnswerBoxes, + showPlaceValueColors, + showProblemNumbers, + showCellBorder, + showTenFrames, + showTenFramesForAll, + fontSize, + } = formState + + const response = await fetch('/api/worksheets/settings', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + type: 'addition', + config: { + problemsPerPage, + cols, + pages, + orientation, + name, + pAnyStart, + pAllStart, + interpolate, + showCarryBoxes, + showAnswerBoxes, + showPlaceValueColors, + showProblemNumbers, + showCellBorder, + showTenFrames, + showTenFramesForAll, + fontSize, + }, + }), + }) + + if (response.ok) { + setLastSaved(new Date()) + } + } catch (error) { + console.error('Failed to save worksheet settings:', error) + } finally { + setIsSaving(false) + } + }, 1000) // 1 second debounce for auto-save + + return () => clearTimeout(timer) + }, [formState, settingsLoaded]) + const handleFormChange = (updates: Partial) => { setFormState((prev) => { const newState = { ...prev, ...updates } @@ -192,16 +293,39 @@ export default function AdditionWorksheetPage() { })} > {/* Configuration Panel */} -
- +
+
+ +
+ + {/* Settings saved indicator */} + {settingsLoaded && ( +
+ {isSaving ? ( + Saving settings... + ) : lastSaved ? ( + + ✓ Settings saved at {lastSaved.toLocaleTimeString()} + + ) : null} +
+ )}
{/* Preview & Generate Panel */}