From 22a7d1ad458385b6709462ee02cca2ad65d6bbec Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Sun, 5 Oct 2025 15:03:05 +0200 Subject: [PATCH] Implement note rules So we may specify regex rules to map to notes --- frontend/src/App.tsx | 2 + frontend/src/components/Header.tsx | 16 +-- frontend/src/components/SignatureCard.tsx | 10 +- frontend/src/components/SignatureListItem.tsx | 10 +- frontend/src/lib/pbtypes.ts | 102 +++++++++++++++ frontend/src/pages/SignatureRules.tsx | 119 ++++++++++++++++++ frontend/src/pages/SystemView.tsx | 9 +- frontend/src/utils/signatureParser.ts | 75 +++-------- 8 files changed, 271 insertions(+), 72 deletions(-) create mode 100644 frontend/src/pages/SignatureRules.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 1b5f8a6..fc3e39a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -7,6 +7,7 @@ import { SystemView } from "./pages/SystemView"; import NotFound from "./pages/NotFound"; import "./App.css"; import { SearchDialog } from "@/components/SearchDialog"; +import { SignatureRules } from "./pages/SignatureRules"; const queryClient = new QueryClient(); @@ -19,6 +20,7 @@ function App() { } /> } /> } /> + } /> } /> diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 0d89ab0..487e323 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -98,23 +98,23 @@ export const Header = ({ title, breadcrumbs = [] }: HeaderProps) => {

{title}

+ {chars.length > 0 && ( -
{chars.map((c) => ( - handleCharacterClick(c)} - className={`px-3 py-1 text-xs cursor-pointer transition-colors text-center overflow-hidden text-ellipsis ${ - c.waypoint_enabled - ? 'bg-purple-500/20 text-purple-200 border border-purple-400/40 hover:bg-purple-500/30' + className={`px-3 py-1 text-xs cursor-pointer transition-colors text-center overflow-hidden text-ellipsis ${c.waypoint_enabled + ? 'bg-purple-500/20 text-purple-200 border border-purple-400/40 hover:bg-purple-500/30' : 'bg-gray-500/20 text-gray-400 border border-gray-400/40 hover:bg-gray-500/30' - }`} + }`} title={`Click to ${c.waypoint_enabled ? 'disable' : 'enable'} waypoints for ${c.character_name}`} > {c.character_name} diff --git a/frontend/src/components/SignatureCard.tsx b/frontend/src/components/SignatureCard.tsx index 6028dcb..b500f39 100644 --- a/frontend/src/components/SignatureCard.tsx +++ b/frontend/src/components/SignatureCard.tsx @@ -75,10 +75,12 @@ export const SignatureCard = ({ signature, onDelete, onUpdate }: SignatureCardPr {signature.signame || 'Unnamed Signature'} {signature.note && ( -
- - {signature.note} - +
+ {signature.note.split(';').filter(Boolean).map((note, index) => ( + + {note.trim()} + + ))}
)}
diff --git a/frontend/src/components/SignatureListItem.tsx b/frontend/src/components/SignatureListItem.tsx index 746975b..4edc226 100644 --- a/frontend/src/components/SignatureListItem.tsx +++ b/frontend/src/components/SignatureListItem.tsx @@ -117,9 +117,13 @@ export const SignatureListItem = ({ signature, onDelete, onUpdate }: SignatureLi )} {signature.note && ( - - {signature.note} - +
+ {signature.note.split(';').filter(Boolean).map((note, index) => ( + + {note.trim()} + + ))} +
)}
diff --git a/frontend/src/lib/pbtypes.ts b/frontend/src/lib/pbtypes.ts index 0ddda32..2c0b7f9 100644 --- a/frontend/src/lib/pbtypes.ts +++ b/frontend/src/lib/pbtypes.ts @@ -11,8 +11,13 @@ export enum Collections { Mfas = "_mfas", Otps = "_otps", Superusers = "_superusers", + IndBillitem = "ind_billItem", + IndChar = "ind_char", + IndJob = "ind_job", + IndTransaction = "ind_transaction", Regionview = "regionview", Signature = "signature", + SignatureNoteRules = "signature_note_rules", Sigview = "sigview", System = "system", WormholeSystems = "wormholeSystems", @@ -94,6 +99,74 @@ export type SuperusersRecord = { verified?: boolean } +export type IndBillitemRecord = { + created?: IsoDateString + id: string + name: string + quantity: number + updated?: IsoDateString +} + +export type IndCharRecord = { + created?: IsoDateString + id: string + name: string + updated?: IsoDateString +} + +export enum IndJobStatusOptions { + "Planned" = "Planned", + "Acquisition" = "Acquisition", + "Running" = "Running", + "Done" = "Done", + "Selling" = "Selling", + "Closed" = "Closed", + "Tracked" = "Tracked", + "Staging" = "Staging", + "Inbound" = "Inbound", + "Outbound" = "Outbound", + "Delivered" = "Delivered", + "Queued" = "Queued", +} +export type IndJobRecord = { + billOfMaterials?: RecordIdString[] + character?: RecordIdString + consumedMaterials?: RecordIdString[] + created?: IsoDateString + expenditures?: RecordIdString[] + id: string + income?: RecordIdString[] + jobEnd?: IsoDateString + jobStart?: IsoDateString + outputItem: string + outputQuantity: number + parallel?: number + produced?: number + projectedCost?: number + projectedRevenue?: number + runtime?: number + saleEnd?: IsoDateString + saleStart?: IsoDateString + status: IndJobStatusOptions + updated?: IsoDateString +} + +export type IndTransactionRecord = { + buyer?: string + corporation?: string + created?: IsoDateString + date: IsoDateString + id: string + itemName: string + job?: RecordIdString + location?: string + quantity: number + totalPrice: number + unitPrice: number + updated?: IsoDateString + wallet?: string +} + export type RegionviewRecord = { id: string sigcount?: number @@ -114,6 +187,15 @@ export type SignatureRecord = { updated?: IsoDateString } +export type SignatureNoteRulesRecord = { + created?: IsoDateString + enabled?: boolean + id: string + note: string + regex: string + updated?: IsoDateString +} + export type SigviewRecord = { created?: IsoDateString dangerous?: boolean @@ -153,8 +235,13 @@ export type ExternalauthsResponse = Required = Required & BaseSystemFields export type OtpsResponse = Required & BaseSystemFields export type SuperusersResponse = Required & AuthSystemFields +export type IndBillitemResponse = Required & BaseSystemFields +export type IndCharResponse = Required & BaseSystemFields +export type IndJobResponse = Required & BaseSystemFields +export type IndTransactionResponse = Required & BaseSystemFields export type RegionviewResponse = Required & BaseSystemFields export type SignatureResponse = Required & BaseSystemFields +export type SignatureNoteRulesResponse = Required & BaseSystemFields export type SigviewResponse = Required & BaseSystemFields export type SystemResponse = Required & BaseSystemFields export type WormholeSystemsResponse = Required & BaseSystemFields @@ -167,8 +254,13 @@ export type CollectionRecords = { _mfas: MfasRecord _otps: OtpsRecord _superusers: SuperusersRecord + ind_billItem: IndBillitemRecord + ind_char: IndCharRecord + ind_job: IndJobRecord + ind_transaction: IndTransactionRecord regionview: RegionviewRecord signature: SignatureRecord + signature_note_rules: SignatureNoteRulesRecord sigview: SigviewRecord system: SystemRecord wormholeSystems: WormholeSystemsRecord @@ -180,8 +272,13 @@ export type CollectionResponses = { _mfas: MfasResponse _otps: OtpsResponse _superusers: SuperusersResponse + ind_billItem: IndBillitemResponse + ind_char: IndCharResponse + ind_job: IndJobResponse + ind_transaction: IndTransactionResponse regionview: RegionviewResponse signature: SignatureResponse + signature_note_rules: SignatureNoteRulesResponse sigview: SigviewResponse system: SystemResponse wormholeSystems: WormholeSystemsResponse @@ -196,8 +293,13 @@ export type TypedPocketBase = PocketBase & { collection(idOrName: '_mfas'): RecordService collection(idOrName: '_otps'): RecordService collection(idOrName: '_superusers'): RecordService + collection(idOrName: 'ind_billItem'): RecordService + collection(idOrName: 'ind_char'): RecordService + collection(idOrName: 'ind_job'): RecordService + collection(idOrName: 'ind_transaction'): RecordService collection(idOrName: 'regionview'): RecordService collection(idOrName: 'signature'): RecordService + collection(idOrName: 'signature_note_rules'): RecordService collection(idOrName: 'sigview'): RecordService collection(idOrName: 'system'): RecordService collection(idOrName: 'wormholeSystems'): RecordService diff --git a/frontend/src/pages/SignatureRules.tsx b/frontend/src/pages/SignatureRules.tsx new file mode 100644 index 0000000..66f341e --- /dev/null +++ b/frontend/src/pages/SignatureRules.tsx @@ -0,0 +1,119 @@ +import { useEffect, useState } from 'react'; +import pb from '@/lib/pocketbase'; +import { Header } from '@/components/Header'; +import { Button } from '@/components/ui/button'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; +import { Input } from '@/components/ui/input'; +import { Switch } from '@/components/ui/switch'; +import { toast } from '@/hooks/use-toast'; +import { SignatureNoteRulesResponse, Collections } from '@/lib/pbtypes'; + +export const SignatureRules = () => { + const [rules, setRules] = useState([]); + const [loading, setLoading] = useState(false); + const [creating, setCreating] = useState({ regex: '', note: '' }); + + const load = async () => { + setLoading(true); + try { + const list = await pb.collection(Collections.SignatureNoteRules).getFullList({ batch: 1000, sort: '-updated' }); + setRules(list); + } catch (e: any) { + toast({ title: 'Load failed', description: String(e), variant: 'destructive' }); + } finally { + setLoading(false); + } + }; + + useEffect(() => { load(); }, []); + + const handleCreate = async () => { + if (!creating.regex.trim() || !creating.note.trim()) return; + try { + await pb.collection(Collections.SignatureNoteRules).create({ regex: creating.regex.trim(), note: creating.note.trim(), enabled: true }); + setCreating({ regex: '', note: '' }); + await load(); + toast({ title: 'Rule added', description: 'New rule created.' }); + } catch (e: any) { + toast({ title: 'Create failed', description: String(e), variant: 'destructive' }); + } + }; + + const handleUpdate = async (id: string, patch: Partial) => { + try { + await pb.collection(Collections.SignatureNoteRules).update(id, patch); + await load(); + } catch (e: any) { + toast({ title: 'Update failed', description: String(e), variant: 'destructive' }); + } + }; + + const handleDelete = async (id: string) => { + try { + await pb.collection(Collections.SignatureNoteRules).delete(id); + await load(); + toast({ title: 'Rule deleted' }); + } catch (e: any) { + toast({ title: 'Delete failed', description: String(e), variant: 'destructive' }); + } + }; + + return ( +
+
+
+
+
+
+ setCreating({ ...creating, regex: e.target.value })} + className="font-mono" + /> + setCreating({ ...creating, note: e.target.value })} /> + +
+
+ +
+ + + + Enabled + Regex + Note + + + + + {rules.map(r => ( + + + handleUpdate(r.id, { enabled: v })} /> + + + handleUpdate(r.id, { regex: e.target.value })} + className="font-mono" + /> + + + handleUpdate(r.id, { note: e.target.value })} /> + + + + + + ))} + +
+
+
+
+
+ ); +}; diff --git a/frontend/src/pages/SystemView.tsx b/frontend/src/pages/SystemView.tsx index 7b49d26..80ab66f 100644 --- a/frontend/src/pages/SystemView.tsx +++ b/frontend/src/pages/SystemView.tsx @@ -8,7 +8,7 @@ import { Header } from "@/components/Header"; import { parseSignature, parseScannedPercentage } from "@/utils/signatureParser"; import { getSystemId } from "@/utils/systemApi"; import pb from "@/lib/pocketbase"; -import { SigviewRecord as Signature, SignatureRecord } from "@/lib/pbtypes"; +import { SigviewRecord as Signature, SignatureRecord, SignatureNoteRulesResponse, Collections } from "@/lib/pbtypes"; export const SystemView = () => { const { system, region } = useParams(); @@ -163,12 +163,17 @@ export const SystemView = () => { try { const systemId = await getSystemId(system); + let rules: Array> = []; + try { + const list = await pb.collection(Collections.SignatureNoteRules).getFullList({ batch: 1000 }); + rules = list.filter(r => r.enabled).map(r => ({ regex: r.regex, note: r.note, enabled: r.enabled })); + } catch { } const lines = pastedText.trim().split('\n').filter(line => line.trim()); const parsedSignatures: Omit[] = []; // Parse all signatures for (const line of lines) { - const parsed = parseSignature(line); + const parsed = parseSignature(line, rules); if (parsed) { parsedSignatures.push({ ...parsed, diff --git a/frontend/src/utils/signatureParser.ts b/frontend/src/utils/signatureParser.ts index b4e72d3..02a18ac 100644 --- a/frontend/src/utils/signatureParser.ts +++ b/frontend/src/utils/signatureParser.ts @@ -1,52 +1,7 @@ -import { SigviewRecord as Signature } from "@/lib/pbtypes"; +import { SigviewRecord as Signature, SignatureNoteRulesResponse } from "@/lib/pbtypes"; -const oneOutOfTen = [ - "Minmatar Contracted Bio-Farm", - "Old Meanie - Cultivation Center", - "Pith Robux Asteroid Mining & Co.", - "Sansha Military Outpost", - "Serpentis Drug Outlet", -]; -const twoOutOfTen = [ - "Angel Creo-Corp Mining", - "Blood Raider Human Farm", - "Pith Merchant Depot", - "Sansha Acclimatization Facility", - "Serpentis Live Cargo Distribution Facilities", - "Rogue Drone Infestation Sprout", -]; -const threeOutOfTen = [ - "Angel Repurposed Outpost", - "Blood Raider Intelligence Collection Point", - "Guristas Guerilla Grounds", - "Sansha's Command Relay Outpost", - "Serpentis Narcotic Warehouses", - "Rogue Drone Asteroid Infestation", -]; -const fourOutOfTen = [ - "Angel Cartel Occupied Mining Colony", - "Mul-Zatah Monastery", - "Guristas Scout Outpost", - "Sansha's Nation Occupied Mining Colony", - "Serpentis Phi-Outpost", - "Drone Infested Mine", -]; -const fiveOutOfTen = [ - "Angel's Red Light District", - "Blood Raider Psychotropics Depot", - "Guristas Hallucinogen Supply Waypoint", - "Sansha's Nation Neural Paralytic Facility", - "Serpentis Corporation Hydroponics Site", - "Outgrowth Rogue Drone Hive", -]; -function isFourOutOfTen(signature: string): boolean { - return fourOutOfTen.some((s) => signature.includes(s)); -} -function isFiveOutOfTen(signature: string): boolean { - return fiveOutOfTen.some((s) => signature.includes(s)); -} -export const parseSignature = (text: string): Omit | null => { +export const parseSignature = (text: string, rules?: Array>): Omit | null => { const parts = text.split('\t'); if (parts.length < 4) return null; @@ -56,16 +11,26 @@ export const parseSignature = (text: string): Omit 0) { + for (const rule of rules) { + if (rule && rule.enabled) { + try { + const re = new RegExp(rule.regex, 'i'); + if (re.test(parts[3])) { + appliedNotes.push(rule.note); + } + } catch { + // invalid regex - ignore + } + } + } } + const dedupedNotes = Array.from(new Set(appliedNotes)).filter(Boolean); + const note = dedupedNotes.join(';'); + return { identifier: parts[0], type: parts[2],