Title, Provider, tags from paperless
This commit is contained in:
@@ -13,7 +13,7 @@ import {
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { z } from "zod";
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
createContract,
|
||||
fetchContract,
|
||||
fetchPaperlessDocument,
|
||||
fetchPaperlessDocumentById,
|
||||
updateContract
|
||||
} from "../api/contracts";
|
||||
import { fetchServerConfig } from "../api/config";
|
||||
@@ -29,6 +30,7 @@ import PaperlessSearchDialog from "../components/PaperlessSearchDialog";
|
||||
import PageHeader from "../components/PageHeader";
|
||||
import { useSnackbar } from "../hooks/useSnackbar";
|
||||
import { ContractPayload, PaperlessDocument } from "../types";
|
||||
import { extractPaperlessProvider, extractPaperlessTags, extractPaperlessTitle } from "../utils/paperless";
|
||||
|
||||
const formSchema = z.object({
|
||||
title: z.string().min(1, "Titel erforderlich"),
|
||||
@@ -94,7 +96,8 @@ export default function ContractForm({ mode }: Props) {
|
||||
formState: { errors },
|
||||
reset,
|
||||
setValue,
|
||||
watch
|
||||
watch,
|
||||
getValues
|
||||
} = useForm<FormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
@@ -134,6 +137,25 @@ export default function ContractForm({ mode }: Props) {
|
||||
const [selectedDocument, setSelectedDocument] = useState<PaperlessDocument | null>(null);
|
||||
const paperlessDocumentId = watch("paperlessDocumentId");
|
||||
|
||||
const watchedTitle = watch("title");
|
||||
const watchedProvider = watch("provider");
|
||||
const watchedCategory = watch("category");
|
||||
const watchedPaperlessId = watch("paperlessDocumentId");
|
||||
const watchedTags = watch("tags");
|
||||
|
||||
const providerSuggestion = useMemo(
|
||||
() => (selectedDocument ? extractPaperlessProvider(selectedDocument) : null),
|
||||
[selectedDocument]
|
||||
);
|
||||
const tagsSuggestion = useMemo(
|
||||
() => (selectedDocument ? extractPaperlessTags(selectedDocument) : []),
|
||||
[selectedDocument]
|
||||
);
|
||||
const titleSuggestion = useMemo(
|
||||
() => (selectedDocument ? extractPaperlessTitle(selectedDocument) : null),
|
||||
[selectedDocument]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (mode === "edit" && contract) {
|
||||
reset({
|
||||
@@ -179,6 +201,33 @@ export default function ContractForm({ mode }: Props) {
|
||||
}
|
||||
}, [paperlessDocumentId, selectedDocument]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedDocument) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
console.debug("Paperless document details", selectedDocument);
|
||||
console.debug("Extracted suggestions", {
|
||||
title: titleSuggestion,
|
||||
provider: providerSuggestion,
|
||||
tags: tagsSuggestion
|
||||
});
|
||||
}
|
||||
|
||||
if (titleSuggestion && !getValues("title")?.trim()) {
|
||||
setValue("title", titleSuggestion, { shouldDirty: true });
|
||||
}
|
||||
|
||||
if (providerSuggestion && !getValues("provider")?.trim()) {
|
||||
setValue("provider", providerSuggestion, { shouldDirty: true });
|
||||
}
|
||||
|
||||
if (tagsSuggestion.length > 0 && !getValues("tags")?.trim()) {
|
||||
setValue("tags", tagsSuggestion.join(", "), { shouldDirty: true });
|
||||
}
|
||||
}, [selectedDocument, titleSuggestion, providerSuggestion, tagsSuggestion, getValues, setValue]);
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: async (values: FormValues) => {
|
||||
const payload: ContractPayload = {
|
||||
@@ -243,22 +292,34 @@ export default function ContractForm({ mode }: Props) {
|
||||
label={t("contractForm.fields.title")}
|
||||
fullWidth
|
||||
required
|
||||
InputLabelProps={{ shrink: Boolean(watchedTitle?.trim()) }}
|
||||
{...register("title")}
|
||||
error={Boolean(errors.title)}
|
||||
helperText={errors.title?.message}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<TextField label={t("contractForm.fields.provider")} fullWidth {...register("provider")} />
|
||||
<TextField
|
||||
label={t("contractForm.fields.provider")}
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: Boolean(watchedProvider?.trim()) }}
|
||||
{...register("provider")}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<TextField label={t("contractForm.fields.category")} fullWidth {...register("category")} />
|
||||
<TextField
|
||||
label={t("contractForm.fields.category")}
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: Boolean(watchedCategory?.trim()) }}
|
||||
{...register("category")}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Stack direction={{ xs: "column", sm: "row" }} spacing={1} alignItems="flex-start">
|
||||
<TextField
|
||||
label={t("contractForm.fields.paperlessId")}
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: Boolean(watchedPaperlessId?.trim()) }}
|
||||
{...register("paperlessDocumentId")}
|
||||
error={Boolean(errors.paperlessDocumentId)}
|
||||
helperText={errors.paperlessDocumentId?.message}
|
||||
@@ -283,6 +344,17 @@ export default function ContractForm({ mode }: Props) {
|
||||
})}
|
||||
</Typography>
|
||||
)}
|
||||
{(providerSuggestion || tagsSuggestion.length > 0) && (
|
||||
<Typography variant="caption" color="text.secondary" sx={{ display: "block", mt: 0.5 }}>
|
||||
{providerSuggestion
|
||||
? t("contractForm.suggestionProvider", { provider: providerSuggestion })
|
||||
: null}
|
||||
{providerSuggestion && tagsSuggestion.length > 0 ? " • " : ""}
|
||||
{tagsSuggestion.length > 0
|
||||
? t("contractForm.suggestionTags", { tags: tagsSuggestion.slice(0, 5).join(", ") })
|
||||
: null}
|
||||
</Typography>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<TextField
|
||||
@@ -368,7 +440,8 @@ export default function ContractForm({ mode }: Props) {
|
||||
<TextField
|
||||
label={t("contractForm.fields.tags")}
|
||||
fullWidth
|
||||
placeholder={t("contractForm.tagsPlaceholder")}
|
||||
placeholder={watchedTags?.trim() ? undefined : t("contractForm.tagsPlaceholder")}
|
||||
InputLabelProps={{ shrink: Boolean(watchedTags?.trim()) }}
|
||||
{...register("tags")}
|
||||
/>
|
||||
</Grid>
|
||||
@@ -390,9 +463,23 @@ export default function ContractForm({ mode }: Props) {
|
||||
<PaperlessSearchDialog
|
||||
open={searchDialogOpen}
|
||||
onClose={() => setSearchDialogOpen(false)}
|
||||
onSelect={(doc) => {
|
||||
onSelect={async (doc) => {
|
||||
const idValue = doc.id ? String(doc.id) : "";
|
||||
setValue("paperlessDocumentId", idValue, { shouldDirty: true });
|
||||
|
||||
if (doc.id) {
|
||||
try {
|
||||
const detailed = await fetchPaperlessDocumentById(doc.id);
|
||||
if (detailed) {
|
||||
setSelectedDocument(detailed);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
// Fallback to the selected doc if the detail fetch fails.
|
||||
console.error("Failed to fetch Paperless document details", error);
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedDocument(doc);
|
||||
}}
|
||||
/>
|
||||
|
||||
1
frontend/src/routes/ContractForm.tsx.orig
Normal file
1
frontend/src/routes/ContractForm.tsx.orig
Normal file
@@ -0,0 +1 @@
|
||||
<placeholder original snippet>
|
||||
Reference in New Issue
Block a user