← back to Omega Watches 2

src/app/(dashboard)/alerts/page.tsx

279 lines

"use client";

import { useEffect, useState } from "react";
import { formatDateTime, formatCurrency } from "@/lib/utils";

interface AlertRule {
  id: string;
  reference_number: string;
  collection: string;
  model_name: string;
  alert_type: "price_below" | "price_above";
  threshold: string;
  current_avg_price: string | null;
  is_triggered: boolean;
  is_active: boolean;
  trigger_count: number;
  created_by: string;
  notes: string | null;
  created_at: string;
  last_triggered_at: string | null;
}

export default function AlertsPage() {
  const [alerts, setAlerts] = useState<AlertRule[]>([]);
  const [summary, setSummary] = useState({ total: 0, active: 0, triggered: 0 });
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");
  const [creating, setCreating] = useState(false);

  const [referenceNumber, setReferenceNumber] = useState("");
  const [alertType, setAlertType] = useState<"price_below" | "price_above">("price_below");
  const [threshold, setThreshold] = useState("");
  const [notes, setNotes] = useState("");

  function getHeaders() {
    const token = typeof window !== "undefined" ? localStorage.getItem("omega_token") : null;
    const h: Record<string, string> = { "Content-Type": "application/json" };
    if (token) h["Authorization"] = `Bearer ${token}`;
    return h;
  }

  async function loadAlerts() {
    try {
      const res = await fetch("/api/alerts", { headers: getHeaders() });
      const json = await res.json();
      if (json.success) {
        setAlerts(json.data.rules);
        setSummary(json.data.summary);
      } else {
        setError(json.error?.message || "Failed to load alerts");
      }
    } catch (e: any) {
      setError(e.message);
    } finally {
      setLoading(false);
    }
  }

  useEffect(() => { loadAlerts(); }, []);

  async function toggleActive(id: string, isActive: boolean) {
    try {
      const res = await fetch("/api/alerts", {
        method: "PUT",
        headers: getHeaders(),
        body: JSON.stringify({ id, is_active: isActive }),
      });
      const json = await res.json();
      if (json.success) {
        loadAlerts();
      }
    } catch (e: any) {
      setError(e.message);
    }
  }

  async function createAlert() {
    if (!referenceNumber || !threshold) return;
    setCreating(true);
    try {
      const res = await fetch("/api/alerts", {
        method: "POST",
        headers: getHeaders(),
        body: JSON.stringify({
          reference_number: referenceNumber,
          alert_type: alertType,
          threshold: parseFloat(threshold),
          notes: notes || null,
        }),
      });
      const json = await res.json();
      if (json.success) {
        setReferenceNumber("");
        setThreshold("");
        setNotes("");
        loadAlerts();
      } else {
        setError(json.error?.message || "Failed to create alert");
      }
    } catch (e: any) {
      setError(e.message);
    } finally {
      setCreating(false);
    }
  }

  if (loading) {
    return <div className="flex h-full items-center justify-center"><div className="text-gray-400">Loading alerts...</div></div>;
  }

  return (
    <div className="space-y-6">
      <div className="flex items-center justify-between">
        <h1 className="text-xl font-bold text-gray-100">Price Alert Rules</h1>
        <button onClick={loadAlerts} className="btn-secondary text-xs">Refresh</button>
      </div>

      {error && (
        <div className="rounded bg-red-900/30 p-3 text-red-300">
          {error}
          <button onClick={() => setError("")} className="ml-2 text-xs underline">Dismiss</button>
        </div>
      )}

      {/* Summary */}
      <div className="grid gap-4 md:grid-cols-3">
        <div className="card border-navy-700 text-center">
          <div className="text-3xl font-bold text-gray-200">{summary.total}</div>
          <div className="text-xs text-gray-500">Total Rules</div>
        </div>
        <div className="card border-navy-700 text-center">
          <div className="text-3xl font-bold text-green-400">{summary.active}</div>
          <div className="text-xs text-gray-500">Active</div>
        </div>
        <div className="card border-navy-700 text-center">
          <div className={`text-3xl font-bold ${summary.triggered > 0 ? "text-orange-400" : "text-green-400"}`}>
            {summary.triggered}
          </div>
          <div className="text-xs text-gray-500">Triggered</div>
        </div>
      </div>

      {/* Create Form */}
      <div className="card border-navy-700">
        <h2 className="mb-4 text-sm font-semibold uppercase tracking-wider text-gray-400">Create New Alert</h2>
        <div className="grid gap-4 md:grid-cols-2">
          <div>
            <label className="mb-1 block text-xs text-gray-400">Reference Number</label>
            <input type="text" className="input" placeholder="e.g., 310.30.42.50.01.001"
              value={referenceNumber} onChange={(e) => setReferenceNumber(e.target.value)} />
          </div>
          <div>
            <label className="mb-1 block text-xs text-gray-400">Alert Type</label>
            <select className="input" value={alertType}
              onChange={(e) => setAlertType(e.target.value as "price_below" | "price_above")}>
              <option value="price_below">Price Falls Below</option>
              <option value="price_above">Price Rises Above</option>
            </select>
          </div>
          <div>
            <label className="mb-1 block text-xs text-gray-400">Threshold (USD)</label>
            <input type="number" className="input" placeholder="e.g., 5000"
              value={threshold} onChange={(e) => setThreshold(e.target.value)} />
          </div>
          <div>
            <label className="mb-1 block text-xs text-gray-400">Notes (Optional)</label>
            <input type="text" className="input" placeholder="e.g., Target buy price"
              value={notes} onChange={(e) => setNotes(e.target.value)} />
          </div>
        </div>
        <div className="mt-4 flex justify-end">
          <button onClick={createAlert} disabled={creating || !referenceNumber || !threshold}
            className="btn-primary disabled:opacity-50">
            {creating ? "Creating..." : "Create Alert"}
          </button>
        </div>
      </div>

      {/* Alert Rules */}
      <div className="card border-navy-700">
        <h2 className="mb-4 text-sm font-semibold uppercase tracking-wider text-gray-400">
          Alert Rules ({alerts.length})
        </h2>

        {alerts.length === 0 ? (
          <div className="p-8 text-center">
            <div className="text-4xl text-gray-600">&#128276;</div>
            <div className="mt-2 text-lg font-medium text-gray-400">No Alerts Yet</div>
            <div className="text-sm text-gray-500">Create your first price alert rule above.</div>
          </div>
        ) : (
          <div className="space-y-3">
            {alerts.map((alert) => {
              const triggered = alert.is_triggered;
              return (
                <div key={alert.id}
                  className={triggered
                    ? "rounded-lg border border-orange-700 bg-orange-900/20 p-4 shadow-lg shadow-orange-900/30"
                    : "rounded-lg border border-navy-700 bg-navy-900/50 p-4"
                  }>
                  <div className="flex items-start justify-between">
                    <div className="flex-1">
                      <div className="flex items-center gap-2">
                        <span className="font-mono text-sm font-medium text-omega-400">
                          {alert.reference_number}
                        </span>
                        <span className="text-xs text-gray-500">{alert.collection}</span>
                        {triggered && (
                          <span className="rounded bg-orange-900/50 px-2 py-0.5 text-xs font-bold text-orange-300 animate-pulse">
                            TRIGGERED
                          </span>
                        )}
                        <span className={`rounded px-2 py-0.5 text-xs font-bold ${
                          alert.is_active ? "bg-green-900/50 text-green-300" : "bg-gray-800 text-gray-500"
                        }`}>
                          {alert.is_active ? "Active" : "Inactive"}
                        </span>
                      </div>
                      {alert.model_name && (
                        <div className="mt-1 text-xs text-gray-400">{alert.model_name}</div>
                      )}
                      <div className="mt-2 flex flex-wrap items-center gap-4 text-xs">
                        <div>
                          <span className="text-gray-500">Type: </span>
                          <span className="text-gray-300">
                            {alert.alert_type === "price_below" ? "Price Falls Below" : "Price Rises Above"}
                          </span>
                        </div>
                        <div>
                          <span className="text-gray-500">Threshold: </span>
                          <span className="font-medium text-gray-200">
                            {formatCurrency(parseFloat(alert.threshold))}
                          </span>
                        </div>
                        {alert.current_avg_price && (
                          <div>
                            <span className="text-gray-500">Current Avg: </span>
                            <span className={`font-medium ${triggered ? "text-orange-400" : "text-gray-200"}`}>
                              {formatCurrency(parseFloat(alert.current_avg_price))}
                            </span>
                          </div>
                        )}
                        <div>
                          <span className="text-gray-500">Triggers: </span>
                          <span className="text-gray-300">{alert.trigger_count}</span>
                        </div>
                      </div>
                      {alert.notes && (
                        <div className="mt-2 text-xs text-gray-500">
                          <span className="font-medium">Note:</span> {alert.notes}
                        </div>
                      )}
                      <div className="mt-2 flex items-center gap-4 text-xs text-gray-600">
                        <span>By {alert.created_by}</span>
                        <span>{formatDateTime(alert.created_at)}</span>
                        {alert.last_triggered_at && (
                          <span className="text-orange-500">Last triggered {formatDateTime(alert.last_triggered_at)}</span>
                        )}
                      </div>
                    </div>
                    <button onClick={() => toggleActive(alert.id, !alert.is_active)}
                      className={`ml-4 rounded-md px-3 py-1 text-xs font-medium transition-colors ${
                        alert.is_active
                          ? "bg-red-900/50 text-red-300 hover:bg-red-900/70"
                          : "bg-green-900/50 text-green-300 hover:bg-green-900/70"
                      }`}>
                      {alert.is_active ? "Disable" : "Enable"}
                    </button>
                  </div>
                </div>
              );
            })}
          </div>
        )}
      </div>
    </div>
  );
}