このコミットが含まれているのは:
@@ -0,0 +1,90 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
import DateTimeField from '@/components/common/DateTimeField'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { countsFieldLabel, countsFilterFields, countsOperators, readCountsJSON }
|
||||
from '@/lib/countsDefinitions'
|
||||
import useDialogue from '@/lib/dialogues/useDialogue'
|
||||
import { inputClass } from '@/lib/utils'
|
||||
import { useUITranslation } from '@/lib/useUITranslation'
|
||||
|
||||
import type { CountsResource } from '@/lib/counts'
|
||||
import type { CountsCondition } from '@/lib/countsDefinitions'
|
||||
|
||||
|
||||
const ConditionEditor = ({ resource, initial, save }: {
|
||||
resource: CountsResource; initial?: CountsCondition
|
||||
save: (condition: CountsCondition) => void },
|
||||
) => {
|
||||
const { label, message } = useUITranslation ('counts')
|
||||
const fields = countsFilterFields (resource)
|
||||
const [field, setField] = useState (initial?.field ?? 'resource_id')
|
||||
const [operator, setOperator] = useState (initial?.operator ?? 'eq')
|
||||
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)}>
|
||||
{countsOperators[fields[field]].map (key => <option key={key} value={key}>
|
||||
{message (`operators.${key}`).text}</option>)}
|
||||
</select>
|
||||
<Button type="button" onClick={() => save ({ ...initial, field, operator })}>
|
||||
{label (initial == null ? 'add' : 'shared:actions.edit')}</Button>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
const CountsAdditionalConditions = ({ resource, params, update }: {
|
||||
resource: CountsResource; params: URLSearchParams
|
||||
update: (key: string, value: string) => void },
|
||||
) => {
|
||||
const { label, message } = useUITranslation ('counts')
|
||||
const dialogue = useDialogue ()
|
||||
const conditions = readCountsJSON<CountsCondition[]> (params, 'conditions', [])
|
||||
const write = (next: CountsCondition[]) => update ('conditions', JSON.stringify (next))
|
||||
const edit = (index?: number) => {
|
||||
void dialogue.form ({ title: label ('additionalConditions'), body: controls => (
|
||||
<ConditionEditor resource={resource} initial={index == null ? undefined : conditions[index]}
|
||||
save={condition => {
|
||||
write (index == null ? [...conditions, condition]
|
||||
: conditions.map ((item, position) => position === index ? condition : item))
|
||||
controls.close ()
|
||||
}}/>) })
|
||||
}
|
||||
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 (resource)[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 => 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
|
||||
新しいイシューから参照
ユーザーをブロックする