fix: goal+language in ChannelEdit, metrics 500 (await params)
ChannelEdit.js: - Добавлены goal (multi-select + кастомные, как в форме создания) - Добавлен language (select: ru/en/uk/kk) - Импортированы Plus, X иконки и GOALS константа app/api/metrics/channel/[channelId]/route.js: app/api/metrics/best-time/[channelId]/route.js: - await params (Next.js 16 требует), иначе 500
This commit is contained in:
@@ -2,7 +2,15 @@
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import { ArrowLeft, Save, Trash2, Loader2, Image as ImageIcon, Type, Palette } from 'lucide-react';
|
||||
import { ArrowLeft, Save, Trash2, Loader2, Image as ImageIcon, Type, Palette, Plus, X } from 'lucide-react';
|
||||
|
||||
const GOALS = [
|
||||
{ v: 'educational', label: 'Обучение', desc: 'Объясняем, разбираем' },
|
||||
{ v: 'news', label: 'Новости', desc: 'Что произошло' },
|
||||
{ v: 'entertainment', label: 'Развлечение', desc: 'Лёгкий контент, мемы' },
|
||||
{ v: 'expert', label: 'Экспертный', desc: 'Глубокий анализ, инсайты' },
|
||||
{ v: 'sales', label: 'Продажи', desc: 'Подвести к покупке' },
|
||||
];
|
||||
|
||||
const TONES = [
|
||||
{ v: 'friendly', label: 'Дружелюбный' },
|
||||
@@ -66,6 +74,11 @@ export default function ChannelEdit({ channel }) {
|
||||
const [name, setName] = useState(channel.name || '');
|
||||
const [niche, setNiche] = useState(channel.niche || '');
|
||||
const [audience, setAudience] = useState(channel.audience || '');
|
||||
const [goals, setGoals] = useState(
|
||||
channel.goal ? channel.goal.split(',').map(g => g.trim()).filter(Boolean) : ['educational']
|
||||
);
|
||||
const [customGoal, setCustomGoal] = useState('');
|
||||
const [language, setLanguage] = useState(channel.language || 'ru');
|
||||
const [tone, setTone] = useState(style.tone || 'friendly');
|
||||
const [formality, setFormality] = useState(style.formality || 'informal');
|
||||
const [humor, setHumor] = useState(style.humor || 'moderate');
|
||||
@@ -92,7 +105,7 @@ export default function ChannelEdit({ channel }) {
|
||||
setError('');
|
||||
try {
|
||||
const data = {
|
||||
name, niche, audience,
|
||||
name, niche, audience, goal: goals.join(','), language,
|
||||
style: {
|
||||
tone, formality, humor,
|
||||
post_length: postLength,
|
||||
@@ -185,6 +198,47 @@ export default function ChannelEdit({ channel }) {
|
||||
<label className="label">Аудитория</label>
|
||||
<textarea className="input min-h-[70px]" value={audience} onChange={e => setAudience(e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="label">Цель канала <span className="text-gray-500 font-normal">(можно несколько)</span></label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-5 gap-2 mb-2">
|
||||
{GOALS.map(g => {
|
||||
const on = goals.includes(g.v);
|
||||
return (
|
||||
<button key={g.v} type="button"
|
||||
onClick={() => setGoals(on ? goals.filter(x => x !== g.v) : [...goals, g.v])}
|
||||
className={`p-2.5 rounded-lg border text-left transition-colors ${on ? 'border-accent bg-accent/10' : 'border-border bg-surface2 hover:border-gray-600'}`}>
|
||||
<div className="text-sm font-medium">{g.label}</div>
|
||||
<div className="text-xs text-gray-500 mt-0.5">{g.desc}</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<input className="input text-sm flex-1" placeholder="Своя цель — введи и нажми +"
|
||||
value={customGoal} onChange={e => setCustomGoal(e.target.value)}
|
||||
onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); const v = customGoal.trim(); if (v && !goals.includes(v)) setGoals([...goals, v]); setCustomGoal(''); }}} />
|
||||
<button type="button" onClick={() => { const v = customGoal.trim(); if (v && !goals.includes(v)) setGoals([...goals, v]); setCustomGoal(''); }}
|
||||
disabled={!customGoal.trim()} className="btn-primary px-3 disabled:opacity-40"><Plus className="w-4 h-4" /></button>
|
||||
</div>
|
||||
{goals.filter(g => !GOALS.find(x => x.v === g)).length > 0 && (
|
||||
<div className="flex flex-wrap gap-1.5 mt-2">
|
||||
{goals.filter(g => !GOALS.find(x => x.v === g)).map(g => (
|
||||
<span key={g} className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-accent/15 border border-accent/40 text-xs">
|
||||
{g}<button type="button" onClick={() => setGoals(goals.filter(x => x !== g))}><X className="w-3 h-3" /></button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label className="label">Язык постов</label>
|
||||
<select className="input" value={language} onChange={e => setLanguage(e.target.value)}>
|
||||
<option value="ru">Русский</option>
|
||||
<option value="en">English</option>
|
||||
<option value="uk">Українська</option>
|
||||
<option value="kk">Қазақша</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card p-5 space-y-4">
|
||||
|
||||
Reference in New Issue
Block a user