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
|
(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
|
WHERE sps.store_product_id IN (SELECT id FROM store_products WHERE dispensary_id = d.id)) as snapshot_count
|
||||||
FROM dispensaries d
|
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
|
LEFT JOIN chains c ON d.chain_id = c.id
|
||||||
WHERE d.state IS NOT NULL AND d.crawl_enabled = true
|
WHERE d.state IS NOT NULL AND d.crawl_enabled = true
|
||||||
${stateFilter}
|
${stateFilter}
|
||||||
GROUP BY d.id, d.name, d.dba_name, d.city, d.state, d.menu_type, d.crawl_enabled, c.name
|
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
|
ORDER BY sku_count DESC
|
||||||
LIMIT $1
|
LIMIT $1
|
||||||
`, params);
|
`, params);
|
||||||
|
|||||||
@@ -82,10 +82,16 @@ export function IntelligenceStores() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getCrawlFrequencyBadge = (hours: number | null) => {
|
const getCrawlFrequencyBadge = (hours: number | null) => {
|
||||||
if (hours === null) return <span className="badge badge-sm badge-ghost">Unknown</span>;
|
if (hours === null) {
|
||||||
if (hours <= 4) return <span className="badge badge-sm badge-success">High ({hours}h)</span>;
|
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 <= 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 <= 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) {
|
if (loading) {
|
||||||
@@ -263,7 +269,7 @@ export function IntelligenceStores() {
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{store.chainName ? (
|
{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>
|
<span className="text-gray-400">-</span>
|
||||||
)}
|
)}
|
||||||
@@ -275,7 +281,7 @@ export function IntelligenceStores() {
|
|||||||
<span className="font-mono">{(store.snapshotCount || 0).toLocaleString()}</span>
|
<span className="font-mono">{(store.snapshotCount || 0).toLocaleString()}</span>
|
||||||
</td>
|
</td>
|
||||||
<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)}
|
{formatTimeAgo(store.lastCrawl)}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
Reference in New Issue
Block a user