feat: add frontend dashboard for run results viewer
- Implemented main dashboard with run metrics and filtering options. - Created detailed view for individual runs with results and anomalies. - Added product listing page with filtering and pagination. - Introduced utility functions for formatting dates and numbers. - Styled components with CSS for a clean and responsive layout. - Set up HTML entry point and linked to the main JavaScript file. - Updated TypeScript configuration to include DOM types.
This commit is contained in:
123
src/database.ts
123
src/database.ts
@@ -19,6 +19,84 @@ export function closeDb(): void {
|
||||
}
|
||||
}
|
||||
|
||||
function createProductAnalysisResultsTable(database: Database): void {
|
||||
database.run(`
|
||||
CREATE TABLE IF NOT EXISTS product_analysis_results (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
asin TEXT NOT NULL,
|
||||
run_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
brand TEXT,
|
||||
category TEXT,
|
||||
unit_cost REAL,
|
||||
current_price REAL,
|
||||
avg_price_90d REAL,
|
||||
avg_price_90d_sheet REAL,
|
||||
selling_price_sheet REAL,
|
||||
sales_rank INTEGER,
|
||||
sales_rank_avg_90d INTEGER,
|
||||
seller_count INTEGER,
|
||||
monthly_sold INTEGER,
|
||||
rank_drops_30d INTEGER,
|
||||
rank_drops_90d INTEGER,
|
||||
fba_fee REAL,
|
||||
fbm_fee REAL,
|
||||
referral_percent REAL,
|
||||
can_sell TEXT,
|
||||
sellability_status TEXT,
|
||||
sellability_reason TEXT,
|
||||
verdict TEXT NOT NULL,
|
||||
confidence REAL NOT NULL,
|
||||
reasoning TEXT,
|
||||
fetched_at TEXT NOT NULL,
|
||||
UNIQUE(run_id, asin),
|
||||
FOREIGN KEY (run_id) REFERENCES category_analysis_runs(id)
|
||||
);
|
||||
`);
|
||||
}
|
||||
|
||||
function ensureProductAnalysisResultsTable(database: Database): void {
|
||||
const tableInfo = database
|
||||
.query("PRAGMA table_info(product_analysis_results)")
|
||||
.all() as Array<{ name: string; pk: number }>;
|
||||
|
||||
if (tableInfo.length === 0) {
|
||||
createProductAnalysisResultsTable(database);
|
||||
return;
|
||||
}
|
||||
|
||||
const hasIdColumn = tableInfo.some((col) => col.name === "id");
|
||||
const hasAsinPrimaryKey = tableInfo.some(
|
||||
(col) => col.name === "asin" && col.pk === 1,
|
||||
);
|
||||
|
||||
if (!hasIdColumn || hasAsinPrimaryKey) {
|
||||
database.run("ALTER TABLE product_analysis_results RENAME TO product_analysis_results_legacy");
|
||||
createProductAnalysisResultsTable(database);
|
||||
database.run(`
|
||||
INSERT INTO product_analysis_results (
|
||||
asin, run_id, name, brand, category, unit_cost,
|
||||
current_price, avg_price_90d, avg_price_90d_sheet,
|
||||
selling_price_sheet, sales_rank, sales_rank_avg_90d,
|
||||
seller_count, monthly_sold, rank_drops_30d, rank_drops_90d,
|
||||
fba_fee, fbm_fee, referral_percent, can_sell,
|
||||
sellability_status, sellability_reason,
|
||||
verdict, confidence, reasoning, fetched_at
|
||||
)
|
||||
SELECT
|
||||
asin, run_id, name, brand, category, unit_cost,
|
||||
current_price, avg_price_90d, avg_price_90d_sheet,
|
||||
selling_price_sheet, sales_rank, sales_rank_avg_90d,
|
||||
seller_count, monthly_sold, rank_drops_30d, rank_drops_90d,
|
||||
fba_fee, fbm_fee, referral_percent, can_sell,
|
||||
sellability_status, sellability_reason,
|
||||
verdict, confidence, reasoning, fetched_at
|
||||
FROM product_analysis_results_legacy
|
||||
`);
|
||||
database.run("DROP TABLE product_analysis_results_legacy");
|
||||
}
|
||||
}
|
||||
|
||||
export function initDb(dbPath: string): void {
|
||||
const database = getDb(dbPath);
|
||||
database.run(`
|
||||
@@ -80,35 +158,18 @@ export function initDb(dbPath: string): void {
|
||||
error_message TEXT
|
||||
);
|
||||
`);
|
||||
database.run(`
|
||||
CREATE TABLE IF NOT EXISTS product_analysis_results (
|
||||
asin TEXT PRIMARY KEY,
|
||||
run_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
brand TEXT,
|
||||
category TEXT,
|
||||
unit_cost REAL,
|
||||
current_price REAL,
|
||||
avg_price_90d REAL,
|
||||
avg_price_90d_sheet REAL,
|
||||
selling_price_sheet REAL,
|
||||
sales_rank INTEGER,
|
||||
sales_rank_avg_90d INTEGER,
|
||||
seller_count INTEGER,
|
||||
monthly_sold INTEGER,
|
||||
rank_drops_30d INTEGER,
|
||||
rank_drops_90d INTEGER,
|
||||
fba_fee REAL,
|
||||
fbm_fee REAL,
|
||||
referral_percent REAL,
|
||||
can_sell TEXT,
|
||||
sellability_status TEXT,
|
||||
sellability_reason TEXT,
|
||||
verdict TEXT NOT NULL,
|
||||
confidence REAL NOT NULL,
|
||||
reasoning TEXT,
|
||||
fetched_at TEXT NOT NULL,
|
||||
FOREIGN KEY (run_id) REFERENCES category_analysis_runs(id)
|
||||
);
|
||||
`);
|
||||
ensureProductAnalysisResultsTable(database);
|
||||
|
||||
database.run(`CREATE INDEX IF NOT EXISTS idx_runs_timestamp ON runs(timestamp DESC);`);
|
||||
database.run(`CREATE INDEX IF NOT EXISTS idx_results_run_id ON results(run_id);`);
|
||||
database.run(`CREATE INDEX IF NOT EXISTS idx_results_verdict ON results(verdict);`);
|
||||
database.run(`CREATE INDEX IF NOT EXISTS idx_results_sellability_status ON results(sellability_status);`);
|
||||
database.run(`CREATE INDEX IF NOT EXISTS idx_results_fetched_at ON results(fetched_at DESC);`);
|
||||
database.run(`CREATE INDEX IF NOT EXISTS idx_results_asin ON results(asin);`);
|
||||
database.run(`CREATE INDEX IF NOT EXISTS idx_category_runs_timestamp ON category_analysis_runs(run_timestamp DESC);`);
|
||||
database.run(`CREATE INDEX IF NOT EXISTS idx_category_runs_status ON category_analysis_runs(status);`);
|
||||
database.run(`CREATE INDEX IF NOT EXISTS idx_product_results_run_id ON product_analysis_results(run_id);`);
|
||||
database.run(`CREATE INDEX IF NOT EXISTS idx_product_results_verdict ON product_analysis_results(verdict);`);
|
||||
database.run(`CREATE INDEX IF NOT EXISTS idx_product_results_sellability_status ON product_analysis_results(sellability_status);`);
|
||||
database.run(`CREATE INDEX IF NOT EXISTS idx_product_results_fetched_at ON product_analysis_results(fetched_at DESC);`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user