Add target price alerts, historical low indicator, bulk actions, and dashboard summary

Features:
- Target price alerts: Set a specific price target and get notified when reached
- Historical low indicator: Badge showing when current price is at/near all-time low
- Bulk actions: Select multiple products to delete at once
- Dashboard summary: Shows total products, items at lowest price, at target, biggest drops

Backend changes:
- Add target_price column to products table
- Add target_price notification type with Telegram/Discord support
- Include min_price in product queries for historical low detection
- Update scheduler to check target price conditions

Frontend changes:
- Add target price input to ProductDetail notification settings
- Show target price badge on product cards
- Add "Lowest Price" and "Near Low" badges to product cards
- Add bulk selection mode with checkboxes
- Add dashboard summary cards at top of product list

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
clucraft 2026-01-21 13:40:39 -05:00
parent 2acc47c21c
commit a85e22d8bc
9 changed files with 454 additions and 5 deletions

View file

@ -24,6 +24,7 @@ export default function ProductDetail() {
const [error, setError] = useState('');
const [notificationSettings, setNotificationSettings] = useState<NotificationSettings | null>(null);
const [priceDropThreshold, setPriceDropThreshold] = useState<string>('');
const [targetPrice, setTargetPrice] = useState<string>('');
const [notifyBackInStock, setNotifyBackInStock] = useState(false);
const REFRESH_INTERVALS = [
@ -50,6 +51,9 @@ export default function ProductDetail() {
if (productRes.data.price_drop_threshold !== null && productRes.data.price_drop_threshold !== undefined) {
setPriceDropThreshold(productRes.data.price_drop_threshold.toString());
}
if (productRes.data.target_price !== null && productRes.data.target_price !== undefined) {
setTargetPrice(productRes.data.target_price.toString());
}
setNotifyBackInStock(productRes.data.notify_back_in_stock || false);
} catch {
setError('Failed to load product details');
@ -121,13 +125,16 @@ export default function ProductDetail() {
setIsSavingNotifications(true);
try {
const threshold = priceDropThreshold ? parseFloat(priceDropThreshold) : null;
const target = targetPrice ? parseFloat(targetPrice) : null;
await productsApi.update(productId, {
price_drop_threshold: threshold,
target_price: target,
notify_back_in_stock: notifyBackInStock,
});
setProduct({
...product,
price_drop_threshold: threshold,
target_price: target,
notify_back_in_stock: notifyBackInStock,
});
} catch {
@ -678,6 +685,23 @@ export default function ProductDetail() {
</span>
</div>
<div className="notification-form-group">
<label>Target Price</label>
<input
type="number"
min="0"
step="0.01"
value={targetPrice}
onChange={(e) => setTargetPrice(e.target.value)}
placeholder="Enter target price (e.g., 49.99)"
/>
<span className="hint">
Notify when price drops to or below this amount ({product.currency || 'USD'})
</span>
</div>
</div>
<div className="notification-form-row">
<div className="notification-form-group">
<label>Back in Stock Alert</label>
<label className="notification-checkbox-group">