init
This commit is contained in:
45
src/data.ts
Normal file
45
src/data.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import type { BuyerDocument, PersonGroup } from "./types.ts";
|
||||
|
||||
const text = (value: unknown) => typeof value === "string" ? value.trim() : "";
|
||||
const normalize = (value: unknown) => text(value).toLocaleLowerCase();
|
||||
|
||||
export function validateDocuments(value: unknown): BuyerDocument[] {
|
||||
if (!Array.isArray(value)) throw new Error("JSON root must be an array.");
|
||||
return value.map((item, index) => {
|
||||
if (!item || typeof item !== "object") throw new Error(`Record ${index + 1} is not an object.`);
|
||||
const doc = item as Partial<BuyerDocument>;
|
||||
if (!text(doc.file_name) || !text(doc.name_from_filename) || !text(doc._letter)) {
|
||||
throw new Error(`Record ${index + 1} is missing file_name, name_from_filename, or _letter.`);
|
||||
}
|
||||
return doc as BuyerDocument;
|
||||
});
|
||||
}
|
||||
|
||||
export function groupDocuments(documents: BuyerDocument[]): PersonGroup[] {
|
||||
const groups = new Map<string, BuyerDocument[]>();
|
||||
for (const doc of documents) {
|
||||
const key = doc.name_from_filename.trim();
|
||||
const bucket = groups.get(key) ?? [];
|
||||
bucket.push(doc);
|
||||
groups.set(key, bucket);
|
||||
}
|
||||
|
||||
return [...groups.entries()]
|
||||
.map(([key, docs]) => {
|
||||
docs.sort((a, b) => a.file_name.localeCompare(b.file_name));
|
||||
const preferred = docs.find((d) => text(d.prospective_buyer))?.prospective_buyer;
|
||||
const searchText = normalize([
|
||||
key,
|
||||
preferred,
|
||||
...docs.flatMap((d) => [d.types_of_business_raw, d.notes_business_raw, d.address]),
|
||||
].filter(Boolean).join("\n"));
|
||||
return { key, displayName: text(preferred) || key, documents: docs, searchText };
|
||||
})
|
||||
.sort((a, b) => a.key.localeCompare(b.key));
|
||||
}
|
||||
|
||||
export function filterGroups(groups: PersonGroup[], query: string): PersonGroup[] {
|
||||
const terms = normalize(query).split(/\s+/).filter(Boolean);
|
||||
if (!terms.length) return groups;
|
||||
return groups.filter((group) => terms.every((term) => group.searchText.includes(term)));
|
||||
}
|
||||
13
src/paths.ts
Normal file
13
src/paths.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { isAbsolute, join, normalize, relative } from "jsr:@std/path";
|
||||
import type { BuyerDocument } from "./types.ts";
|
||||
|
||||
export function resolvePdfPath(baseDirectory: string, doc: BuyerDocument): string {
|
||||
if (!baseDirectory.trim()) throw new Error("PDF base directory is not configured.");
|
||||
if (!isAbsolute(baseDirectory)) throw new Error("PDF base directory must be absolute.");
|
||||
|
||||
const base = normalize(baseDirectory);
|
||||
const full = normalize(join(base, doc._letter, doc.file_name));
|
||||
const rel = relative(base, full);
|
||||
if (rel.startsWith("..") || isAbsolute(rel)) throw new Error("Resolved PDF path escapes the base directory.");
|
||||
return full;
|
||||
}
|
||||
35
src/types.ts
Normal file
35
src/types.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
export interface BuyerDocument {
|
||||
file_name: string;
|
||||
name_from_filename: string;
|
||||
_letter: string;
|
||||
prospective_buyer?: string | null;
|
||||
name_company?: string | null;
|
||||
company?: string | null;
|
||||
phone?: string | null;
|
||||
cell?: string | null;
|
||||
email?: string | null;
|
||||
address?: string | null;
|
||||
state?: string | null;
|
||||
how_did_you_hear?: string | null;
|
||||
interested_in_updates?: string | null;
|
||||
types_of_business_raw?: string | null;
|
||||
notes_business_raw?: string | null;
|
||||
background_experience?: string | null;
|
||||
total_purchase_price?: string | null;
|
||||
down_payment?: string | null;
|
||||
down_payment_raw?: string | null;
|
||||
date_of_introduction?: string | null;
|
||||
_doc_type?: string | null;
|
||||
_pages_total?: number | null;
|
||||
_notes_page?: number | null;
|
||||
_info_page?: number | null;
|
||||
_ca_page?: number | null;
|
||||
_vision_error?: string | null;
|
||||
}
|
||||
|
||||
export interface PersonGroup {
|
||||
key: string;
|
||||
displayName: string;
|
||||
documents: BuyerDocument[];
|
||||
searchText: string;
|
||||
}
|
||||
Reference in New Issue
Block a user