348 lines
18 KiB
JavaScript
348 lines
18 KiB
JavaScript
/**
|
||
* @file SmsCityProfilesPanel.jsx
|
||
* @description Профили SMS по городам.
|
||
*/
|
||
import React, { useState, useEffect, useCallback } from "react";
|
||
import { Panel } from "../UI/Panel";
|
||
import { supabase } from "../../supabaseClient";
|
||
import { CRIMEAN_CITIES } from "../../constants/cities.js";
|
||
|
||
const THEME_STYLES = `
|
||
.sms-card {
|
||
background: #e8f0ec;
|
||
border: 1px solid #c8d8d2;
|
||
border-radius: 16px;
|
||
padding: 16px;
|
||
}
|
||
[data-theme="dark"] .sms-card {
|
||
background: #0d1a16;
|
||
border: 1px solid rgba(236,245,242,0.12);
|
||
}
|
||
.sms-inner {
|
||
background: #ffffff;
|
||
border: 1px solid #d0dad6;
|
||
border-radius: 10px;
|
||
padding: 12px;
|
||
}
|
||
[data-theme="dark"] .sms-inner {
|
||
background: #142822;
|
||
border: 1px solid rgba(236,245,242,0.08);
|
||
}
|
||
.sms-input {
|
||
background: #f4f7f5;
|
||
border: 1px solid #c8d8d2;
|
||
border-radius: 10px;
|
||
padding: 8px 12px;
|
||
color: #10211b;
|
||
width: 100%;
|
||
font-size: 14px;
|
||
font-family: inherit;
|
||
}
|
||
[data-theme="dark"] .sms-input {
|
||
background: #09110f;
|
||
border: 1px solid rgba(236,245,242,0.12);
|
||
color: #ecf5f2;
|
||
}
|
||
.sms-input::placeholder { color: #5d6d66; }
|
||
[data-theme="dark"] .sms-input::placeholder { color: #9eb0aa; }
|
||
.sms-label { font-size: 12px; font-weight: 500; color: #5d6d66; }
|
||
[data-theme="dark"] .sms-label { color: #9eb0aa; }
|
||
.sms-city-name { font-size: 16px; font-weight: 700; color: #10211b; }
|
||
[data-theme="dark"] .sms-city-name { color: #ecf5f2; }
|
||
.sms-section-title { font-size: 14px; font-weight: 600; color: #10211b; }
|
||
[data-theme="dark"] .sms-section-title { color: #ecf5f2; }
|
||
.sms-muted { font-size: 12px; color: #5d6d66; }
|
||
[data-theme="dark"] .sms-muted { color: #9eb0aa; }
|
||
.sms-text { font-size: 13px; color: #10211b; }
|
||
[data-theme="dark"] .sms-text { color: #ecf5f2; }
|
||
.sms-italic { font-size: 12px; color: #5d6d66; font-style: italic; line-height: 1.4; margin-top: 6px; }
|
||
[data-theme="dark"] .sms-italic { color: #9eb0aa; }
|
||
.sms-toggle-text { font-size: 13px; color: #10211b; }
|
||
[data-theme="dark"] .sms-toggle-text { color: #ecf5f2; }
|
||
.sms-btn {
|
||
border-radius: 12px; padding: 8px 16px; font-size: 12px; font-weight: 600;
|
||
color: #fff; border: none; cursor: pointer; background: #12805c;
|
||
}
|
||
[data-theme="dark"] .sms-btn { background: #57d8a9; color: #06120d; }
|
||
.sms-btn-sm {
|
||
border-radius: 8px; border: 1px solid #c8d8d2; padding: 4px 10px;
|
||
font-size: 12px; font-weight: 500; color: #10211b; cursor: pointer; background: transparent;
|
||
}
|
||
[data-theme="dark"] .sms-btn-sm { border: 1px solid rgba(236,245,242,0.12); color: #ecf5f2; }
|
||
.sms-btn-del {
|
||
border-radius: 8px; padding: 4px 8px; font-size: 14px;
|
||
color: #5d6d66; cursor: pointer; background: transparent; border: none;
|
||
}
|
||
[data-theme="dark"] .sms-btn-del { color: #9eb0aa; }
|
||
.sms-mode-btn-active-g {
|
||
flex: 1; border-radius: 10px; border: 1px solid #22c55e;
|
||
background: rgba(34,197,94,0.1); color: #22c55e;
|
||
padding: 8px 12px; font-size: 14px; font-weight: 500; cursor: pointer;
|
||
}
|
||
.sms-mode-btn-inactive {
|
||
flex: 1; border-radius: 10px; border: 1px solid #c8d8d2;
|
||
background: transparent; color: #5d6d66;
|
||
padding: 8px 12px; font-size: 14px; font-weight: 500; cursor: pointer;
|
||
}
|
||
[data-theme="dark"] .sms-mode-btn-inactive {
|
||
border: 1px solid rgba(236,245,242,0.12); color: #9eb0aa;
|
||
}
|
||
.sms-mode-btn-active-b {
|
||
flex: 1; border-radius: 10px; border: 1px solid #3b82f6;
|
||
background: rgba(59,130,246,0.1); color: #3b82f6;
|
||
padding: 8px 12px; font-size: 14px; font-weight: 500; cursor: pointer;
|
||
}
|
||
`;
|
||
|
||
export const SmsCityProfilesPanel = () => {
|
||
const [profiles, setProfiles] = useState([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const [showForm, setShowForm] = useState(false);
|
||
const [saving, setSaving] = useState(false);
|
||
const [message, setMessage] = useState("");
|
||
|
||
const [formCity, setFormCity] = useState("Ялта");
|
||
const [formDeliveryMode, setFormDeliveryMode] = useState("notification");
|
||
const [formDeliveryText, setFormDeliveryText] = useState("");
|
||
const [formPickupMode, setFormPickupMode] = useState("notification");
|
||
const [formPickupText, setFormPickupText] = useState("");
|
||
const [formSecondSms, setFormSecondSms] = useState(true);
|
||
const [formPaidStorage, setFormPaidStorage] = useState(true);
|
||
const [formSmsEnabled, setFormSmsEnabled] = useState(true);
|
||
const [formNote, setFormNote] = useState("");
|
||
const [editingId, setEditingId] = useState(null);
|
||
|
||
const fetchProfiles = useCallback(async () => {
|
||
setLoading(true);
|
||
try {
|
||
const { data, error } = await supabase
|
||
.from("sms_city_profiles")
|
||
.select("*")
|
||
.order("city", { ascending: true });
|
||
if (error) throw error;
|
||
setProfiles(data || []);
|
||
} catch (e) {
|
||
console.error("fetch sms_city_profiles error", e);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, []);
|
||
|
||
useEffect(() => { fetchProfiles(); }, [fetchProfiles]);
|
||
|
||
const resetForm = () => {
|
||
setFormCity("Ялта"); setFormDeliveryMode("notification"); setFormDeliveryText("");
|
||
setFormPickupMode("notification"); setFormPickupText("");
|
||
setFormSecondSms(true); setFormPaidStorage(true); setFormSmsEnabled(true);
|
||
setFormNote(""); setEditingId(null);
|
||
};
|
||
|
||
const handleSave = async () => {
|
||
setSaving(true);
|
||
try {
|
||
const payload = {
|
||
city: formCity, delivery_mode: formDeliveryMode, delivery_text: formDeliveryText || null,
|
||
pickup_mode: formPickupMode, pickup_text: formPickupText || null,
|
||
second_sms_enabled: formSecondSms, paid_storage_enabled: formPaidStorage,
|
||
sms_enabled: formSmsEnabled, note: formNote || "",
|
||
};
|
||
if (editingId) {
|
||
const { error } = await supabase.from("sms_city_profiles").update(payload).eq("id", editingId);
|
||
if (error) throw error;
|
||
setMessage("✅ Профиль обновлён");
|
||
} else {
|
||
const { error } = await supabase.from("sms_city_profiles").insert(payload);
|
||
if (error) throw error;
|
||
setMessage("✅ Профиль добавлен");
|
||
}
|
||
setShowForm(false); resetForm(); fetchProfiles();
|
||
setTimeout(() => setMessage(""), 3000);
|
||
} catch (e) {
|
||
setMessage("❌ Ошибка: " + e.message);
|
||
} finally { setSaving(false); }
|
||
};
|
||
|
||
const handleEdit = (p) => {
|
||
setEditingId(p.id); setFormCity(p.city);
|
||
setFormDeliveryMode(p.delivery_mode || "link"); setFormDeliveryText(p.delivery_text || "");
|
||
setFormPickupMode(p.pickup_mode || "link"); setFormPickupText(p.pickup_text || "");
|
||
setFormSecondSms(p.second_sms_enabled ?? true); setFormPaidStorage(p.paid_storage_enabled ?? true);
|
||
setFormSmsEnabled(p.sms_enabled ?? true); setFormNote(p.note || "");
|
||
setShowForm(true);
|
||
};
|
||
|
||
const handleDelete = async (id) => {
|
||
try { const { error } = await supabase.from("sms_city_profiles").delete().eq("id", id); if (error) throw error; fetchProfiles(); }
|
||
catch (e) { console.error("delete error", e); }
|
||
};
|
||
|
||
const ModeBadge = ({ mode }) => mode === "notification"
|
||
? <span style={{ backgroundColor: "rgba(59,130,246,0.15)", color: "#3b82f6", borderRadius: "6px", padding: "2px 8px", fontSize: "10px", fontWeight: 600 }}>Уведомление</span>
|
||
: <span style={{ backgroundColor: "rgba(34,197,94,0.15)", color: "#22c55e", borderRadius: "6px", padding: "2px 8px", fontSize: "10px", fontWeight: 600 }}>Ссылка</span>;
|
||
|
||
const Toggle = ({ checked, onChange, label }) => (
|
||
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||
<button type="button" onClick={onChange} style={{
|
||
position: "relative", height: "20px", width: "40px", borderRadius: "9999px",
|
||
transition: "all 0.2s", flexShrink: 0, border: "none", cursor: "pointer",
|
||
backgroundColor: checked ? "#22c55e" : "#c8d8d2",
|
||
}}>
|
||
<span style={{ position: "absolute", top: "2px", height: "16px", width: "16px", borderRadius: "9999px", backgroundColor: "#fff", transition: "all 0.2s", left: checked ? "20px" : "2px" }} />
|
||
</button>
|
||
{label && <span className="sms-toggle-text">{label}</span>}
|
||
</div>
|
||
);
|
||
|
||
return (
|
||
<div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
|
||
<style>{THEME_STYLES}</style>
|
||
|
||
{/* Header */}
|
||
<Panel className="p-5">
|
||
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
|
||
<span style={{ fontSize: "20px" }}>🏙️</span>
|
||
<div>
|
||
<h2 className="sms-section-title" style={{ textTransform: "uppercase", letterSpacing: "0.16em" }}>
|
||
Профили SMS по городам
|
||
</h2>
|
||
<p className="sms-muted" style={{ marginTop: "4px" }}>
|
||
Настройка поведения SMS для каждого города.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<button type="button" className="sms-btn" onClick={() => { setShowForm(!showForm); if (!showForm) resetForm(); }}>
|
||
{showForm ? "Отмена" : "+ Профиль"}
|
||
</button>
|
||
</div>
|
||
{message && <span className="sms-text" style={{ display: "block", marginTop: "8px" }}>{message}</span>}
|
||
</Panel>
|
||
|
||
{/* Form */}
|
||
{showForm && (
|
||
<Panel className="p-5" style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
|
||
<h3 className="sms-section-title">{editingId ? "Редактировать профиль" : "Новый профиль города"}</h3>
|
||
|
||
<div style={{ display: "grid", gap: "16px", gridTemplateColumns: "1fr 1fr" }}>
|
||
<div style={{ display: "flex", flexDirection: "column", gap: "4px" }}>
|
||
<label className="sms-label">Город</label>
|
||
<select value={formCity} onChange={(e) => setFormCity(e.target.value)} disabled={!!editingId}
|
||
className="sms-input" style={{ opacity: editingId ? 0.5 : 1 }}>
|
||
{CRIMEAN_CITIES.map((c) => <option key={c} value={c}>{c}</option>)}
|
||
</select>
|
||
</div>
|
||
<div className="sms-inner">
|
||
<Toggle checked={formSmsEnabled} onChange={() => setFormSmsEnabled(!formSmsEnabled)} label="SMS включены для города" />
|
||
{!formSmsEnabled && <p style={{ fontSize: "12px", color: "#c93d3d", marginTop: "8px" }}>⚠ Все SMS в этот город отключены</p>}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Delivery */}
|
||
<div className="sms-inner">
|
||
<div className="sms-muted" style={{ fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: "12px" }}>📦 Доставка</div>
|
||
<div style={{ display: "flex", gap: "8px" }}>
|
||
<button type="button" className={formDeliveryMode === "link" ? "sms-mode-btn-active-g" : "sms-mode-btn-inactive"} onClick={() => setFormDeliveryMode("link")}>🔗 Ссылка</button>
|
||
<button type="button" className={formDeliveryMode === "notification" ? "sms-mode-btn-active-b" : "sms-mode-btn-inactive"} onClick={() => setFormDeliveryMode("notification")}>📢 Уведомление</button>
|
||
</div>
|
||
{formDeliveryMode === "notification" && (
|
||
<textarea value={formDeliveryText} onChange={(e) => setFormDeliveryText(e.target.value)} placeholder="Ваш заказ готов. Доставим по графику." rows={2} className="sms-input" style={{ resize: "none", marginTop: "8px" }} />
|
||
)}
|
||
<p className="sms-muted" style={{ marginTop: "8px" }}>
|
||
{formDeliveryMode === "link" ? "Клиент получит ссылку для выбора даты" : "Уведомление без ссылки, без 2-й SMS"}
|
||
</p>
|
||
</div>
|
||
|
||
{/* Pickup */}
|
||
<div className="sms-inner">
|
||
<div className="sms-muted" style={{ fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: "12px" }}>🏪 Самовывоз</div>
|
||
<div style={{ display: "flex", gap: "8px" }}>
|
||
<button type="button" className={formPickupMode === "link" ? "sms-mode-btn-active-g" : "sms-mode-btn-inactive"} onClick={() => setFormPickupMode("link")}>🔗 Ссылка</button>
|
||
<button type="button" className={formPickupMode === "notification" ? "sms-mode-btn-active-b" : "sms-mode-btn-inactive"} onClick={() => setFormPickupMode("notification")}>📢 Уведомление</button>
|
||
</div>
|
||
{formPickupMode === "notification" && (
|
||
<textarea value={formPickupText} onChange={(e) => setFormPickupText(e.target.value)} placeholder="Ваш заказ готов. Заберите со склада." rows={2} className="sms-input" style={{ resize: "none", marginTop: "8px" }} />
|
||
)}
|
||
</div>
|
||
|
||
<div style={{ display: "grid", gap: "12px", gridTemplateColumns: "1fr 1fr" }}>
|
||
<div className="sms-inner"><Toggle checked={formSecondSms} onChange={() => setFormSecondSms(!formSecondSms)} label="2-я SMS (повтор)" /></div>
|
||
<div className="sms-inner"><Toggle checked={formPaidStorage} onChange={() => setFormPaidStorage(!formPaidStorage)} label="SMS: платное хранение" /></div>
|
||
</div>
|
||
|
||
<div style={{ display: "flex", flexDirection: "column", gap: "4px" }}>
|
||
<label className="sms-label">Комментарий</label>
|
||
<input type="text" value={formNote} onChange={(e) => setFormNote(e.target.value)} placeholder="Например: Ялта — доставка без согласования" className="sms-input" />
|
||
</div>
|
||
|
||
<button type="button" disabled={saving} onClick={handleSave} className="sms-btn" style={{ padding: "10px 20px", fontSize: "14px", opacity: saving ? 0.4 : 1 }}>
|
||
{saving ? "Сохранение..." : editingId ? "Обновить профиль" : "Сохранить профиль"}
|
||
</button>
|
||
</Panel>
|
||
)}
|
||
|
||
{/* Profiles list */}
|
||
<Panel className="p-5" style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
|
||
<h2 className="sms-section-title" style={{ textTransform: "uppercase", letterSpacing: "0.16em" }}>
|
||
Профили ({profiles.length})
|
||
</h2>
|
||
{loading ? (
|
||
<p className="sms-muted">Загрузка...</p>
|
||
) : profiles.length === 0 ? (
|
||
<div style={{ border: "1px dashed #c8d8d2", borderRadius: "12px", padding: "24px", textAlign: "center" }}>
|
||
<p className="sms-muted">Профилей нет. Города без профиля используют стандартный флоу (ссылка на согласование).</p>
|
||
</div>
|
||
) : (
|
||
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
|
||
{profiles.map((p) => (
|
||
<div key={p.id} className="sms-card">
|
||
{/* Header */}
|
||
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "12px" }}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
|
||
<span className="sms-city-name">{p.city}</span>
|
||
{!p.sms_enabled
|
||
? <span style={{ backgroundColor: "rgba(239,68,68,0.15)", color: "#ef4444", borderRadius: "6px", padding: "2px 8px", fontSize: "10px", fontWeight: 600 }}>SMS OFF</span>
|
||
: <span style={{ backgroundColor: "rgba(34,197,94,0.15)", color: "#22c55e", borderRadius: "6px", padding: "2px 8px", fontSize: "10px", fontWeight: 600 }}>SMS ON</span>}
|
||
</div>
|
||
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||
<button type="button" className="sms-btn-sm" onClick={() => handleEdit(p)}>✏️ Изменить</button>
|
||
<button type="button" className="sms-btn-del" onClick={() => handleDelete(p.id)} title="Удалить">✕</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Config */}
|
||
<div style={{ display: "grid", gap: "8px", gridTemplateColumns: "1fr 1fr", marginBottom: "12px" }}>
|
||
<div className="sms-inner">
|
||
<div style={{ display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap" }}>
|
||
<span className="sms-text" style={{ fontSize: "12px", fontWeight: 500 }}>📦 Доставка</span>
|
||
<ModeBadge mode={p.delivery_mode} />
|
||
</div>
|
||
{p.delivery_text && <p className="sms-italic">"{p.delivery_text}"</p>}
|
||
</div>
|
||
<div className="sms-inner">
|
||
<div style={{ display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap" }}>
|
||
<span className="sms-text" style={{ fontSize: "12px", fontWeight: 500 }}>🏪 Самовывоз</span>
|
||
<ModeBadge mode={p.pickup_mode} />
|
||
</div>
|
||
{p.pickup_text && <p className="sms-italic">"{p.pickup_text}"</p>}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Toggles */}
|
||
<div style={{ display: "flex", flexWrap: "wrap", alignItems: "center", gap: "16px", paddingTop: "8px", borderTop: "1px solid #c8d8d2" }}>
|
||
<span className="sms-text" style={{ fontSize: "12px", fontWeight: 500, opacity: p.second_sms_enabled ? 1 : 0.5 }}>
|
||
2-я SMS: {p.second_sms_enabled ? "✅" : "❌"}
|
||
</span>
|
||
<span className="sms-text" style={{ fontSize: "12px", fontWeight: 500, opacity: p.paid_storage_enabled ? 1 : 0.5 }}>
|
||
Платное хранение: {p.paid_storage_enabled ? "✅" : "❌"}
|
||
</span>
|
||
</div>
|
||
|
||
{p.note && <p className="sms-muted" style={{ paddingTop: "8px" }}>💬 {p.note}</p>}
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</Panel>
|
||
</div>
|
||
);
|
||
}; |