このコミットが含まれているのは:
2026-10-27 19:54:31 +09:00
コミット 4f117c7ba3
7個のファイルの変更、366行の追加、127行の削除
+64 -45
ファイルの表示
@@ -71,69 +71,88 @@ const CountsAdditionalConditions = ({ params, update }: {
const dialogue = useDialogue ()
const conditions = readCountState<CountsCondition[]> (params, 'conditions', [])
const metadata = definition.filterFields ?? definition.fields
const fields = metadata.filter (field => field.operators.length > 0)
const fieldName = (key: string) => (metadata.find (item => item.key === key)?.labels
?? [countsFieldLabel (key)]).map (key => message (key).text).join (' › ')
const write = (next: CountsCondition[]) => update ('conditions', encodeCountState ('conditions', next))
const write = (next: CountsCondition[]) => update (
'conditions', encodeCountState ('conditions', next))
const replace = (index: number, condition: CountsCondition) => write (
conditions.map ((item, position) => position === index ? condition : item))
const patch = (index: number, change: Partial<CountsCondition>) =>
replace (index, { ...conditions[index], ...change })
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))
if (index == null) write ([...conditions, condition])
else replace (index, condition)
}
dialogue.form ({ title: label ('additionalConditions'), footerLayout: 'inline',
body: controls => <ConditionEditor definition={definition}
initial={initial} controls={controls} save={save}/> })
void 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))
const valueInput = (condition: CountsCondition, index: number, key: 'value' | 'end') => {
const type = countsFilterFields (definition)[condition.field]
const aria = message (key === 'end' ? 'through' : 'value')
const aria = message (key === 'end' ? 'through'
: (condition.operator === 'range' ? 'from' : 'value'))
if (type === 'boolean' || condition.field === 'event_type')
{
const options: string[] = type === 'boolean' ? ['true', 'false'] : [...countsEventTypes]
const namespace = type === 'boolean' ? 'truthValues' : 'events'
return <select key={key} value={condition[key] ?? ''} disabled={condition.blank}
className={inputClass (false, 'w-36 py-1')} aria-label={aria.text}
onChange={event => patch (index, { [key]: event.target.value })}>
<option value=""/>
{options.map (value => <option key={value} value={value}
lang={message (`${namespace}.${value}`).language}>
{message (`${namespace}.${value}`).text}</option>)}
return <select value={condition[key] ?? ''} disabled={condition.blank}
className={inputClass (false, 'h-7 w-36 min-w-0 max-w-full py-1')}
aria-label={aria.text}
lang={aria.language}
onChange={event => patch (index, { [key]: event.target.value })}>
<option value=""/>
{options.map (value => <option key={value} value={value}
lang={message (`${namespace}.${value}`).language}>
{message (`${namespace}.${value}`).text}</option>)}
</select>
}
if (type === 'datetime')
return <DateTimeField precision="second" key={key}
value={condition[key] ?? ''} disabled={condition.blank}
aria-label={aria.text} lang={aria.language}
onChange={value => patch (index, { [key]: value ?? '' })}/>
return <input key={key} value={condition[key] ?? ''} disabled={condition.blank}
type={(type === 'integer' || type === 'interval') ? 'number' : 'text'}
className={inputClass (false, 'w-36 py-1')}
aria-label={aria.text} lang={aria.language}
onChange={event => patch (index, { [key]: event.target.value })}/>
return <DateTimeField precision="second" className="h-7 w-48 min-w-0 max-w-full py-1"
value={condition[key] ?? ''} disabled={condition.blank}
aria-label={aria.text} lang={aria.language}
onChange={value => patch (index, { [key]: value ?? '' })}/>
return <input value={condition[key] ?? ''} disabled={condition.blank}
type={(type === 'integer' || type === 'interval') ? 'number' : 'text'}
className={inputClass (false, 'h-7 w-36 min-w-0 max-w-full py-1')}
aria-label={aria.text} lang={aria.language}
onChange={event => patch (index, { [key]: event.target.value })}/>
}
return <fieldset className="space-y-1">
return <fieldset className="min-w-0 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) => {
return <div key={index} className="flex flex-wrap items-center gap-1">
<button type="button" onClick={() => edit (index)}>
{fieldName (condition.field)}
{' · '}{label (`operators.${condition.operator}`)}
</button>
{(condition.operator === 'range' ? ['value', 'end'] as const : ['value'] as const)
.map (key => valueInput (condition, index, key))}
<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>
})}
<Button type="button" variant="outline" size="sm" disabled={fields.length === 0}
onClick={() => edit ()}>{label ('add')}</Button></legend>
{conditions.map ((condition, index) => (
<div key={index} className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1
border-b border-slate-200 py-1 dark:border-slate-700">
<Button type="button" variant="ghost" size="sm" onClick={() => edit (index)}
aria-label={`${fieldName (condition.field)} ${message (
`operators.${condition.operator}`).text}`}
className="h-7 min-w-0 max-w-full justify-start gap-2 whitespace-normal px-1 text-left">
<span className="min-w-0 break-words">{fieldName (condition.field)}</span>
{condition.operator !== 'range' && <span aria-hidden="true" className="shrink-0">
{label (`operatorSymbols.${condition.operator}`)}</span>}
</Button>
<div className="inline-flex min-w-0 max-w-full items-center gap-1">
{valueInput (condition, index, 'value')}
{condition.operator === 'range' && <>
<span aria-hidden="true" className="shrink-0">{label ('operatorSymbols.range')}</span>
{valueInput (condition, index, 'end')}
</>}
</div>
<div className="inline-flex shrink-0 items-center gap-1">
<label className="inline-flex items-center gap-1 text-sm">
<input type="checkbox" checked={condition.blank ?? false}
onChange={event => patch (index, { blank: event.target.checked })}/>
{label ('blank')}</label>
<Button type="button" variant="ghost" size="sm" className="h-7 px-1"
aria-label={message ('remove').text}
onClick={() => write (conditions.filter ((_, position) => index !== position))}>
×</Button>
</div>
</div>))}
</fieldset>
}