fix: SMS city profiles — inline styles для надёжного контраста
- Карточки профиля: accent-soft (зелёный оттенок) — виден на Panel - Внутренние блоки: surface-strong (непрозрачный) — контраст с карточкой - Инпуты: color-base — отличается от карточек - Все цвета через CSS variables — работают в обеих темах - Никаких Tailwind классов с var() которые могут не разрешиться
This commit is contained in:
parent
69c261cc8f
commit
2103fe2afe
|
|
@ -1,12 +1,6 @@
|
||||||
/**
|
/**
|
||||||
* @file SmsCityProfilesPanel.jsx
|
* @file SmsCityProfilesPanel.jsx
|
||||||
* @description Профили SMS по городам.
|
* @description Профили SMS по городам.
|
||||||
* Для каждого города настраивается:
|
|
||||||
* - Доставка: ссылка на согласование | уведомление без ссылки + кастомный текст
|
|
||||||
* - Самовывоз: ссылка на согласование | уведомление без ссылки + кастомный текст
|
|
||||||
* - 2-я SMS: вкл/выкл
|
|
||||||
* - Платное хранение: вкл/выкл
|
|
||||||
* - SMS вообще: вкл/выкл (мастер-выключатель)
|
|
||||||
*/
|
*/
|
||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
import { Panel } from "../UI/Panel";
|
import { Panel } from "../UI/Panel";
|
||||||
|
|
@ -20,7 +14,6 @@ export const SmsCityProfilesPanel = () => {
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [message, setMessage] = useState("");
|
const [message, setMessage] = useState("");
|
||||||
|
|
||||||
// Form state
|
|
||||||
const [formCity, setFormCity] = useState("Ялта");
|
const [formCity, setFormCity] = useState("Ялта");
|
||||||
const [formDeliveryMode, setFormDeliveryMode] = useState("notification");
|
const [formDeliveryMode, setFormDeliveryMode] = useState("notification");
|
||||||
const [formDeliveryText, setFormDeliveryText] = useState("");
|
const [formDeliveryText, setFormDeliveryText] = useState("");
|
||||||
|
|
@ -30,8 +23,6 @@ export const SmsCityProfilesPanel = () => {
|
||||||
const [formPaidStorage, setFormPaidStorage] = useState(true);
|
const [formPaidStorage, setFormPaidStorage] = useState(true);
|
||||||
const [formSmsEnabled, setFormSmsEnabled] = useState(true);
|
const [formSmsEnabled, setFormSmsEnabled] = useState(true);
|
||||||
const [formNote, setFormNote] = useState("");
|
const [formNote, setFormNote] = useState("");
|
||||||
|
|
||||||
// Edit state
|
|
||||||
const [editingId, setEditingId] = useState(null);
|
const [editingId, setEditingId] = useState(null);
|
||||||
|
|
||||||
const fetchProfiles = useCallback(async () => {
|
const fetchProfiles = useCallback(async () => {
|
||||||
|
|
@ -80,16 +71,11 @@ export const SmsCityProfilesPanel = () => {
|
||||||
note: formNote || "",
|
note: formNote || "",
|
||||||
};
|
};
|
||||||
if (editingId) {
|
if (editingId) {
|
||||||
const { error } = await supabase
|
const { error } = await supabase.from("sms_city_profiles").update(payload).eq("id", editingId);
|
||||||
.from("sms_city_profiles")
|
|
||||||
.update(payload)
|
|
||||||
.eq("id", editingId);
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
setMessage("✅ Профиль обновлён");
|
setMessage("✅ Профиль обновлён");
|
||||||
} else {
|
} else {
|
||||||
const { error } = await supabase
|
const { error } = await supabase.from("sms_city_profiles").insert(payload);
|
||||||
.from("sms_city_profiles")
|
|
||||||
.insert(payload);
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
setMessage("✅ Профиль добавлен");
|
setMessage("✅ Профиль добавлен");
|
||||||
}
|
}
|
||||||
|
|
@ -120,10 +106,7 @@ export const SmsCityProfilesPanel = () => {
|
||||||
|
|
||||||
const handleDelete = async (id) => {
|
const handleDelete = async (id) => {
|
||||||
try {
|
try {
|
||||||
const { error } = await supabase
|
const { error } = await supabase.from("sms_city_profiles").delete().eq("id", id);
|
||||||
.from("sms_city_profiles")
|
|
||||||
.delete()
|
|
||||||
.eq("id", id);
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
fetchProfiles();
|
fetchProfiles();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -133,10 +116,7 @@ export const SmsCityProfilesPanel = () => {
|
||||||
|
|
||||||
const handleQuickToggle = async (profile, field) => {
|
const handleQuickToggle = async (profile, field) => {
|
||||||
try {
|
try {
|
||||||
const { error } = await supabase
|
const { error } = await supabase.from("sms_city_profiles").update({ [field]: !profile[field] }).eq("id", profile.id);
|
||||||
.from("sms_city_profiles")
|
|
||||||
.update({ [field]: !profile[field] })
|
|
||||||
.eq("id", profile.id);
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
fetchProfiles();
|
fetchProfiles();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -144,42 +124,100 @@ export const SmsCityProfilesPanel = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ── Styles ──────────────────────────────────────────────────────────────
|
||||||
|
// Карточка профиля — лёгкий зелёный оттенок, виден на белом Panel
|
||||||
|
const profileCardStyle = {
|
||||||
|
backgroundColor: "var(--color-accent-soft)",
|
||||||
|
border: "1px solid var(--color-border)",
|
||||||
|
borderRadius: "16px",
|
||||||
|
padding: "16px",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Внутренний блок (Доставка/Самовывоз) — непрозрачный, контраст с карточкой
|
||||||
|
const innerBlockStyle = {
|
||||||
|
backgroundColor: "var(--color-surface-strong)",
|
||||||
|
border: "1px solid var(--color-border)",
|
||||||
|
borderRadius: "10px",
|
||||||
|
padding: "12px",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Инпут — фон базовый
|
||||||
|
const inputStyle = {
|
||||||
|
backgroundColor: "var(--color-base)",
|
||||||
|
border: "1px solid var(--color-border)",
|
||||||
|
borderRadius: "10px",
|
||||||
|
padding: "8px 12px",
|
||||||
|
color: "var(--color-text)",
|
||||||
|
width: "100%",
|
||||||
|
fontSize: "14px",
|
||||||
|
};
|
||||||
|
|
||||||
|
const labelStyle = {
|
||||||
|
fontSize: "12px",
|
||||||
|
fontWeight: 500,
|
||||||
|
color: "var(--color-text-muted)",
|
||||||
|
};
|
||||||
|
|
||||||
// ── UI helpers ──────────────────────────────────────────────────────────
|
// ── UI helpers ──────────────────────────────────────────────────────────
|
||||||
const ModeBadge = ({ mode }) => {
|
const ModeBadge = ({ mode }) => {
|
||||||
if (mode === "notification") {
|
if (mode === "notification") {
|
||||||
return <span className="rounded-md px-2 py-0.5 text-[10px] font-semibold" style={{ backgroundColor: "rgba(59,130,246,0.15)", color: "#3b82f6" }}>Уведомление</span>;
|
return <span style={{ backgroundColor: "rgba(59,130,246,0.15)", color: "#3b82f6", borderRadius: "6px", padding: "2px 8px", fontSize: "10px", fontWeight: 600 }}>Уведомление</span>;
|
||||||
}
|
}
|
||||||
return <span className="rounded-md px-2 py-0.5 text-[10px] font-semibold" style={{ backgroundColor: "rgba(34,197,94,0.15)", color: "#22c55e" }}>Ссылка</span>;
|
return <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 }) => (
|
const Toggle = ({ checked, onChange, label }) => (
|
||||||
<div className="flex items-center gap-2">
|
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onChange}
|
onClick={onChange}
|
||||||
className={`relative h-5 w-10 rounded-full transition shrink-0 ${checked ? "bg-[#22c55e]" : "bg-[var(--color-border)]"}`}
|
style={{
|
||||||
|
position: "relative", height: "20px", width: "40px", borderRadius: "9999px",
|
||||||
|
transition: "all 0.2s", flexShrink: 0,
|
||||||
|
backgroundColor: checked ? "#22c55e" : "var(--color-border)",
|
||||||
|
border: "none", cursor: "pointer",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<span className={`absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all ${checked ? "left-[20px]" : "left-0.5"}`} />
|
<span style={{
|
||||||
|
position: "absolute", top: "2px", height: "16px", width: "16px", borderRadius: "9999px",
|
||||||
|
backgroundColor: "#fff", transition: "all 0.2s",
|
||||||
|
left: checked ? "20px" : "2px",
|
||||||
|
}} />
|
||||||
</button>
|
</button>
|
||||||
{label && <span className="text-xs text-[var(--color-text)]">{label}</span>}
|
{label && <span style={{ fontSize: "13px", color: "var(--color-text)" }}>{label}</span>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const inputCls = "w-full rounded-lg border border-[var(--color-border)] bg-[var(--color-base)] px-3 py-2 text-sm text-[var(--color-text)] placeholder:text-[var(--color-text-muted)]";
|
const modeButton = (active, activeColor, activeBg, label, onClick) => ({
|
||||||
const cardCls = "rounded-xl border border-[var(--color-border)] bg-[var(--color-base)] p-4 space-y-3";
|
type: "button",
|
||||||
|
onClick,
|
||||||
|
style: {
|
||||||
|
flex: 1,
|
||||||
|
borderRadius: "10px",
|
||||||
|
border: `1px solid ${active ? activeColor : "var(--color-border)"}`,
|
||||||
|
backgroundColor: active ? activeBg : "transparent",
|
||||||
|
color: active ? activeColor : "var(--color-text-muted)",
|
||||||
|
padding: "8px 12px",
|
||||||
|
fontSize: "14px",
|
||||||
|
fontWeight: 500,
|
||||||
|
cursor: "pointer",
|
||||||
|
transition: "all 0.15s",
|
||||||
|
},
|
||||||
|
children: label,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<Panel className="p-5 space-y-3">
|
<Panel className="p-5">
|
||||||
<div className="flex items-center justify-between">
|
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
|
||||||
<div className="flex items-center gap-3">
|
<div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
|
||||||
<span className="text-xl">🏙️</span>
|
<span style={{ fontSize: "20px" }}>🏙️</span>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-sm font-semibold uppercase tracking-[0.16em] text-[var(--color-text)]">
|
<h2 style={{ fontSize: "14px", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.16em", color: "var(--color-text)" }}>
|
||||||
Профили SMS по городам
|
Профили SMS по городам
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-xs text-[var(--color-text-muted)] mt-1">
|
<p style={{ fontSize: "12px", color: "var(--color-text-muted)", marginTop: "4px" }}>
|
||||||
Настройка поведения SMS для каждого города: согласование по ссылке или уведомление.
|
Настройка поведения SMS для каждого города: согласование по ссылке или уведомление.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -187,64 +225,53 @@ export const SmsCityProfilesPanel = () => {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => { setShowForm(!showForm); if (!showForm) resetForm(); }}
|
onClick={() => { setShowForm(!showForm); if (!showForm) resetForm(); }}
|
||||||
className="rounded-xl bg-[var(--color-accent)] px-4 py-2 text-xs font-semibold text-white transition hover:opacity-90"
|
style={{
|
||||||
|
borderRadius: "12px", backgroundColor: "var(--color-accent)", padding: "8px 16px",
|
||||||
|
fontSize: "12px", fontWeight: 600, color: "#fff", border: "none", cursor: "pointer",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{showForm ? "Отмена" : "+ Профиль"}
|
{showForm ? "Отмена" : "+ Профиль"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{message && <span className="text-sm text-[var(--color-text)]">{message}</span>}
|
{message && <span style={{ fontSize: "14px", color: "var(--color-text)", display: "block", marginTop: "8px" }}>{message}</span>}
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
{/* Add/Edit form */}
|
{/* Add/Edit form */}
|
||||||
{showForm && (
|
{showForm && (
|
||||||
<Panel className="p-5 space-y-4">
|
<Panel className="p-5" style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
|
||||||
<h3 className="text-sm font-semibold text-[var(--color-text)]">
|
<h3 style={{ fontSize: "14px", fontWeight: 600, color: "var(--color-text)" }}>
|
||||||
{editingId ? "Редактировать профиль" : "Новый профиль города"}
|
{editingId ? "Редактировать профиль" : "Новый профиль города"}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
{/* City + master toggle */}
|
{/* City + master toggle */}
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div style={{ display: "grid", gap: "16px", gridTemplateColumns: "1fr 1fr" }}>
|
||||||
<div className="space-y-1">
|
<div style={{ display: "flex", flexDirection: "column", gap: "4px" }}>
|
||||||
<label className="text-xs font-medium text-[var(--color-text-muted)]">Город</label>
|
<label style={labelStyle}>Город</label>
|
||||||
<select
|
<select
|
||||||
value={formCity}
|
value={formCity}
|
||||||
onChange={(e) => setFormCity(e.target.value)}
|
onChange={(e) => setFormCity(e.target.value)}
|
||||||
disabled={!!editingId}
|
disabled={!!editingId}
|
||||||
className={`${inputCls} disabled:opacity-50`}
|
style={{ ...inputStyle, opacity: editingId ? 0.5 : 1 }}
|
||||||
>
|
>
|
||||||
{CRIMEAN_CITIES.map((c) => (
|
{CRIMEAN_CITIES.map((c) => (
|
||||||
<option key={c} value={c} style={{ backgroundColor: "var(--color-dropdown-surface)", color: "var(--color-text)" }}>{c}</option>
|
<option key={c} value={c}>{c}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className={cardCls}>
|
<div style={innerBlockStyle}>
|
||||||
<Toggle checked={formSmsEnabled} onChange={() => setFormSmsEnabled(!formSmsEnabled)} label="SMS включены для города" />
|
<Toggle checked={formSmsEnabled} onChange={() => setFormSmsEnabled(!formSmsEnabled)} label="SMS включены для города" />
|
||||||
{!formSmsEnabled && (
|
{!formSmsEnabled && (
|
||||||
<p className="text-xs text-[var(--color-danger)]">⚠ Все SMS в этот город отключены</p>
|
<p style={{ fontSize: "12px", color: "var(--color-danger)", marginTop: "8px" }}>⚠ Все SMS в этот город отключены</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Delivery */}
|
{/* Delivery */}
|
||||||
<div className={cardCls}>
|
<div style={innerBlockStyle}>
|
||||||
<div className="text-xs font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">📦 Доставка</div>
|
<div style={{ fontSize: "12px", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.05em", color: "var(--color-text-muted)", marginBottom: "12px" }}>📦 Доставка</div>
|
||||||
<div className="flex gap-2">
|
<div style={{ display: "flex", gap: "8px" }}>
|
||||||
<button
|
<button {...modeButton(formDeliveryMode === "link", "#22c55e", "rgba(34,197,94,0.1)", "🔗 Ссылка", () => setFormDeliveryMode("link"))} />
|
||||||
type="button"
|
<button {...modeButton(formDeliveryMode === "notification", "#3b82f6", "rgba(59,130,246,0.1)", "📢 Уведомление", () => setFormDeliveryMode("notification"))} />
|
||||||
onClick={() => setFormDeliveryMode("link")}
|
|
||||||
className={["flex-1 rounded-lg border px-3 py-2 text-sm font-medium transition",
|
|
||||||
formDeliveryMode === "link" ? "border-[#22c55e] bg-[rgba(34,197,94,0.1)] text-[#22c55e]" : "border-[var(--color-border)] text-[var(--color-text-muted)] hover:bg-[var(--color-surface)]"].join(" ")}
|
|
||||||
>
|
|
||||||
🔗 Ссылка
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setFormDeliveryMode("notification")}
|
|
||||||
className={["flex-1 rounded-lg border px-3 py-2 text-sm font-medium transition",
|
|
||||||
formDeliveryMode === "notification" ? "border-[#3b82f6] bg-[rgba(59,130,246,0.1)] text-[#3b82f6]" : "border-[var(--color-border)] text-[var(--color-text-muted)] hover:bg-[var(--color-surface)]"].join(" ")}
|
|
||||||
>
|
|
||||||
📢 Уведомление
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
{formDeliveryMode === "notification" && (
|
{formDeliveryMode === "notification" && (
|
||||||
<textarea
|
<textarea
|
||||||
|
|
@ -252,34 +279,20 @@ export const SmsCityProfilesPanel = () => {
|
||||||
onChange={(e) => setFormDeliveryText(e.target.value)}
|
onChange={(e) => setFormDeliveryText(e.target.value)}
|
||||||
placeholder="Ваш заказ готов. Доставим по графику."
|
placeholder="Ваш заказ готов. Доставим по графику."
|
||||||
rows={2}
|
rows={2}
|
||||||
className={`${inputCls} resize-none`}
|
style={{ ...inputStyle, resize: "none", marginTop: "8px" }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<p className="text-xs text-[var(--color-text-muted)]">
|
<p style={{ fontSize: "12px", color: "var(--color-text-muted)", marginTop: "8px" }}>
|
||||||
{formDeliveryMode === "link" ? "Клиент получит ссылку для выбора даты доставки" : "Уведомление без ссылки, без 2-й SMS"}
|
{formDeliveryMode === "link" ? "Клиент получит ссылку для выбора даты доставки" : "Уведомление без ссылки, без 2-й SMS"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Pickup */}
|
{/* Pickup */}
|
||||||
<div className={cardCls}>
|
<div style={innerBlockStyle}>
|
||||||
<div className="text-xs font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">🏪 Самовывоз</div>
|
<div style={{ fontSize: "12px", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.05em", color: "var(--color-text-muted)", marginBottom: "12px" }}>🏪 Самовывоз</div>
|
||||||
<div className="flex gap-2">
|
<div style={{ display: "flex", gap: "8px" }}>
|
||||||
<button
|
<button {...modeButton(formPickupMode === "link", "#22c55e", "rgba(34,197,94,0.1)", "🔗 Ссылка", () => setFormPickupMode("link"))} />
|
||||||
type="button"
|
<button {...modeButton(formPickupMode === "notification", "#3b82f6", "rgba(59,130,246,0.1)", "📢 Уведомление", () => setFormPickupMode("notification"))} />
|
||||||
onClick={() => setFormPickupMode("link")}
|
|
||||||
className={["flex-1 rounded-lg border px-3 py-2 text-sm font-medium transition",
|
|
||||||
formPickupMode === "link" ? "border-[#22c55e] bg-[rgba(34,197,94,0.1)] text-[#22c55e]" : "border-[var(--color-border)] text-[var(--color-text-muted)] hover:bg-[var(--color-surface)]"].join(" ")}
|
|
||||||
>
|
|
||||||
🔗 Ссылка
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setFormPickupMode("notification")}
|
|
||||||
className={["flex-1 rounded-lg border px-3 py-2 text-sm font-medium transition",
|
|
||||||
formPickupMode === "notification" ? "border-[#3b82f6] bg-[rgba(59,130,246,0.1)] text-[#3b82f6]" : "border-[var(--color-border)] text-[var(--color-text-muted)] hover:bg-[var(--color-surface)]"].join(" ")}
|
|
||||||
>
|
|
||||||
📢 Уведомление
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
{formPickupMode === "notification" && (
|
{formPickupMode === "notification" && (
|
||||||
<textarea
|
<textarea
|
||||||
|
|
@ -287,30 +300,30 @@ export const SmsCityProfilesPanel = () => {
|
||||||
onChange={(e) => setFormPickupText(e.target.value)}
|
onChange={(e) => setFormPickupText(e.target.value)}
|
||||||
placeholder="Ваш заказ готов. Заберите со склада."
|
placeholder="Ваш заказ готов. Заберите со склада."
|
||||||
rows={2}
|
rows={2}
|
||||||
className={`${inputCls} resize-none`}
|
style={{ ...inputStyle, resize: "none", marginTop: "8px" }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Toggles row */}
|
{/* Toggles row */}
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<div style={{ display: "grid", gap: "12px", gridTemplateColumns: "1fr 1fr" }}>
|
||||||
<div className={cardCls}>
|
<div style={innerBlockStyle}>
|
||||||
<Toggle checked={formSecondSms} onChange={() => setFormSecondSms(!formSecondSms)} label="2-я SMS (повтор)" />
|
<Toggle checked={formSecondSms} onChange={() => setFormSecondSms(!formSecondSms)} label="2-я SMS (повтор)" />
|
||||||
</div>
|
</div>
|
||||||
<div className={cardCls}>
|
<div style={innerBlockStyle}>
|
||||||
<Toggle checked={formPaidStorage} onChange={() => setFormPaidStorage(!formPaidStorage)} label="SMS: платное хранение" />
|
<Toggle checked={formPaidStorage} onChange={() => setFormPaidStorage(!formPaidStorage)} label="SMS: платное хранение" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Note */}
|
{/* Note */}
|
||||||
<div className="space-y-1">
|
<div style={{ display: "flex", flexDirection: "column", gap: "4px" }}>
|
||||||
<label className="text-xs font-medium text-[var(--color-text-muted)]">Комментарий</label>
|
<label style={labelStyle}>Комментарий</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formNote}
|
value={formNote}
|
||||||
onChange={(e) => setFormNote(e.target.value)}
|
onChange={(e) => setFormNote(e.target.value)}
|
||||||
placeholder="Например: Ялта — доставка без согласования"
|
placeholder="Например: Ялта — доставка без согласования"
|
||||||
className={inputCls}
|
style={inputStyle}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -318,7 +331,11 @@ export const SmsCityProfilesPanel = () => {
|
||||||
type="button"
|
type="button"
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
className="rounded-xl bg-[var(--color-accent)] px-5 py-2.5 text-sm font-semibold text-white transition hover:opacity-90 disabled:opacity-40"
|
style={{
|
||||||
|
borderRadius: "12px", backgroundColor: "var(--color-accent)", padding: "10px 20px",
|
||||||
|
fontSize: "14px", fontWeight: 600, color: "#fff", border: "none", cursor: "pointer",
|
||||||
|
opacity: saving ? 0.4 : 1,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{saving ? "Сохранение..." : editingId ? "Обновить профиль" : "Сохранить профиль"}
|
{saving ? "Сохранение..." : editingId ? "Обновить профиль" : "Сохранить профиль"}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -326,44 +343,52 @@ export const SmsCityProfilesPanel = () => {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Profiles list */}
|
{/* Profiles list */}
|
||||||
<Panel className="p-5 space-y-3">
|
<Panel className="p-5" style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
|
||||||
<h2 className="text-sm font-semibold uppercase tracking-[0.16em] text-[var(--color-text)]">
|
<h2 style={{ fontSize: "14px", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.16em", color: "var(--color-text)" }}>
|
||||||
Профили ({profiles.length})
|
Профили ({profiles.length})
|
||||||
</h2>
|
</h2>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<p className="text-sm text-[var(--color-text-muted)]">Загрузка...</p>
|
<p style={{ fontSize: "14px", color: "var(--color-text-muted)" }}>Загрузка...</p>
|
||||||
) : profiles.length === 0 ? (
|
) : profiles.length === 0 ? (
|
||||||
<div className="rounded-xl border border-dashed border-[var(--color-border)] p-6 text-center">
|
<div style={{ border: "1px dashed var(--color-border)", borderRadius: "12px", padding: "24px", textAlign: "center" }}>
|
||||||
<p className="text-sm text-[var(--color-text-muted)]">
|
<p style={{ fontSize: "14px", color: "var(--color-text-muted)" }}>
|
||||||
Профилей нет. Города без профиля используют стандартный флоу (ссылка на согласование).
|
Профилей нет. Города без профиля используют стандартный флоу (ссылка на согласование).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-3">
|
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
|
||||||
{profiles.map((p) => (
|
{profiles.map((p) => (
|
||||||
<div key={p.id} className="rounded-xl border border-[var(--color-border)] bg-[var(--color-surface)] p-4 space-y-3">
|
<div key={p.id} style={profileCardStyle}>
|
||||||
{/* Header row */}
|
{/* Header row */}
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "12px" }}>
|
||||||
<div className="flex items-center gap-3">
|
<div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
|
||||||
<span className="text-base font-semibold text-[var(--color-text)]">{p.city}</span>
|
<span style={{ fontSize: "16px", fontWeight: 600, color: "var(--color-text)" }}>{p.city}</span>
|
||||||
{!p.sms_enabled ? (
|
{!p.sms_enabled ? (
|
||||||
<span className="rounded-md px-2 py-0.5 text-[10px] font-semibold" style={{ backgroundColor: "rgba(239,68,68,0.15)", color: "#ef4444" }}>SMS OFF</span>
|
<span style={{ backgroundColor: "rgba(239,68,68,0.15)", color: "#ef4444", borderRadius: "6px", padding: "2px 8px", fontSize: "10px", fontWeight: 600 }}>SMS OFF</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="rounded-md px-2 py-0.5 text-[10px] font-semibold" style={{ backgroundColor: "rgba(34,197,94,0.15)", color: "#22c55e" }}>SMS ON</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>
|
||||||
<div className="flex items-center gap-2">
|
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleEdit(p)}
|
onClick={() => handleEdit(p)}
|
||||||
className="rounded-lg border border-[var(--color-border)] px-2.5 py-1 text-xs font-medium text-[var(--color-text)] hover:bg-[var(--color-surface)]"
|
style={{
|
||||||
|
borderRadius: "8px", border: "1px solid var(--color-border)", padding: "4px 10px",
|
||||||
|
fontSize: "12px", fontWeight: 500, color: "var(--color-text)", cursor: "pointer",
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
✏️ Изменить
|
✏️ Изменить
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleDelete(p.id)}
|
onClick={() => handleDelete(p.id)}
|
||||||
className="rounded-lg p-1.5 text-[var(--color-text-muted)] hover:bg-[var(--color-surface)] hover:text-[var(--color-danger)]"
|
style={{
|
||||||
|
borderRadius: "8px", padding: "4px 8px", fontSize: "14px",
|
||||||
|
color: "var(--color-text-muted)", cursor: "pointer", backgroundColor: "transparent",
|
||||||
|
border: "none",
|
||||||
|
}}
|
||||||
title="Удалить"
|
title="Удалить"
|
||||||
>
|
>
|
||||||
✕
|
✕
|
||||||
|
|
@ -372,41 +397,39 @@ export const SmsCityProfilesPanel = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Config grid */}
|
{/* Config grid */}
|
||||||
<div className="grid gap-2 sm:grid-cols-2">
|
<div style={{ display: "grid", gap: "8px", gridTemplateColumns: "1fr 1fr", marginBottom: "12px" }}>
|
||||||
{/* Delivery */}
|
<div style={innerBlockStyle}>
|
||||||
<div className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-strong)] p-3 space-y-1.5">
|
<div style={{ display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap" }}>
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
<span style={{ fontSize: "12px", fontWeight: 500, color: "var(--color-text)" }}>📦 Доставка</span>
|
||||||
<span className="text-xs font-medium text-[var(--color-text)]">📦 Доставка</span>
|
|
||||||
<ModeBadge mode={p.delivery_mode} />
|
<ModeBadge mode={p.delivery_mode} />
|
||||||
</div>
|
</div>
|
||||||
{p.delivery_text && (
|
{p.delivery_text && (
|
||||||
<p className="text-xs text-[var(--color-text-muted)] italic leading-snug">"{p.delivery_text}"</p>
|
<p style={{ fontSize: "12px", color: "var(--color-text-muted)", fontStyle: "italic", lineHeight: "1.4", marginTop: "6px" }}>"{p.delivery_text}"</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* Pickup */}
|
<div style={innerBlockStyle}>
|
||||||
<div className="rounded-lg border border-[var(--color-border)] bg-[var(--color-surface-strong)] p-3 space-y-1.5">
|
<div style={{ display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap" }}>
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
<span style={{ fontSize: "12px", fontWeight: 500, color: "var(--color-text)" }}>🏪 Самовывоз</span>
|
||||||
<span className="text-xs font-medium text-[var(--color-text)]">🏪 Самовывоз</span>
|
|
||||||
<ModeBadge mode={p.pickup_mode} />
|
<ModeBadge mode={p.pickup_mode} />
|
||||||
</div>
|
</div>
|
||||||
{p.pickup_text && (
|
{p.pickup_text && (
|
||||||
<p className="text-xs text-[var(--color-text-muted)] italic leading-snug">"{p.pickup_text}"</p>
|
<p style={{ fontSize: "12px", color: "var(--color-text-muted)", fontStyle: "italic", lineHeight: "1.4", marginTop: "6px" }}>"{p.pickup_text}"</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Toggles row */}
|
{/* Toggles row */}
|
||||||
<div className="flex flex-wrap items-center gap-4 pt-1 border-t border-[var(--color-border)]">
|
<div style={{ display: "flex", flexWrap: "wrap", alignItems: "center", gap: "16px", paddingTop: "8px", borderTop: "1px solid var(--color-border)" }}>
|
||||||
<span className={`text-xs font-medium ${p.second_sms_enabled ? "text-[var(--color-text)]" : "text-[var(--color-text-muted)]"}`}>
|
<span style={{ fontSize: "12px", fontWeight: 500, color: p.second_sms_enabled ? "var(--color-text)" : "var(--color-text-muted)" }}>
|
||||||
2-я SMS: {p.second_sms_enabled ? "✅" : "❌"}
|
2-я SMS: {p.second_sms_enabled ? "✅" : "❌"}
|
||||||
</span>
|
</span>
|
||||||
<span className={`text-xs font-medium ${p.paid_storage_enabled ? "text-[var(--color-text)]" : "text-[var(--color-text-muted)]"}`}>
|
<span style={{ fontSize: "12px", fontWeight: 500, color: p.paid_storage_enabled ? "var(--color-text)" : "var(--color-text-muted)" }}>
|
||||||
Платное хранение: {p.paid_storage_enabled ? "✅" : "❌"}
|
Платное хранение: {p.paid_storage_enabled ? "✅" : "❌"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{p.note && (
|
{p.note && (
|
||||||
<p className="text-xs text-[var(--color-text-muted)] pt-1">💬 {p.note}</p>
|
<p style={{ fontSize: "12px", color: "var(--color-text-muted)", paddingTop: "8px" }}>💬 {p.note}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue