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:
Ник (Claude)
2026-06-10 15:10:33 +03:00
parent a3d881aeed
commit 8ad9d19569
3 changed files with 60 additions and 4 deletions
+56 -2
View File
@@ -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">