Remove NewBook room blocking — app never writes to NewBook
Unsellable flag is in-app visibility only; staff mark rooms out of order in NewBook through their own process. NewBook use is now read-only (room sync + occupancy filter). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
6ca395097e
commit
3289a31027
9 changed files with 11 additions and 114 deletions
|
|
@ -84,12 +84,6 @@ export function resolveTask(id: number, body: {
|
|||
export function addComment(id: number, note: string, addToTemplate = false): Promise<{ ok: boolean; added_to_template: boolean }> {
|
||||
return request(`/tasks/${id}/comments`, { method: 'POST', body: JSON.stringify({ note, add_to_template: addToTemplate }) })
|
||||
}
|
||||
export function blockRoomInNewbook(id: number): Promise<{ ok: boolean }> {
|
||||
return request(`/tasks/${id}/newbook-block`, { method: 'POST' })
|
||||
}
|
||||
export function unblockRoomInNewbook(id: number): Promise<{ ok: boolean }> {
|
||||
return request(`/tasks/${id}/newbook-unblock`, { method: 'POST' })
|
||||
}
|
||||
export function fetchOccupancy(): Promise<{ date: string; occupied_site_ids: string[] }> {
|
||||
return request('/occupancy')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from 'react'
|
|||
import { X, Camera } from 'lucide-react'
|
||||
import type { Category, Location, Task, Priority, AppConfig, Asset } from '../types'
|
||||
import { PRIORITIES, PRIORITY_LABELS } from '../types'
|
||||
import { createTask, fetchTasks, uploadTaskPhoto, blockRoomInNewbook, fetchAssets } from '../api'
|
||||
import { createTask, fetchTasks, uploadTaskPhoto, fetchAssets } from '../api'
|
||||
import AssigneeSelect, { type Assignment } from './AssigneeSelect'
|
||||
import { PriorityBadge, StatusBadge } from './shared'
|
||||
|
||||
|
|
@ -33,7 +33,6 @@ export default function NewTaskModal({ categories, locations, config, onClose, o
|
|||
contractor_id: config?.default_contractor_id ?? null,
|
||||
})
|
||||
|
||||
const location = useMemo(() => locations.find(l => l.id === locationId), [locations, locationId])
|
||||
const locationAssets = useMemo(
|
||||
() => assets.filter(a => a.location_id === locationId),
|
||||
[assets, locationId]
|
||||
|
|
@ -75,14 +74,6 @@ export default function NewTaskModal({ categories, locations, config, onClose, o
|
|||
await uploadTaskPhoto(task.id, file, 'report').catch(() => {})
|
||||
}
|
||||
|
||||
// Explicit confirm — never block a room in NewBook silently
|
||||
if (unusable && location?.source === 'newbook') {
|
||||
const ok = window.confirm(
|
||||
`Also mark ${location.name} as out of order in NewBook (status: ${config?.newbook_block_status || 'Maintenance'}) so it can't be sold?`
|
||||
)
|
||||
if (ok) await blockRoomInNewbook(task.id).catch(err => window.alert(`NewBook block failed: ${err.message}`))
|
||||
}
|
||||
|
||||
onCreated()
|
||||
onClose()
|
||||
} catch (err) {
|
||||
|
|
@ -157,10 +148,15 @@ export default function NewTaskModal({ categories, locations, config, onClose, o
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<label className="field-check" style={{ marginBottom: 12 }}>
|
||||
<label className="field-check" style={{ marginBottom: unusable ? 4 : 12 }}>
|
||||
<input type="checkbox" checked={unusable} onChange={e => setUnusable(e.target.checked)} />
|
||||
Makes this location unusable / unsellable
|
||||
</label>
|
||||
{unusable && (
|
||||
<div className="field-hint" style={{ marginBottom: 12 }}>
|
||||
This flag is for visibility here only — mark the room out of order in NewBook as usual.
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="field-row">
|
||||
<div className="field">
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import {
|
||||
X, Camera, MessageSquare, ArrowRight, RotateCcw, PlusCircle,
|
||||
Ban, CheckCircle2, Image as ImageIcon, PoundSterling, UserRound,
|
||||
CheckCircle2, Image as ImageIcon, PoundSterling, UserRound,
|
||||
} from 'lucide-react'
|
||||
import type { TaskDetail, TaskStatus, TaskEvent, AuthUser } from '../types'
|
||||
import { STATUS_LABELS, TRANSITIONS, can } from '../types'
|
||||
import {
|
||||
fetchTask, updateTask, resolveTask, addComment, uploadTaskPhoto, deletePhoto,
|
||||
blockRoomInNewbook, unblockRoomInNewbook, photoUrl, fetchAssignableUsers,
|
||||
photoUrl, fetchAssignableUsers,
|
||||
} from '../api'
|
||||
import { useAuth } from './AuthGate'
|
||||
import AssigneeSelect, { type Assignment } from './AssigneeSelect'
|
||||
|
|
@ -22,8 +22,6 @@ function EventIcon({ type }: { type: string }) {
|
|||
case 'cost': return <PoundSterling {...props} />
|
||||
case 'reassigned': return <UserRound {...props} />
|
||||
case 'reopened': return <RotateCcw {...props} />
|
||||
case 'newbook_block': return <Ban {...props} />
|
||||
case 'newbook_unblock': return <CheckCircle2 {...props} />
|
||||
default: return <ArrowRight {...props} />
|
||||
}
|
||||
}
|
||||
|
|
@ -107,7 +105,6 @@ export default function TaskModal({ taskId, onClose, onChanged }: {
|
|||
const canResolve = can(user, 'resolve')
|
||||
const canReport = can(user, 'report')
|
||||
const showCosts = can(user, 'costs')
|
||||
const isRoom = task.location_source === 'newbook' && !!task.newbook_site_id
|
||||
|
||||
// Non-resolve transitions offered as buttons; resolve statuses open the resolve form
|
||||
const moves = (TRANSITIONS[task.status] || []).filter(s => !['temporary_fix', 'fixed'].includes(s))
|
||||
|
|
@ -135,10 +132,6 @@ export default function TaskModal({ taskId, onClose, onChanged }: {
|
|||
note: resolveNote || undefined,
|
||||
})
|
||||
if (resolveFile) await uploadTaskPhoto(task!.id, resolveFile, 'resolution').catch(() => {})
|
||||
if (task!.newbook_blocked && status === 'fixed') {
|
||||
const ok = window.confirm('This room is blocked in NewBook — release it now?')
|
||||
if (ok) await unblockRoomInNewbook(task!.id).catch(() => {})
|
||||
}
|
||||
})
|
||||
setShowResolve(null)
|
||||
}
|
||||
|
|
@ -153,7 +146,6 @@ export default function TaskModal({ taskId, onClose, onChanged }: {
|
|||
<StatusBadge status={task.status} />
|
||||
<PriorityBadge priority={task.priority} />
|
||||
{task.unusable && <UnusableBadge />}
|
||||
{task.newbook_blocked && <span className="badge badge-outline">Blocked in NewBook</span>}
|
||||
{task.template_id && <span className="badge badge-outline">Recurring</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -243,25 +235,6 @@ export default function TaskModal({ taskId, onClose, onChanged }: {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* NewBook room block */}
|
||||
{canUpdate && isRoom && task.status !== 'fixed' && (
|
||||
<div className="chip-bar">
|
||||
{task.newbook_blocked ? (
|
||||
<button className="btn btn-sm" disabled={busy} onClick={() => run(() => unblockRoomInNewbook(task.id))}>
|
||||
<CheckCircle2 size={13} strokeWidth={1.75} /> Release room in NewBook
|
||||
</button>
|
||||
) : (
|
||||
<button className="btn btn-sm" disabled={busy} onClick={() => {
|
||||
if (window.confirm(`Mark ${task.location_name} out of order in NewBook so it can't be sold?`)) {
|
||||
run(() => blockRoomInNewbook(task.id))
|
||||
}
|
||||
}}>
|
||||
<Ban size={13} strokeWidth={1.75} /> Block room in NewBook
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Resolve form */}
|
||||
{showResolve && (
|
||||
<div className="card" style={{ background: 'var(--ok-bg)' }}>
|
||||
|
|
|
|||
|
|
@ -100,24 +100,6 @@ export default function Settings() {
|
|||
</div>
|
||||
<div className="field-hint">Uses the stack SMTP settings (Settings app → integrations).</div>
|
||||
</div>
|
||||
|
||||
<div className="section-title">NewBook room blocking</div>
|
||||
<div className="card">
|
||||
<div className="field-row">
|
||||
<div className="field">
|
||||
<label>Status set when blocking a room</label>
|
||||
<input type="text" value={config.newbook_block_status} onChange={e => set({ newbook_block_status: e.target.value })} />
|
||||
</div>
|
||||
<div className="field">
|
||||
<label>Status set when releasing a room</label>
|
||||
<input type="text" value={config.newbook_unblock_status} onChange={e => set({ newbook_unblock_status: e.target.value })} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="field-hint">
|
||||
Must match NewBook site status values exactly (e.g. Maintenance, Dirty). Blocking is always an explicit
|
||||
per-task confirmation — never automatic.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,7 +115,6 @@ export default function Summary() {
|
|||
<div className="task-card-title">
|
||||
{t.title}
|
||||
{t.unusable && <UnusableBadge />}
|
||||
{t.newbook_blocked && <span className="badge badge-outline">NB blocked</span>}
|
||||
{t.template_id && <span className="badge badge-outline">Recurring</span>}
|
||||
</div>
|
||||
<div className="task-card-meta">
|
||||
|
|
|
|||
|
|
@ -76,7 +76,6 @@ export interface Task {
|
|||
priority: Priority
|
||||
status: TaskStatus
|
||||
unusable: boolean
|
||||
newbook_blocked: boolean
|
||||
hold_until: string | null
|
||||
due_date: string | null
|
||||
assigned_type: AssignedType
|
||||
|
|
@ -213,8 +212,6 @@ export interface AppConfig {
|
|||
urgent_notify_email: string
|
||||
notify_on_assign: boolean
|
||||
notify_on_urgent: boolean
|
||||
newbook_block_status: string
|
||||
newbook_unblock_status: string
|
||||
}
|
||||
|
||||
export interface AuthUser {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue