From f165ccb941f655d5c198cd33609240963258c55d Mon Sep 17 00:00:00 2001 From: Justin Paul Date: Sun, 7 Jun 2026 09:58:45 -0400 Subject: [PATCH] Tree search + click-rebuild; searchable relationship picker; gender dropdown - Tree page: add a "Find a person" search box that jumps the chart to a match and rebuilds the hourglass (parents/grandparents/partner/children) around them. Clicking any card recenters via family-chart's default behavior (setAncestryDepth 3 / setProgenyDepth 2), syncing focus through setAfterUpdate for the "Open profile" link. - Person detail: replace the relationship "add" setPGiven(e.target.value)} /> setPSurname(e.target.value)} /> - setPGender(e.target.value)} /> + - + {(relKind === "parent" || relKind === "child") && ( setQuery(e.target.value)} + placeholder="Find a person…" + className="w-56" + /> + {matches.length > 0 && ( + + )} + +
diff --git a/frontend/components/person-combobox.tsx b/frontend/components/person-combobox.tsx new file mode 100644 index 0000000..0e361a8 --- /dev/null +++ b/frontend/components/person-combobox.tsx @@ -0,0 +1,103 @@ +"use client"; + +import { useEffect, useMemo, useRef, useState } from "react"; + +import type { components } from "@/lib/api/schema"; + +type Person = components["schemas"]["PersonRead"]; + +/** + * A type-to-filter person picker. Shows a text input; as you type, a dropdown + * of matching people appears. Selecting one sets `value` (a person id) and + * fills the input with their name. Replaces a plain setOpen(true)} + onChange={(e) => { + setQuery(e.target.value); + setOpen(true); + if (value) onChange(""); // typing invalidates the prior pick + }} + /> + {open && matches.length > 0 && ( +
    + {matches.map((p) => ( +
  • + +
  • + ))} +
+ )} +
+ ); +} diff --git a/frontend/lib/api/schema.d.ts b/frontend/lib/api/schema.d.ts index f6b65f3..f9e3013 100644 --- a/frontend/lib/api/schema.d.ts +++ b/frontend/lib/api/schema.d.ts @@ -190,7 +190,8 @@ export interface paths { delete: operations["delete_tree_api_v1_trees__tree_id__delete"]; options?: never; head?: never; - patch?: never; + /** Update Tree */ + patch: operations["update_tree_api_v1_trees__tree_id__patch"]; trace?: never; }; "/api/v1/trees/{tree_id}/restore": { @@ -366,7 +367,8 @@ export interface paths { delete: operations["delete_relationship_api_v1_trees__tree_id__relationships__relationship_id__delete"]; options?: never; head?: never; - patch?: never; + /** Update Relationship */ + patch: operations["update_relationship_api_v1_trees__tree_id__relationships__relationship_id__patch"]; trace?: never; }; "/api/v1/trees/{tree_id}/sources": { @@ -402,7 +404,8 @@ export interface paths { delete: operations["delete_source_api_v1_trees__tree_id__sources__source_id__delete"]; options?: never; head?: never; - patch?: never; + /** Update Source */ + patch: operations["update_source_api_v1_trees__tree_id__sources__source_id__patch"]; trace?: never; }; "/api/v1/trees/{tree_id}/citations": { @@ -437,7 +440,8 @@ export interface paths { delete: operations["delete_citation_api_v1_trees__tree_id__citations__citation_id__delete"]; options?: never; head?: never; - patch?: never; + /** Update Citation */ + patch: operations["update_citation_api_v1_trees__tree_id__citations__citation_id__patch"]; trace?: never; }; "/api/v1/trees/{tree_id}/media": { @@ -489,7 +493,8 @@ export interface paths { delete: operations["delete_media_api_v1_trees__tree_id__media__media_id__delete"]; options?: never; head?: never; - patch?: never; + /** Update Media */ + patch: operations["update_media_api_v1_trees__tree_id__media__media_id__patch"]; trace?: never; }; "/api/v1/trees/{tree_id}/gedcom/import": { @@ -610,6 +615,14 @@ export interface components { */ created_at: string; }; + /** CitationUpdate */ + CitationUpdate: { + /** Page */ + page?: string | null; + /** Detail */ + detail?: string | null; + confidence?: components["schemas"]["CitationConfidence"] | null; + }; /** EventCreate */ EventCreate: { /** Event Type */ @@ -756,6 +769,17 @@ export interface components { /** Url */ url?: string | null; }; + /** MediaUpdate */ + MediaUpdate: { + /** Title */ + title?: string | null; + /** Person Id */ + person_id?: string | null; + /** Event Id */ + event_id?: string | null; + /** Source Id */ + source_id?: string | null; + }; /** * ParentChildQualifier * @description Qualifies a parent_child edge so adoption/donor/blended families are @@ -898,6 +922,12 @@ export interface components { * @enum {string} */ RelationshipType: "parent_child" | "partnership" | "sibling"; + /** RelationshipUpdate */ + RelationshipUpdate: { + qualifier?: components["schemas"]["ParentChildQualifier"] | null; + /** Notes */ + notes?: string | null; + }; /** SessionRead */ SessionRead: { user: components["schemas"]["UserRead"]; @@ -962,6 +992,25 @@ export interface components { */ created_at: string; }; + /** SourceUpdate */ + SourceUpdate: { + /** Title */ + title?: string | null; + /** Author */ + author?: string | null; + /** Source Type */ + source_type?: string | null; + /** Repository */ + repository?: string | null; + /** Url */ + url?: string | null; + /** Citation Text */ + citation_text?: string | null; + /** Publication Info */ + publication_info?: string | null; + /** Quality Note */ + quality_note?: string | null; + }; /** TokenRequest */ TokenRequest: { /** Token */ @@ -999,6 +1048,14 @@ export interface components { */ created_at: string; }; + /** TreeUpdate */ + TreeUpdate: { + /** Name */ + name?: string | null; + /** Description */ + description?: string | null; + visibility?: components["schemas"]["TreeVisibility"] | null; + }; /** * TreeVisibility * @enum {string} @@ -1414,6 +1471,41 @@ export interface operations { }; }; }; + update_tree_api_v1_trees__tree_id__patch: { + parameters: { + query?: never; + header?: never; + path: { + tree_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TreeUpdate"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TreeRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; restore_tree_api_v1_trees__tree_id__restore_post: { parameters: { query?: never; @@ -1936,6 +2028,42 @@ export interface operations { }; }; }; + update_relationship_api_v1_trees__tree_id__relationships__relationship_id__patch: { + parameters: { + query?: never; + header?: never; + path: { + tree_id: string; + relationship_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RelationshipUpdate"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RelationshipRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; list_sources_api_v1_trees__tree_id__sources_get: { parameters: { query?: never; @@ -2064,6 +2192,42 @@ export interface operations { }; }; }; + update_source_api_v1_trees__tree_id__sources__source_id__patch: { + parameters: { + query?: never; + header?: never; + path: { + tree_id: string; + source_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SourceUpdate"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SourceRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; list_citations_api_v1_trees__tree_id__citations_get: { parameters: { query?: never; @@ -2160,6 +2324,42 @@ export interface operations { }; }; }; + update_citation_api_v1_trees__tree_id__citations__citation_id__patch: { + parameters: { + query?: never; + header?: never; + path: { + tree_id: string; + citation_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CitationUpdate"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CitationRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; list_media_api_v1_trees__tree_id__media_get: { parameters: { query?: never; @@ -2288,6 +2488,42 @@ export interface operations { }; }; }; + update_media_api_v1_trees__tree_id__media__media_id__patch: { + parameters: { + query?: never; + header?: never; + path: { + tree_id: string; + media_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["MediaUpdate"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MediaRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; import_gedcom_api_v1_trees__tree_id__gedcom_import_post: { parameters: { query?: never; diff --git a/frontend/openapi.json b/frontend/openapi.json index c897474..a449db6 100644 --- a/frontend/openapi.json +++ b/frontend/openapi.json @@ -408,6 +408,57 @@ } } }, + "patch": { + "tags": [ + "trees" + ], + "summary": "Update Tree", + "operationId": "update_tree_api_v1_trees__tree_id__patch", + "parameters": [ + { + "name": "tree_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Tree Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TreeUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TreeRead" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, "delete": { "tags": [ "trees" @@ -1239,6 +1290,67 @@ } }, "/api/v1/trees/{tree_id}/relationships/{relationship_id}": { + "patch": { + "tags": [ + "relationships" + ], + "summary": "Update Relationship", + "operationId": "update_relationship_api_v1_trees__tree_id__relationships__relationship_id__patch", + "parameters": [ + { + "name": "tree_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Tree Id" + } + }, + { + "name": "relationship_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Relationship Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RelationshipUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RelationshipRead" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, "delete": { "tags": [ "relationships" @@ -1434,6 +1546,67 @@ } } }, + "patch": { + "tags": [ + "sources" + ], + "summary": "Update Source", + "operationId": "update_source_api_v1_trees__tree_id__sources__source_id__patch", + "parameters": [ + { + "name": "tree_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Tree Id" + } + }, + { + "name": "source_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Source Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SourceUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SourceRead" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, "delete": { "tags": [ "sources" @@ -1578,6 +1751,67 @@ } }, "/api/v1/trees/{tree_id}/citations/{citation_id}": { + "patch": { + "tags": [ + "citations" + ], + "summary": "Update Citation", + "operationId": "update_citation_api_v1_trees__tree_id__citations__citation_id__patch", + "parameters": [ + { + "name": "tree_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Tree Id" + } + }, + { + "name": "citation_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Citation Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CitationUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CitationRead" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, "delete": { "tags": [ "citations" @@ -1773,6 +2007,67 @@ } }, "/api/v1/trees/{tree_id}/media/{media_id}": { + "patch": { + "tags": [ + "media" + ], + "summary": "Update Media", + "operationId": "update_media_api_v1_trees__tree_id__media__media_id__patch", + "parameters": [ + { + "name": "tree_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Tree Id" + } + }, + { + "name": "media_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "title": "Media Id" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MediaUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MediaRead" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, "delete": { "tags": [ "media" @@ -2212,6 +2507,44 @@ ], "title": "CitationRead" }, + "CitationUpdate": { + "properties": { + "page": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Page" + }, + "detail": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Detail" + }, + "confidence": { + "anyOf": [ + { + "$ref": "#/components/schemas/CitationConfidence" + }, + { + "type": "null" + } + ] + } + }, + "type": "object", + "title": "CitationUpdate" + }, "EventCreate": { "properties": { "event_type": { @@ -2754,6 +3087,59 @@ ], "title": "MediaRead" }, + "MediaUpdate": { + "properties": { + "title": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Title" + }, + "person_id": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "title": "Person Id" + }, + "event_id": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "title": "Event Id" + }, + "source_id": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "title": "Source Id" + } + }, + "type": "object", + "title": "MediaUpdate" + }, "ParentChildQualifier": { "type": "string", "enum": [ @@ -3159,6 +3545,33 @@ ], "title": "RelationshipType" }, + "RelationshipUpdate": { + "properties": { + "qualifier": { + "anyOf": [ + { + "$ref": "#/components/schemas/ParentChildQualifier" + }, + { + "type": "null" + } + ] + }, + "notes": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Notes" + } + }, + "type": "object", + "title": "RelationshipUpdate" + }, "SessionRead": { "properties": { "user": { @@ -3387,6 +3800,100 @@ ], "title": "SourceRead" }, + "SourceUpdate": { + "properties": { + "title": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Title" + }, + "author": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Author" + }, + "source_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Source Type" + }, + "repository": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Repository" + }, + "url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Url" + }, + "citation_text": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Citation Text" + }, + "publication_info": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Publication Info" + }, + "quality_note": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Quality Note" + } + }, + "type": "object", + "title": "SourceUpdate" + }, "TokenRequest": { "properties": { "token": { @@ -3475,6 +3982,44 @@ ], "title": "TreeRead" }, + "TreeUpdate": { + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Description" + }, + "visibility": { + "anyOf": [ + { + "$ref": "#/components/schemas/TreeVisibility" + }, + { + "type": "null" + } + ] + } + }, + "type": "object", + "title": "TreeUpdate" + }, "TreeVisibility": { "type": "string", "enum": [ -- 2.52.0