feat(registratura): smart subject autocomplete v2 — seed templates, project linking, dynamic placeholders

- Add SEED_TEMPLATES catalog (11 doc types x 2-4 templates = ~30 predefined patterns)
- Add {proiect} field type with mini-autocomplete from Tag Manager projects
- Pre-fill {an} fields with current year on template selection
- Dynamic placeholder changes based on documentType + direction (22 combinations)
- Dropdown on empty focus: "Sabloane recomandate" + "Recente" sections
- Direction-aware tooltip on Subiect field (intrat vs iesit examples)
- getRecommendedTemplates() merges seeds + DB-learned, DB takes priority

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
AI Assistant
2026-03-10 12:46:05 +02:00
parent 3a3db3f366
commit e30b437dce
4 changed files with 551 additions and 55 deletions
@@ -1,15 +1,18 @@
"use client";
import { useEffect, useRef } from "react";
import { useEffect, useRef, useState, useMemo } from "react";
import { X } from "lucide-react";
import { cn } from "@/shared/lib/utils";
import type { SubjectTemplate } from "../services/subject-template-service";
import type { Tag } from "@/core/tagging/types";
interface SubjectTemplateInputProps {
template: SubjectTemplate;
fieldValues: Record<string, string>;
onFieldChange: (fieldId: string, value: string) => void;
onClear: () => void;
/** Project tags for {proiect} field autocomplete */
projectTags?: Tag[];
}
export function SubjectTemplateInput({
@@ -17,6 +20,7 @@ export function SubjectTemplateInput({
fieldValues,
onFieldChange,
onClear,
projectTags,
}: SubjectTemplateInputProps) {
const firstFieldRef = useRef<HTMLInputElement>(null);
@@ -52,6 +56,20 @@ export function SubjectTemplateInput({
const isFirst = fieldRendered === 0;
fieldRendered++;
// Project field — render with mini-autocomplete
if (field.fieldType === "proiect" && projectTags && projectTags.length > 0) {
return (
<ProjectFieldInput
key={field.id}
field={field}
value={fieldValues[field.id] ?? ""}
onChange={(val) => onFieldChange(field.id, val)}
projectTags={projectTags}
inputRef={isFirst ? firstFieldRef : undefined}
/>
);
}
return (
<input
key={field.id}
@@ -74,10 +92,98 @@ export function SubjectTemplateInput({
type="button"
onClick={onClear}
className="ml-auto shrink-0 text-muted-foreground hover:text-foreground transition-colors"
title="Renunță la șablon"
title="Renunta la sablon"
>
<X className="h-3.5 w-3.5" />
</button>
</div>
);
}
// ── Project Field with mini-autocomplete ──
interface ProjectFieldInputProps {
field: { id: string; placeholder: string; width: string };
value: string;
onChange: (value: string) => void;
projectTags: Tag[];
inputRef?: React.Ref<HTMLInputElement>;
}
function ProjectFieldInput({
field,
value,
onChange,
projectTags,
inputRef,
}: ProjectFieldInputProps) {
const [focused, setFocused] = useState(false);
const suggestions = useMemo(() => {
if (!value || value.length < 1) return projectTags.slice(0, 8);
const q = value.toLowerCase();
return projectTags
.filter(
(t) =>
t.label.toLowerCase().includes(q) ||
(t.projectCode && t.projectCode.toLowerCase().includes(q)),
)
.slice(0, 8);
}, [value, projectTags]);
const showDropdown = focused && suggestions.length > 0;
return (
<span className="relative inline-flex">
<input
ref={inputRef}
value={value}
onChange={(e) => onChange(e.target.value)}
onFocus={() => setFocused(true)}
onBlur={() => setTimeout(() => setFocused(false), 200)}
placeholder={field.placeholder}
className={cn(
"border-b-2 border-dashed border-primary/40 bg-transparent",
"text-sm font-medium text-foreground",
"px-1 py-0 outline-none",
"focus:border-primary focus:border-solid",
"placeholder:text-muted-foreground/50 placeholder:italic placeholder:text-xs",
field.width,
)}
/>
{showDropdown && (
<div className="absolute top-full left-0 z-20 mt-1 min-w-48 rounded-md border bg-popover p-1 shadow-md max-h-48 overflow-y-auto">
{suggestions.map((tag) => (
<button
key={tag.id}
type="button"
className="w-full rounded px-2 py-1 text-left text-xs hover:bg-accent flex items-center gap-1.5"
onMouseDown={() => {
const display = tag.projectCode
? `${tag.projectCode} ${tag.label}`
: tag.label;
onChange(display);
setFocused(false);
}}
>
{tag.color && (
<span
className="inline-block h-2.5 w-2.5 rounded-full shrink-0"
style={{ backgroundColor: tag.color }}
/>
)}
<span className="truncate">
{tag.projectCode && (
<span className="font-mono text-muted-foreground mr-1">
{tag.projectCode}
</span>
)}
{tag.label}
</span>
</button>
))}
</div>
)}
</span>
);
}