Adds a new "ההחלטה" tab to the case detail page showing all 12 decision
blocks with rendered markdown content and inline editing that saves back
to the DB via two new FastAPI endpoints.
Backend (web/app.py):
- GET /api/cases/{n}/decision-blocks — returns all 12 blocks (empty
ones included) merged from BLOCK_CONFIG + decision_blocks table.
Exposes source_of_truth ("docx"|"blocks") and active_draft_path.
- PUT /api/cases/{n}/decision-blocks/{block_id} — inline save via
block_writer.save_block_content; warns (does not block) when an
active DOCX draft exists.
Frontend:
- src/lib/api/decision-blocks.ts — typed hooks (useDecisionBlocks,
useSaveBlock) following the cases.ts hand-written-module pattern.
- src/components/cases/decision-blocks-panel.tsx — accordion of 12
blocks; view mode renders Markdown component; edit mode is a textarea
with on-blur save (derived from ChairEditor pattern, setState-during-
render for re-sync to avoid effect cascade).
- BLOCK_LABELS in feedback.ts extended from 7 → 12 blocks.
- cases/[caseNumber]/page.tsx — new "ההחלטה" tab wired to the panel.
No DB migration required — decision_blocks + active_draft_path exist.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
76 lines
2.2 KiB
TypeScript
76 lines
2.2 KiB
TypeScript
/**
|
|
* Decision-blocks domain hooks.
|
|
*
|
|
* The 12-block decision content lives in the `decision_blocks` table and was
|
|
* previously only reachable via DOCX export. These hooks back the interactive
|
|
* block viewer/editor on the case page:
|
|
* GET /api/cases/{n}/decision-blocks → all 12 blocks (empty included)
|
|
* PUT /api/cases/{n}/decision-blocks/{block} → save inline-edited content
|
|
*
|
|
* The endpoints aren't declared with response models on the FastAPI side, so
|
|
* we maintain local types here (same convention as cases.ts).
|
|
*/
|
|
|
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
import { apiRequest } from "./client";
|
|
|
|
export type BlockStatus = "empty" | "draft" | "review" | "final";
|
|
|
|
export type DecisionBlock = {
|
|
block_id: string;
|
|
block_index: number;
|
|
title: string;
|
|
content: string;
|
|
word_count: number;
|
|
status: BlockStatus;
|
|
generation_type: string;
|
|
model_used: string;
|
|
/** ISO timestamp; null when the block was never written */
|
|
updated_at: string | null;
|
|
};
|
|
|
|
export type DecisionBlocksResponse = {
|
|
case_number: string;
|
|
has_decision: boolean;
|
|
decision_id: string | null;
|
|
active_draft_path: string | null;
|
|
source_of_truth: "docx" | "blocks";
|
|
blocks: DecisionBlock[];
|
|
};
|
|
|
|
export type SaveBlockResponse = {
|
|
block: DecisionBlock;
|
|
active_draft_warning: boolean;
|
|
};
|
|
|
|
export const decisionBlocksKeys = {
|
|
all: ["decision-blocks"] as const,
|
|
case: (caseNumber: string) =>
|
|
[...decisionBlocksKeys.all, caseNumber] as const,
|
|
};
|
|
|
|
export function useDecisionBlocks(caseNumber: string) {
|
|
return useQuery({
|
|
queryKey: decisionBlocksKeys.case(caseNumber),
|
|
queryFn: ({ signal }) =>
|
|
apiRequest<DecisionBlocksResponse>(
|
|
`/api/cases/${caseNumber}/decision-blocks`,
|
|
{ signal },
|
|
),
|
|
});
|
|
}
|
|
|
|
export function useSaveBlock(caseNumber: string) {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ blockId, content }: { blockId: string; content: string }) =>
|
|
apiRequest<SaveBlockResponse>(
|
|
`/api/cases/${caseNumber}/decision-blocks/${blockId}`,
|
|
{ method: "PUT", body: { content } },
|
|
),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: decisionBlocksKeys.case(caseNumber) });
|
|
},
|
|
});
|
|
}
|