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 (
+
+ );
+};
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],