supersam/src/components/admin/StopWordsPanel.jsx

131 lines
4.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React from "react";
import { Panel } from "../UI/Panel";
import { Button } from "../UI/Button";
import { Skeleton } from "../UI/Loading";
import { supabase } from "../../supabaseClient";
export const StopWordsPanel = () => {
const [words, setWords] = React.useState([]);
const [newWord, setNewWord] = React.useState("");
const [isLoading, setIsLoading] = React.useState(true);
const [error, setError] = React.useState("");
const [deletingId, setDeletingId] = React.useState(null);
const loadWords = React.useCallback(async () => {
setIsLoading(true);
setError("");
const { data, error: fetchError } = await supabase
.from("stop_words")
.select("id, word, created_at")
.order("word", { ascending: true });
if (fetchError) {
setError(fetchError.message);
} else {
setWords(data || []);
}
setIsLoading(false);
}, []);
React.useEffect(() => { loadWords(); }, [loadWords]);
const handleAdd = async () => {
const trimmed = newWord.trim().toLowerCase();
if (!trimmed) return;
if (words.some((w) => w.word === trimmed)) {
setError("Такое слово уже есть");
return;
}
setError("");
const { error: insertError } = await supabase
.from("stop_words")
.insert({ word: trimmed });
if (insertError) {
setError(insertError.message);
return;
}
setNewWord("");
await loadWords();
};
const handleDelete = async (id) => {
setDeletingId(id);
const { error: deleteError } = await supabase
.from("stop_words")
.delete()
.eq("id", id);
if (deleteError) {
setError(deleteError.message);
} else {
await loadWords();
}
setDeletingId(null);
};
const handleKeyDown = (e) => {
if (e.key === "Enter") {
e.preventDefault();
handleAdd();
}
};
return (
<Panel className="space-y-5 p-6">
<div>
<h2 className="text-lg font-semibold">Стоп-слова</h2>
<p className="mt-1 text-sm text-[var(--color-text-muted)]">
Позиции с этими словами не показываются клиентам в карточке доставки.
Добавляйте слова-маркеры: «сверление», «обмер» и т.д.
</p>
</div>
<div className="flex gap-3">
<input
type="text"
value={newWord}
onChange={(e) => setNewWord(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Новое стоп-слово"
className="flex-1 rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface)] px-4 py-2.5 text-sm !text-[var(--color-text)] outline-none transition focus:border-[var(--color-accent)]"
maxLength={100}
/>
<Button onClick={handleAdd} disabled={!newWord.trim()}>
Добавить
</Button>
</div>
{error && (
<p className="text-sm text-[var(--color-warning)]">{error}</p>
)}
{isLoading ? (
<div className="flex flex-wrap gap-2">
{Array.from({ length: 6 }).map((_, i) => (
<Skeleton key={i} className="w-20 h-8 rounded-full" />
))}
</div>
) : !words.length ? (
<p className="text-sm text-[var(--color-text-muted)]">Стоп-слов пока нет. Добавьте первое.</p>
) : (
<div className="flex flex-wrap gap-2">
{words.map((w) => (
<span
key={w.id}
className="inline-flex items-center gap-1.5 rounded-full border border-[var(--color-border)] bg-[var(--color-surface-strong)] px-3 py-1.5 text-sm !text-[var(--color-text)]"
>
{w.word}
<button
type="button"
disabled={deletingId === w.id}
onClick={() => handleDelete(w.id)}
className="ml-0.5 flex h-4 w-4 items-center justify-center rounded-full text-[var(--color-text-muted)] transition hover:bg-[var(--color-accent-soft)] hover:!text-[var(--color-danger)] disabled:opacity-40"
aria-label={`Удалить ${w.word}`}
>
×
</button>
</span>
))}
</div>
)}
</Panel>
);
};