fix: smart next-free room number suggestion

- Placeholder shows next free after global max (e.g. 204 if 203 is max)
- Error message suggests next free starting from typed+1:
  type 101 (taken) → suggests 102
  type 201 (taken) → suggests 202
  type 1011 (taken) → suggests 1012
- Suggestion is a clickable link that auto-fills the field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-23 13:51:10 +03:00
parent 4ea6dcc368
commit eea76657e1

View File

@@ -127,21 +127,31 @@ export function RoomModal({ open, room, existingNumbers = [], categories = [], o
setPhotoIdx(p => Math.max(0, p - 1)) setPhotoIdx(p => Math.max(0, p - 1))
} }
// Find next available room number (skips existing ones) const numberTaken = !isEdit && !!form.number && existingNumbers.includes(form.number)
const nextFreeNumber = (() => {
if (isEdit) return null // Find next available room number starting from a given base
const findNextFree = (base: number): string => {
const taken = new Set(existingNumbers) const taken = new Set(existingNumbers)
// Try incrementing from the highest existing number let candidate = base
const nums = existingNumbers
.map(n => parseInt(n))
.filter(n => !isNaN(n))
.sort((a, b) => a - b)
let candidate = (nums[nums.length - 1] ?? 100) + 1
while (taken.has(String(candidate))) candidate++ while (taken.has(String(candidate))) candidate++
return String(candidate) return String(candidate)
}
// Placeholder for empty field: next free after current max
const placeholderNumber = (() => {
if (isEdit) return null
const nums = existingNumbers.map(n => parseInt(n)).filter(n => !isNaN(n))
const max = nums.length > 0 ? Math.max(...nums) : 100
return findNextFree(max + 1)
})() })()
const numberTaken = !isEdit && !!form.number && existingNumbers.includes(form.number) // Suggestion when typed number is taken: increment from typed+1
const nextFreeNumber = (() => {
if (!numberTaken) return null
const typedNum = parseInt(form.number)
if (isNaN(typedNum)) return null
return findNextFree(typedNum + 1)
})()
const handleSave = () => { const handleSave = () => {
if (!form.number || numberTaken) return if (!form.number || numberTaken) return
@@ -233,12 +243,15 @@ export function RoomModal({ open, room, existingNumbers = [], categories = [], o
<input <input
type="text" type="text"
className={cn('input', numberTaken && 'border-red-400 focus:ring-red-400')} className={cn('input', numberTaken && 'border-red-400 focus:ring-red-400')}
placeholder={nextFreeNumber ?? '101'} placeholder={placeholderNumber ?? '101'}
value={form.number} value={form.number}
onChange={e => set('number', e.target.value)} onChange={e => set('number', e.target.value)}
/> />
{numberTaken && ( {numberTaken && (
<p className="text-xs text-red-500 mt-1">Номер {form.number} уже существует{nextFreeNumber ? `. Следующий свободный: ${nextFreeNumber}` : ''}</p> <p className="text-xs text-red-500 mt-1">
Номер {form.number} уже занят
{nextFreeNumber ? <> следующий свободный: <button type="button" className="underline font-medium" onClick={() => set('number', nextFreeNumber)}>{nextFreeNumber}</button></> : ''}
</p>
)} )}
</div> </div>
<div> <div>