M8-R13: simplify prices and meter controls
This commit is contained in:
@@ -167,6 +167,34 @@ function recoveryTargetFor(meter: MeterResponse, meters: MeterResponse[]): Meter
|
||||
return target
|
||||
}
|
||||
|
||||
function BindingTimeline({ binding, sources }: {
|
||||
binding: NonNullable<MeterResponse['bindings']>[number]
|
||||
sources: ReturnType<typeof useSources>
|
||||
}) {
|
||||
const source = sources.data?.items.find((item) => item.uuid === binding.source_uuid)
|
||||
const channels = useSourceChannels(source?.uuid ?? null)
|
||||
const channel = channels.data?.items.find((item) => item.uuid === binding.source_channel_uuid)
|
||||
let endpoint = 'Source details unavailable'
|
||||
if (sources.isLoading) endpoint = 'Loading source details…'
|
||||
else if (!sources.isError && source) endpoint = source.name
|
||||
else if (!sources.isError) endpoint = 'Source unavailable'
|
||||
|
||||
let channelName = 'Channel details unavailable'
|
||||
if (source && channels.isLoading) channelName = 'Loading channel details…'
|
||||
else if (source && !channels.isError && channel) channelName = channel.label
|
||||
else if (source && !channels.isError) channelName = 'Channel unavailable'
|
||||
|
||||
return (
|
||||
<Stack gap={0} mb="xs" data-testid={`binding-timeline-${binding.uuid}`}>
|
||||
<Text size="xs">{endpoint} → {channelName}</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
[{formatLocalDateTime(binding.started_at)}, {binding.ended_at ? formatLocalDateTime(binding.ended_at) : 'open-ended'})
|
||||
{' '}({binding.ended_at ? 'closed' : 'active'})
|
||||
</Text>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Declare meter form (modal)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -515,11 +543,12 @@ function EditMeterForm({ meter, onClose, onSaved }: EditMeterFormProps) {
|
||||
|
||||
interface MeterTableProps {
|
||||
meters: MeterResponse[]
|
||||
sources: ReturnType<typeof useSources>
|
||||
onEdit: (meter: MeterResponse) => void
|
||||
onClose: (meter: MeterResponse) => void
|
||||
}
|
||||
|
||||
function MeterTable({ meters, onEdit, onClose }: MeterTableProps) {
|
||||
function MeterTable({ meters, sources, onEdit, onClose }: MeterTableProps) {
|
||||
if (meters.length === 0) {
|
||||
return (
|
||||
<Text c="dimmed" ta="center" size="sm" data-testid="meters-empty">
|
||||
@@ -585,30 +614,24 @@ function MeterTable({ meters, onEdit, onClose }: MeterTableProps) {
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
{meter.bindings?.length ? meter.bindings.map((binding) => (
|
||||
<Stack key={binding.uuid} gap={0} mb="xs" data-testid={`binding-timeline-${binding.uuid}`}>
|
||||
<Text size="xs">{binding.source_uuid} → {binding.source_channel_uuid}</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
[{formatLocalDateTime(binding.started_at)}, {binding.ended_at ? formatLocalDateTime(binding.ended_at) : 'open-ended'})
|
||||
{' '}({binding.ended_at ? 'closed' : 'active'})
|
||||
</Text>
|
||||
</Stack>
|
||||
<BindingTimeline key={binding.uuid} binding={binding} sources={sources} />
|
||||
)) : <Text size="xs" c="dimmed">Unbound</Text>}
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Group justify="flex-end" gap="xs">
|
||||
{meter.bindings?.filter((binding) => binding.ended_at === null).map((binding) => (
|
||||
<BindingActions key={binding.uuid} meter={meter} binding={binding} meters={meters} />
|
||||
))}
|
||||
{isActive && !meter.bindings?.some((binding) => binding.ended_at === null) && <DirectBindButton meter={meter} meters={meters} />}
|
||||
{isActive && <Button size="xs" color="red" variant="light" onClick={() => onClose(meter)}>Close meter</Button>}
|
||||
<Button
|
||||
size="xs"
|
||||
variant="outline"
|
||||
variant="light"
|
||||
onClick={() => onEdit(meter)}
|
||||
data-testid={`meter-edit-${meter.id}`}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
{meter.bindings?.filter((binding) => binding.ended_at === null).map((binding) => (
|
||||
<BindingActions key={binding.uuid} meter={meter} binding={binding} meters={meters} />
|
||||
))}
|
||||
{isActive && !meter.bindings?.some((binding) => binding.ended_at === null) && <DirectBindButton meter={meter} meters={meters} />}
|
||||
{isActive && <Button size="xs" color="red" variant="light" onClick={() => onClose(meter)}>Close meter</Button>}
|
||||
</Group>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
@@ -622,7 +645,7 @@ function MeterTable({ meters, onEdit, onClose }: MeterTableProps) {
|
||||
|
||||
function DirectBindButton({ meter, meters }: { meter: MeterResponse; meters: MeterResponse[] }) {
|
||||
const [opened, setOpened] = useState(false)
|
||||
return <>{<Button size="xs" variant="subtle" onClick={() => setOpened(true)}>Bind source</Button>}{opened && <DirectBindModal meter={meter} meters={meters} onClose={() => setOpened(false)} />}</>
|
||||
return <>{<Button size="xs" variant="light" onClick={() => setOpened(true)}>Bind source</Button>}{opened && <DirectBindModal meter={meter} meters={meters} onClose={() => setOpened(false)} />}</>
|
||||
}
|
||||
|
||||
function DirectBindModal({ meter, meters, onClose }: { meter: MeterResponse; meters: MeterResponse[]; onClose: () => void }) {
|
||||
@@ -676,8 +699,8 @@ function BindingActions({ meter, binding, meters }: { meter: MeterResponse; bind
|
||||
const recoverySourceIsClosable = meter.ended_at === null ||
|
||||
parseBackendTimestamp(binding.started_at).getTime() < parseBackendTimestamp(meter.ended_at).getTime()
|
||||
return <>
|
||||
<Button size="xs" variant="subtle" onClick={() => setUnbindOpened(true)}>Unbind</Button>
|
||||
{meter.ended_at === null ? <Button size="xs" variant="subtle" onClick={() => setTransferOpened(true)}>Transfer source</Button> : recoveryTarget && recoverySourceIsClosable && <Button size="xs" variant="light" onClick={() => setTransferOpened(true)}>Recover binding</Button>}
|
||||
{meter.ended_at === null ? <Button size="xs" variant="light" onClick={() => setTransferOpened(true)}>Transfer source</Button> : recoveryTarget && recoverySourceIsClosable && <Button size="xs" variant="light" onClick={() => setTransferOpened(true)}>Recover binding</Button>}
|
||||
<Button size="xs" variant="light" onClick={() => setUnbindOpened(true)}>Unbind</Button>
|
||||
{meter.ended_at !== null && recoveryTarget && !recoverySourceIsClosable && <Text size="xs" c="red">Cannot recover: the source binding starts at or after this Meter ended.</Text>}
|
||||
{unbindOpened && <UnbindModal meter={meter} binding={binding} onClose={() => setUnbindOpened(false)} />}
|
||||
{transferOpened && <TransferModal target={recoveryTarget ?? meter} sourceBinding={binding} meters={meters} recovery={!!recoveryTarget} onClose={() => setTransferOpened(false)} />}
|
||||
@@ -793,6 +816,7 @@ function CloseMeterModal({ meter, onClose }: { meter: MeterResponse; onClose: ()
|
||||
|
||||
export function MeterManager() {
|
||||
const metersQuery = useMeters()
|
||||
const sources = useSources()
|
||||
|
||||
const [showDeclareForm, setShowDeclareForm] = useState(false)
|
||||
const [editMeter, setEditMeter] = useState<MeterResponse | null>(null)
|
||||
@@ -845,7 +869,7 @@ export function MeterManager() {
|
||||
</Notification>
|
||||
)}
|
||||
|
||||
<MeterTable meters={meters} onEdit={(m) => setEditMeter(m)} onClose={(m) => setCloseMeter(m)} />
|
||||
<MeterTable meters={meters} sources={sources} onEdit={(m) => setEditMeter(m)} onClose={(m) => setCloseMeter(m)} />
|
||||
|
||||
{/* Declare new meter */}
|
||||
{showDeclareForm && (
|
||||
|
||||
Reference in New Issue
Block a user