feat: Add server-side brand search to Intelligence page

- Backend: Add 'search' param to /api/admin/intelligence/brands
- Frontend: Debounced search triggers server-side query
- Now searches ALL brands, not just top 500

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Kelly
2025-12-13 01:14:02 -07:00
parent 271faf0f00
commit 7067db68fc
4 changed files with 44 additions and 24 deletions

View File

@@ -21,18 +21,30 @@ router.use(authMiddleware);
*/
router.get('/brands', async (req: Request, res: Response) => {
try {
const { limit = '500', offset = '0', state } = req.query;
const { limit = '500', offset = '0', state, search } = req.query;
const limitNum = Math.min(parseInt(limit as string, 10), 1000);
const offsetNum = parseInt(offset as string, 10);
// Build WHERE clause based on state filter
let stateFilter = '';
// Build WHERE clause based on filters
const conditions: string[] = ["sp.brand_name_raw IS NOT NULL AND sp.brand_name_raw != ''"];
const params: any[] = [limitNum, offsetNum];
let paramIndex = 3;
if (state && state !== 'all') {
stateFilter = 'AND d.state = $3';
conditions.push(`d.state = $${paramIndex}`);
params.push(state);
paramIndex++;
}
// Server-side search - case-insensitive partial match
if (search && typeof search === 'string' && search.trim()) {
conditions.push(`sp.brand_name_raw ILIKE $${paramIndex}`);
params.push(`%${search.trim()}%`);
paramIndex++;
}
const whereClause = conditions.join(' AND ');
const { rows } = await pool.query(`
SELECT
sp.brand_name_raw as brand_name,
@@ -43,26 +55,22 @@ router.get('/brands', async (req: Request, res: Response) => {
ROUND(AVG(sp.price_med) FILTER (WHERE sp.price_med > 0)::numeric, 2) as avg_price_med
FROM store_products sp
JOIN dispensaries d ON sp.dispensary_id = d.id
WHERE sp.brand_name_raw IS NOT NULL AND sp.brand_name_raw != ''
${stateFilter}
WHERE ${whereClause}
GROUP BY sp.brand_name_raw
ORDER BY store_count DESC, sku_count DESC
LIMIT $1 OFFSET $2
`, params);
// Get total count with same state filter
const countParams: any[] = [];
let countStateFilter = '';
if (state && state !== 'all') {
countStateFilter = 'AND d.state = $1';
countParams.push(state);
}
// Get total count with same filters (excluding limit/offset)
const countParams = params.slice(2); // Remove limit and offset
const countConditions = conditions.map((c, i) =>
c.replace(/\$\d+/g, (match) => `$${parseInt(match.slice(1)) - 2}`)
);
const { rows: countRows } = await pool.query(`
SELECT COUNT(DISTINCT sp.brand_name_raw) as total
FROM store_products sp
JOIN dispensaries d ON sp.dispensary_id = d.id
WHERE sp.brand_name_raw IS NOT NULL AND sp.brand_name_raw != ''
${countStateFilter}
WHERE ${countConditions.join(' AND ')}
`, countParams);
res.json({