Add per-product pause/resume checking feature

Users can now pause and resume price checking for individual products
or in bulk via the Actions menu.

Backend:
- Added checking_paused column to products table
- Scheduler skips products with checking_paused=true
- Added POST /products/bulk/pause endpoint for bulk pause/resume

Frontend:
- Added Pause Checking and Resume Checking to bulk Actions menu
- Added filter dropdown (All/Active/Paused) next to sort controls
- Paused products show greyed out with pause icon and "Paused" label
- Progress bar hidden when paused

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
clucraft 2026-01-25 21:04:02 -05:00
parent 1f668239bd
commit 26a802e3d0
6 changed files with 176 additions and 6 deletions

View file

@ -191,6 +191,10 @@ async function runMigrations() {
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'products' AND column_name = 'ai_extraction_disabled') THEN
ALTER TABLE products ADD COLUMN ai_extraction_disabled BOOLEAN DEFAULT false;
END IF;
-- Per-product checking pause flag
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'products' AND column_name = 'checking_paused') THEN
ALTER TABLE products ADD COLUMN checking_paused BOOLEAN DEFAULT false;
END IF;
END $$;
`);

View file

@ -344,6 +344,7 @@ export interface Product {
notify_back_in_stock: boolean;
ai_verification_disabled: boolean;
ai_extraction_disabled: boolean;
checking_paused: boolean;
created_at: Date;
}
@ -587,8 +588,8 @@ export const productQueries = {
findDueForRefresh: async (): Promise<Product[]> => {
const result = await pool.query(
`SELECT * FROM products
WHERE next_check_at IS NULL
OR next_check_at < CURRENT_TIMESTAMP`
WHERE (next_check_at IS NULL OR next_check_at < CURRENT_TIMESTAMP)
AND (checking_paused IS NULL OR checking_paused = false)`
);
return result.rows;
},
@ -638,6 +639,15 @@ export const productQueries = {
);
return result.rows[0]?.ai_extraction_disabled === true;
},
bulkSetCheckingPaused: async (ids: number[], userId: number, paused: boolean): Promise<number> => {
if (ids.length === 0) return 0;
const result = await pool.query(
`UPDATE products SET checking_paused = $1 WHERE id = ANY($2) AND user_id = $3`,
[paused, ids, userId]
);
return result.rowCount || 0;
},
};
// Price History types and queries

View file

@ -268,4 +268,31 @@ router.delete('/:id', async (req: AuthRequest, res: Response) => {
}
});
// Bulk pause/resume checking
router.post('/bulk/pause', async (req: AuthRequest, res: Response) => {
try {
const userId = req.userId!;
const { ids, paused } = req.body;
if (!Array.isArray(ids) || ids.length === 0) {
res.status(400).json({ error: 'Product IDs array is required' });
return;
}
if (typeof paused !== 'boolean') {
res.status(400).json({ error: 'Paused status (boolean) is required' });
return;
}
const updated = await productQueries.bulkSetCheckingPaused(ids, userId, paused);
res.json({
message: `${updated} product(s) ${paused ? 'paused' : 'resumed'}`,
updated
});
} catch (error) {
console.error('Error bulk updating pause status:', error);
res.status(500).json({ error: 'Failed to update pause status' });
}
});
export default router;