feat: rental drafts, room modal cleanup, tariff auto-code
- New hotels get 3 rental object templates (tennis/sauna/conference) as drafts (is_active=false) - RentalPage: draft templates shown separately with Activate/Delete buttons - Migration 047: adds is_active column to rental_objects - RoomModal: hide bed type/status/housekeeping in create mode; optional price (from category); restructured Places tab with extra beds constructor - TariffsPage: auto-generate tariff code from name initials if code field is empty - types: ExtraPlace.beds for extra place bed configuration Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -166,6 +166,18 @@ function TariffModal({
|
||||
const set = <K extends keyof typeof form>(k: K, v: typeof form[K]) =>
|
||||
setForm(prev => ({ ...prev, [k]: v }))
|
||||
|
||||
// Авто-генерация кода из названия (если поле кода пустое)
|
||||
const autoCode = (name: string) =>
|
||||
name.trim().split(/\s+/).map(w => w[0]?.toUpperCase() ?? '').join('').slice(0, 6) || ''
|
||||
|
||||
const handleNameChange = (v: string) => {
|
||||
setForm(prev => ({
|
||||
...prev,
|
||||
name: v,
|
||||
code: prev.code || !tariff ? autoCode(v) : prev.code,
|
||||
}))
|
||||
}
|
||||
|
||||
const toggleInclusion = (id: string) =>
|
||||
setForm(prev => ({
|
||||
...prev,
|
||||
@@ -204,9 +216,13 @@ function TariffModal({
|
||||
<>
|
||||
<button onClick={onClose} className="btn-secondary">Отмена</button>
|
||||
<button
|
||||
onClick={() => { if (form.name && form.code) onSave(form) }}
|
||||
onClick={() => {
|
||||
if (!form.name) return
|
||||
const code = form.code || autoCode(form.name) || `T${Date.now().toString(36).toUpperCase().slice(-4)}`
|
||||
onSave({ ...form, code })
|
||||
}}
|
||||
className="btn-primary"
|
||||
disabled={!form.name || !form.code}
|
||||
disabled={!form.name}
|
||||
>
|
||||
{tariff ? 'Сохранить' : 'Создать тариф'}
|
||||
</button>
|
||||
@@ -221,13 +237,13 @@ function TariffModal({
|
||||
Название тарифа *
|
||||
</label>
|
||||
<input type="text" className="input" placeholder="Стандарт с завтраком"
|
||||
value={form.name} onChange={e => set('name', e.target.value)} />
|
||||
value={form.name} onChange={e => handleNameChange(e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1.5">
|
||||
Код *
|
||||
Код <span className="text-slate-400 font-normal text-xs">(необязательно)</span>
|
||||
</label>
|
||||
<input type="text" className="input font-mono uppercase" placeholder="STD-BB"
|
||||
<input type="text" className="input font-mono uppercase" placeholder="авто"
|
||||
value={form.code}
|
||||
onChange={e => set('code', e.target.value.toUpperCase())} />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user