diff --git a/src/components/item-form.tsx b/src/components/item-form.tsx
index 97a3189..b0d3734 100644
--- a/src/components/item-form.tsx
+++ b/src/components/item-form.tsx
@@ -127,7 +127,7 @@ export function ItemForm({
})
const [stagedFiles, setStagedFiles] = useState([])
- // Staged marks & notes (create mode only)
+ // Staged marks & notes
const [stagedMarks, setStagedMarks] = useState([])
const [stagedNotes, setStagedNotes] = useState([])
@@ -144,7 +144,8 @@ export function ItemForm({
"provenance",
...(typeDef && typeDef.fields.length > 0 ? ["typeFields"] : []),
"dimensions",
- ...(!isEdit ? ["marks", "notes"] : []),
+ "marks",
+ "notes",
]
const allExpanded = sectionKeys.every((k) => openSections[k])
const toggleAll = () => {
@@ -517,27 +518,23 @@ export function ItemForm({
- {/* Marks section (create only) */}
- {!isEdit && (
- toggleSection("marks", v)}
- >
-
-
- )}
+ {/* Marks section */}
+ toggleSection("marks", v)}
+ >
+
+
- {/* Notes section (create only) */}
- {!isEdit && (
- toggleSection("notes", v)}
- >
-
-
- )}
+ {/* Notes section */}
+ toggleSection("notes", v)}
+ >
+
+
)}
- {/* Tabs */}
-
-
- Details
- Images
- Provenance
- Dimensions
- Marks
- Notes
-
-
-
- {item.description && (
-
- )}
-
-
-
-
-
- {item.type_fields && typeDef && typeDef.fields.length > 0 && (
- <>
-
-
- {typeDef.label} Details
-
- {typeDef.fields.map((field) => {
- const raw = (
- item.type_fields as Record
- )?.[field.name]
- if (!raw) return null
- let display = String(raw)
- if (field.type === "enum" && field.options) {
- const opt = field.options.find((o) => o.value === raw)
- if (opt) display = opt.label
- }
- return (
-
- )
- })}
- >
- )}
- {!item.description &&
- !item.location &&
- !item.acquisition_date &&
- !item.acquisition_price &&
- !item.estimated_value &&
- !item.acquisition_source &&
- !item.type_fields && (
-
+ {/* Expand / Collapse all */}
+
+
+ {/* Sections */}
+
+
toggleSection("details", v)}
+ >
+
+ {item.description && (
+
+ )}
+
+
+
+
+
+ {item.type_fields && typeDef && typeDef.fields.length > 0 && (
+ <>
+
+
+ {typeDef.label} Details
+
+ {typeDef.fields.map((field) => {
+ const raw = (
+ item.type_fields as Record
+ )?.[field.name]
+ if (!raw) return null
+ let display = String(raw)
+ if (field.type === "enum" && field.options) {
+ const opt = field.options.find((o) => o.value === raw)
+ if (opt) display = opt.label
+ }
+ return (
+
+ )
+ })}
+ >
)}
-
+ {!item.description &&
+ !item.location &&
+ !item.acquisition_date &&
+ !item.acquisition_price &&
+ !item.estimated_value &&
+ !item.acquisition_source &&
+ !item.type_fields && (
+
+ )}
+
+
-
+ toggleSection("images", v)}
+ >
-
-
-
-
-
-
- {(item.artist_maker || item.origin || item.date_era) && (
-
- )}
-
-
+
-
-
-
-
-
-
- {!item.height_cm &&
- !item.width_cm &&
- !item.depth_cm &&
- !item.weight_kg &&
- !item.materials && (
-
+ toggleSection("provenance", v)}
+ >
+
+
+
+
+ {(item.artist_maker || item.origin || item.date_era) && (
+
)}
-
+
+
+
-
+ toggleSection("dimensions", v)}
+ >
+
+
+
+
+
+
+ {!item.height_cm &&
+ !item.width_cm &&
+ !item.depth_cm &&
+ !item.weight_kg &&
+ !item.materials && (
+
+ )}
+
+
+
+ toggleSection("marks", v)}
+ >
-
+
-
+ toggleSection("notes", v)}
+ >
-
-
+
+
{message}
)
}
+function DetailSection({
+ title,
+ open,
+ onOpenChange,
+ children,
+}: {
+ title: string
+ open: boolean
+ onOpenChange: (open: boolean) => void
+ children: React.ReactNode
+}) {
+ return (
+
+
+
+
+
+ {children}
+
+
+ )
+}
+
function ItemHeaderSkeleton() {
return (
@@ -347,11 +451,25 @@ function EditItemDialog({
const queryClient = useQueryClient()
const [uploading, setUploading] = useState(false)
const imageUpload = useUploadItemImageItemsItemIdImagesPost()
+ const createMark = useCreateMarkItemsItemIdMarksPost()
+ const markImageUpload = useUploadMarkImageItemsItemIdMarksMarkIdImagesPost()
+ const createNote = useCreateItemNoteItemsItemIdNotesPost()
const handleSuccess = useCallback(
- async (_updatedItem: ItemRead, stagedFiles: File[]) => {
+ async (
+ _updatedItem: ItemRead,
+ stagedFiles: File[],
+ stagedMarks: StagedMark[],
+ stagedNotes: StagedNote[]
+ ) => {
+ const hasUploads =
+ stagedFiles.length > 0 ||
+ stagedMarks.length > 0 ||
+ stagedNotes.length > 0
+ if (hasUploads) setUploading(true)
+
+ // 1. Upload item images
if (stagedFiles.length > 0) {
- setUploading(true)
const failed: string[] = []
for (const file of stagedFiles) {
try {
@@ -363,12 +481,56 @@ function EditItemDialog({
failed.push(file.name)
}
}
- setUploading(false)
if (failed.length > 0) {
toast.error(`Failed to upload: ${failed.join(", ")}`)
}
}
+ // 2. Create marks + upload mark images
+ for (const sm of stagedMarks) {
+ try {
+ const markRes = await createMark.mutateAsync({
+ itemId: item.id,
+ data: {
+ title: sm.title || undefined,
+ description: sm.description || undefined,
+ },
+ })
+ if (markRes.status === 201 && sm.files.length > 0) {
+ for (const file of sm.files) {
+ try {
+ await markImageUpload.mutateAsync({
+ itemId: item.id,
+ markId: markRes.data.id,
+ data: { file },
+ })
+ } catch {
+ toast.error(`Failed to upload mark image: ${file.name}`)
+ }
+ }
+ }
+ } catch {
+ toast.error(`Failed to create mark: ${sm.title || "Untitled"}`)
+ }
+ }
+
+ // 3. Create notes
+ for (const sn of stagedNotes) {
+ try {
+ await createNote.mutateAsync({
+ itemId: item.id,
+ data: {
+ title: sn.title || undefined,
+ body: sn.body,
+ },
+ })
+ } catch {
+ toast.error(`Failed to create note: ${sn.title || "Untitled"}`)
+ }
+ }
+
+ if (hasUploads) setUploading(false)
+
queryClient.invalidateQueries({
queryKey: getGetItemItemsItemIdGetQueryKey(item.id),
})
@@ -386,7 +548,16 @@ function EditItemDialog({
}
onOpenChange(false)
},
- [item.id, item.collection_id, onOpenChange, queryClient, imageUpload]
+ [
+ item.id,
+ item.collection_id,
+ onOpenChange,
+ queryClient,
+ imageUpload,
+ createMark,
+ markImageUpload,
+ createNote,
+ ]
)
return (
@@ -397,7 +568,7 @@ function EditItemDialog({
{uploading ? (
- Uploading images…
+ Saving…
) : (