/* ===== Подари вторую жизнь — Admin ===== */ function adminReplaceStore(list){ PF.animals.length=0; list.forEach(x=>PF.animals.push(x)); } function buildAnimal(form){ const ageMonths = Math.max(1, Number(form.ageMonths)||12); const photos = (form.photos && form.photos.length) ? form.photos : [PF.U(form.species==='cat'?'1518791841217-8f162f1e1131':'1543466835-00a7907e9de1',800)]; const waiting = Math.max(0, Number(form.waiting)||0); return { id: form.id || ('pet-'+Date.now().toString(36)), name: form.name?.trim()||'Без клички', species:form.species, breed:form.breed?.trim()||'Метис', color:form.color, size:form.size, sex:form.sex, ageMonths, weight: Math.max(0.1, Number(form.weight)||5), health: form.health?.trim()||'Привит, обработан от паразитов', needs: form.needs?.trim() || null, waiting, second: waiting >= 12 || PF.ageGroup(ageMonths)==='senior', loc: form.loc?.trim()||'Передержка, Москва', contact: form.contact?.trim()||'+7 000 000-00-00', desc: form.desc?.trim()||'Описание появится позже.', ageText: PF.ageLabel(ageMonths), ageGroup: PF.ageGroup(ageMonths), photoUrl: photos[0], photoUrlSm: photos[0], galleryUrls: photos, gallery: photos, photo:null, }; } function AdminScreen(){ const { go } = useNav(); const [authed, setAuthed] = useState(()=> sessionStorage.getItem('pf_admin')==='1'); const [pass, setPass] = useState(''); const [err, setErr] = useState(false); const [, force] = useState(0); const [view, setView] = useState('list'); // list | form const [editing, setEditing] = useState(null); const [toast, setToast] = useState(null); const list = PF.animals; const login = (e)=>{ e.preventDefault(); if(pass===PF.ADMIN_PASS){ sessionStorage.setItem('pf_admin','1'); setAuthed(true);} else { setErr(true);} }; const logout = ()=>{ sessionStorage.removeItem('pf_admin'); setAuthed(false); setPass(''); }; const notify = (m)=>{ setToast(m); setTimeout(()=>setToast(null),2400); }; const onSave = (form)=>{ const built = buildAnimal(form); if(form.id){ const i=list.findIndex(x=>x.id===form.id); if(i>=0) list[i]=built; notify('Изменения сохранены'); } else { list.unshift(built); notify('Питомец добавлен'); } setView('list'); setEditing(null); force(n=>n+1); }; const onDelete = (id)=>{ const i=list.findIndex(x=>x.id===id); if(i>=0) list.splice(i,1); notify('Питомец удалён'); force(n=>n+1); }; if(!authed){ return (

Вход для кураторов

Раздел доступен по паролю. Подсказка для демо — lapka

{setPass(e.target.value);setErr(false);}} style={{width:'100%',padding:'14px 16px',borderRadius:'var(--radius-sm)',border:`1.5px solid ${err?'#e0a0a0':'var(--line-strong)'}`,fontSize:16,fontFamily:'var(--font-body)',textAlign:'center',marginBottom:err?8:18}}/> {err &&
Неверный пароль, попробуйте ещё раз
}
); } if(view==='form') return {setView('list');setEditing(null);}} toast={toast}/>; return (
Админ-панель

Управление животными

{list.length} {plural(list.length,'питомец','питомца','питомцев')} в базе

{list.map(a=>(
{a.name}
{PF.speciesOne(a.species)} · {a.breed} · {a.ageText} · {PF.sizeLabel(a.size)}
{a.loc}
))}
{toast && }
); } function AdminForm({ initial, onSave, onCancel, toast }){ const [form, setForm] = useState(()=> initial ? { id:initial.id, name:initial.name, species:initial.species, breed:initial.breed, color:initial.color, size:initial.size, sex:initial.sex, ageMonths:initial.ageMonths, loc:initial.loc, contact:initial.contact, desc:initial.desc, weight:initial.weight, health:initial.health, needs:initial.needs||'', waiting:initial.waiting, photos:[...(initial.galleryUrls||[])] } : { name:'', species:'cat', breed:'', color:'grey', size:'medium', sex:'male', ageMonths:12, weight:5, health:'Привит, обработан от паразитов', needs:'', waiting:1, loc:'', contact:'', desc:'', photos:[] }); const set = (k,v)=> setForm(f=>({...f,[k]:v})); const addPhotos = (files)=>{ [...files].forEach(file=>{ if(!file.type.startsWith('image/'))return; const r=new FileReader(); r.onload=e=>setForm(f=>({...f,photos:[...f.photos,e.target.result]})); r.readAsDataURL(file); }); }; const fieldStyle = {width:'100%',padding:'12px 14px',borderRadius:'var(--radius-sm)',border:'1.5px solid var(--line-strong)',fontSize:15,fontFamily:'var(--font-body)',background:'var(--surface)'}; const Lbl = ({children}) => ; const Seg = ({value,opts,onCh,renderIco}) => (
{opts.map(o=>( ))}
); return (

{initial?'Редактировать питомца':'Новый питомец'}

Заполните карточку — она появится в каталоге сразу после сохранения.

{/* photos */}
Фотографии
{form.photos.map((p,i)=>(
{i===0 && главное}
))}
Кличкаset('name',e.target.value)} placeholder="Например, Барсик"/>
Породаset('breed',e.target.value)} placeholder="Метис / Британская…"/>
Вид({id:s.id,label:s.one}))} onCh={v=>set('species',v)} renderIco={o=>}/>
Окрасset('color',v)} renderIco={o=>}/>
Размерset('size',v)}/>
Полset('sex',v)} renderIco={o=>o.id==='male'?:}/>
Возраст (мес.) set('ageMonths',e.target.value)}/>
≈ {PF.ageLabel(Math.max(1,Number(form.ageMonths)||1))}
Вес (кг) set('weight',e.target.value)}/>
В ожидании (мес.) set('waiting',e.target.value)}/>
{Number(form.waiting)>=12?'· «Второй шанс»':'·'}
Здоровьеset('health',e.target.value)} placeholder="Привит, обработан, стерилизован…"/>
Особые потребности (если есть)set('needs',e.target.value)} placeholder="Диета, без маленьких детей, опытный хозяин…"/>
Описание характера