ファイル
btrc-hub/frontend/src/components/counts/CountsAdditionalConditions.tsx
T
2026-10-26 19:31:00 +09:00

112 行
5.5 KiB
TypeScript
Raw Blame 履歴

このファイルには曖昧(ambiguous)なUnicode文字が含まれています
このファイルには、他の文字と見間違える可能性があるUnicode文字が含まれています。 それが意図的なものと考えられる場合は、この警告を無視して構いません。 それらの文字を表示するにはエスケープボタンを使用します。
import { useEffect, useState } from 'react'
import DateTimeField from '@/components/common/DateTimeField'
import { Button } from '@/components/ui/button'
import { countsEventTypes, countsFieldLabel, countsFilterFields,
readCountsJSON, useCountsDefinition }
from '@/lib/countsDefinitions'
import useDialogue from '@/lib/dialogues/useDialogue'
import { inputClass } from '@/lib/utils'
import { useUITranslation } from '@/lib/useUITranslation'
import type { DialogueFormControls } from '@/lib/dialogues/useDialogue'
import type { CountsCondition, CountsDefinition } from '@/lib/countsDefinitions'
const ConditionEditor = (
{ definition, initial, controls, save }: {
definition: CountsDefinition; initial?: CountsCondition
controls: DialogueFormControls
save: (condition: CountsCondition) => void },
) => {
const { label, message } = useUITranslation ('counts')
const fields = countsFilterFields (definition)
const [field, setField] = useState (initial?.field ?? 'resource_id')
const [operator, setOperator] = useState (initial?.operator ?? 'eq')
const operators = definition.fields.find (item => item.key === field)?.operators ?? []
useEffect (() => {
controls.setActions ([{ label: label (initial == null ? 'add' : 'shared:actions.edit'),
onSelect: () => save ({ ...initial, field, operator }) }])
}, [controls, field, operator, initial, label, save])
return <div className="space-y-2">
<select value={field} aria-label={message ('field').text} className={inputClass ()}
onChange={event => { setField (event.target.value); setOperator ('eq') }}>
{Object.keys (fields).filter (key => fields[key] !== 'json').map (key => (
<option key={key} value={key}>{message (countsFieldLabel (key)).text}</option>))}
</select>
<select value={operator} aria-label={message ('operator').text} className={inputClass ()}
onChange={event => setOperator (event.target.value)}>
{operators.map (key => <option key={key} value={key}>
{message (`operators.${key}`).text}</option>)}
</select>
</div>
}
const CountsAdditionalConditions = ({ params, update }: {
params: URLSearchParams
update: (key: string, value: string) => void },
) => {
const { label, message } = useUITranslation ('counts')
const definition = useCountsDefinition ()
const dialogue = useDialogue ()
const conditions = readCountsJSON<CountsCondition[]> (params, 'conditions', [])
const write = (next: CountsCondition[]) => update ('conditions', JSON.stringify (next))
const edit = (index?: number) => {
const initial = index == null ? undefined : conditions[index]
const save = (condition: CountsCondition) => {
write (index == null ? [...conditions, condition]
: conditions.map ((item, position) => position === index ? condition : item))
}
dialogue.form ({ title: label ('additionalConditions'), footerLayout: 'inline',
body: controls => <ConditionEditor definition={definition}
initial={initial} controls={controls} save={save}/> })
}
const patch = (index: number, change: Partial<CountsCondition>) => write (
conditions.map ((item, position) => position === index ? { ...item, ...change } : item))
return <fieldset className="space-y-1">
<legend className="flex items-center gap-2">{label ('additionalConditions')}
<Button type="button" variant="outline" size="sm" onClick={() => edit ()}>
{label ('add')}</Button></legend>
{conditions.map ((condition, index) => {
const datetime = countsFilterFields (definition)[condition.field] === 'datetime'
return <div key={index} className="flex flex-wrap items-center gap-1">
<button type="button" onClick={() => edit (index)}>
{label (countsFieldLabel (condition.field))}
{' · '}{label (`operators.${condition.operator}`)}
</button>
{(condition.operator === 'range' ? ['value', 'end'] as const : ['value'] as const)
.map (key => condition.field === 'event_type' ? (
<select key={key} value={condition[key] ?? ''} disabled={condition.blank}
className={inputClass (false, 'w-36 py-1')}
aria-label={message ('value').text}
onChange={event => patch (index, { [key]: event.target.value })}>
<option value=""/>
{condition[key] && !countsEventTypes.some (event => event === condition[key])
&& <option value={condition[key]}>{condition[key]}</option>}
{countsEventTypes.map (event => <option key={event} value={event}
lang={message (`events.${event}`).language}>
{message (`events.${event}`).text}</option>)}
</select>) : (datetime ? <DateTimeField precision="second" key={key}
value={condition[key] ?? ''} disabled={condition.blank}
aria-label={message (key === 'end' ? 'through' : 'value').text}
onChange={value => patch (index, { [key]: value ?? '' })}/> : (
<input key={key} value={condition[key] ?? ''} disabled={condition.blank}
className={inputClass (false, 'w-36 py-1')}
aria-label={message ('value').text}
onChange={event => patch (index, { [key]: event.target.value })}/>)))}
<label className="inline-flex items-center gap-1">
<input type="checkbox" checked={condition.blank ?? false}
onChange={event => patch (index, { blank: event.target.checked })}/>
{label ('blank')}</label>
<button type="button" aria-label={message ('remove').text}
onClick={() => write (conditions.filter (
(_, position) => index !== position))}>×</button>
</div>
})}
</fieldset>
}
export default CountsAdditionalConditions