fix: Intelligence stores endpoint and UI consistency
- Fix stores endpoint to only show stores with actual products (INNER JOIN + HAVING) - Update badge colors to match Workers/Tasks dashboard style - Use emerald/amber/red/gray color scheme consistently - Chain badge now uses purple (bg-purple-100) - Add migration 092 to fix Trulieve store URLs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
30
backend/migrations/092_fix_trulieve_urls.sql
Normal file
30
backend/migrations/092_fix_trulieve_urls.sql
Normal file
@@ -0,0 +1,30 @@
|
||||
-- Fix 3 Trulieve/Harvest stores with incorrect menu URLs
|
||||
-- These records have NULL or mismatched platform_dispensary_id so store_discovery
|
||||
-- ON CONFLICT can't update them automatically
|
||||
|
||||
UPDATE dispensaries
|
||||
SET
|
||||
menu_url = 'https://dutchie.com/dispensary/svaccha-llc-nirvana-center-apache-junction',
|
||||
updated_at = NOW()
|
||||
WHERE id = 224;
|
||||
|
||||
UPDATE dispensaries
|
||||
SET
|
||||
menu_url = 'https://dutchie.com/dispensary/trulieve-of-phoenix-tatum',
|
||||
updated_at = NOW()
|
||||
WHERE id = 76;
|
||||
|
||||
UPDATE dispensaries
|
||||
SET
|
||||
menu_url = 'https://dutchie.com/dispensary/harvest-of-havasu',
|
||||
updated_at = NOW()
|
||||
WHERE id = 403;
|
||||
|
||||
-- Queue entry_point_discovery tasks to resolve their platform_dispensary_id
|
||||
-- method='http' ensures only workers that passed http preflight can claim these
|
||||
INSERT INTO worker_tasks (role, dispensary_id, priority, scheduled_for, method)
|
||||
VALUES
|
||||
('entry_point_discovery', 224, 5, NOW(), 'http'),
|
||||
('entry_point_discovery', 76, 5, NOW(), 'http'),
|
||||
('entry_point_discovery', 403, 5, NOW(), 'http')
|
||||
ON CONFLICT DO NOTHING;
|
||||
@@ -325,11 +325,12 @@ router.get('/stores', async (req: Request, res: Response) => {
|
||||
(SELECT COUNT(*) FROM store_product_snapshots sps
|
||||
WHERE sps.store_product_id IN (SELECT id FROM store_products WHERE dispensary_id = d.id)) as snapshot_count
|
||||
FROM dispensaries d
|
||||
LEFT JOIN store_products sp ON sp.dispensary_id = d.id
|
||||
INNER JOIN store_products sp ON sp.dispensary_id = d.id
|
||||
LEFT JOIN chains c ON d.chain_id = c.id
|
||||
WHERE d.state IS NOT NULL AND d.crawl_enabled = true
|
||||
${stateFilter}
|
||||
GROUP BY d.id, d.name, d.dba_name, d.city, d.state, d.menu_type, d.crawl_enabled, c.name
|
||||
HAVING COUNT(sp.id) > 0
|
||||
ORDER BY sku_count DESC
|
||||
LIMIT $1
|
||||
`, params);
|
||||
|
||||
@@ -82,10 +82,16 @@ export function IntelligenceStores() {
|
||||
};
|
||||
|
||||
const getCrawlFrequencyBadge = (hours: number | null) => {
|
||||
if (hours === null) return <span className="badge badge-sm badge-ghost">Unknown</span>;
|
||||
if (hours <= 4) return <span className="badge badge-sm badge-success">High ({hours}h)</span>;
|
||||
if (hours <= 12) return <span className="badge badge-sm badge-warning">Medium ({hours}h)</span>;
|
||||
return <span className="badge badge-sm badge-error">Low ({hours}h)</span>;
|
||||
if (hours === null) {
|
||||
return <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-600">Unknown</span>;
|
||||
}
|
||||
if (hours <= 4) {
|
||||
return <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-emerald-100 text-emerald-700">High ({hours}h)</span>;
|
||||
}
|
||||
if (hours <= 12) {
|
||||
return <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-amber-100 text-amber-700">Medium ({hours}h)</span>;
|
||||
}
|
||||
return <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-red-100 text-red-700">Low ({hours}h)</span>;
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
@@ -263,7 +269,7 @@ export function IntelligenceStores() {
|
||||
</td>
|
||||
<td>
|
||||
{store.chainName ? (
|
||||
<span className="badge badge-sm badge-outline">{store.chainName}</span>
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-purple-100 text-purple-700">{store.chainName}</span>
|
||||
) : (
|
||||
<span className="text-gray-400">-</span>
|
||||
)}
|
||||
@@ -275,7 +281,7 @@ export function IntelligenceStores() {
|
||||
<span className="font-mono">{(store.snapshotCount || 0).toLocaleString()}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span className={store.lastCrawl ? 'text-green-600' : 'text-gray-400'}>
|
||||
<span className={store.lastCrawl ? 'text-emerald-600' : 'text-gray-400'}>
|
||||
{formatTimeAgo(store.lastCrawl)}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
Reference in New Issue
Block a user