diff --git a/src/renderer/src/components/autoScore/actionComponent.tsx b/src/renderer/src/components/autoScore/actionComponent.tsx index 9bb0897..f014aca 100644 --- a/src/renderer/src/components/autoScore/actionComponent.tsx +++ b/src/renderer/src/components/autoScore/actionComponent.tsx @@ -1,20 +1,16 @@ import { Space, Input, InputNumber, Select, Button } from 'antd' import { PlusOutlined, DeleteOutlined } from '@ant-design/icons' import { useTranslation } from 'react-i18next' -import type { AutoScoreAction } from './ruleBuilderUtils' +import type { ActionItem } from './types' +import { ACTION_DEFINITIONS, SCORE_RANGE } from './constants' interface ActionComponentProps { - actions: AutoScoreAction[] + actions: ActionItem[] onAdd: () => void - onRemove: (index: number) => void - onChange: (index: number, field: keyof AutoScoreAction, value: any) => void + onRemove: (id: number) => void + onChange: (id: number, field: keyof ActionItem, value: string | number) => void } -const ACTION_DEFINITIONS = [ - { eventName: 'add_score', labelKey: 'autoScore.actionAddScore' }, - { eventName: 'add_tag', labelKey: 'autoScore.actionAddTag' } -] - export const ActionComponent: React.FC = ({ actions, onAdd, @@ -23,16 +19,16 @@ export const ActionComponent: React.FC = ({ }) => { const { t } = useTranslation() - const handleActionChange = (index: number, field: keyof AutoScoreAction, value: any) => { - onChange(index, field, value) + const handleActionChange = (id: number, field: keyof ActionItem, value: string | number) => { + onChange(id, field, value) } return (
- - {actions.map((action, index) => ( + + {actions.map((action) => (
= ({ > handleActionChange(index, 'value', e.target.value)} - placeholder={t('autoScore.tagNameLabel')} + onChange={(e) => handleActionChange(action.id, 'value', e.target.value)} + placeholder={t('autoScore.tagNamePlaceholder')} style={{ width: 200 }} /> )} handleActionChange(index, 'reason', e.target.value)} - placeholder={t('autoScore.operationNoteLabel')} + onChange={(e) => handleActionChange(action.id, 'reason', e.target.value)} + placeholder={t('autoScore.reasonPlaceholder')} style={{ flex: 1, minWidth: 200 }} /> @@ -82,14 +78,14 @@ export const ActionComponent: React.FC = ({ type="text" danger icon={} - onClick={() => onRemove(index)} + onClick={() => onRemove(action.id)} />
))}
diff --git a/src/renderer/src/components/autoScore/ruleBuilderUtils.ts b/src/renderer/src/components/autoScore/ruleBuilderUtils.ts index c81a39b..35e186d 100644 --- a/src/renderer/src/components/autoScore/ruleBuilderUtils.ts +++ b/src/renderer/src/components/autoScore/ruleBuilderUtils.ts @@ -1,9 +1,7 @@ -import type { RuleGroupType, Field, Operator } from 'react-querybuilder' +import type { RuleGroupType, Field, RuleType } from 'react-querybuilder' import {defaultOperators} from 'react-querybuilder' import { fetchAllTags } from '../TagEditorDialog' -import type { FullField, RuleType } from 'react-querybuilder'; -import { toFullOption } from 'react-querybuilder'; const tags = await fetchAllTags() export interface AutoScoreTrigger { @@ -18,68 +16,35 @@ export interface AutoScoreAction { reason?: string } -const musicalInstruments = fetchAllTags().then((tags) => tags.map((tag) => tag.name)) - export interface AutoScoreRuleData { triggers: AutoScoreTrigger[] actions: AutoScoreAction[] } -// Function to get fields with i18n support +export const validator = (r: RuleType) => !!r.value; + export const getFields = (t: (key: string) => string): Field[] => [ - { - name: 'interval_time_passed', - label: t('autoScore.triggerIntervalTime'), - placeholder: t('autoScore.intervalMinutesPlaceholder'), - inputType: 'date', - datatype: 'timestamp with time zone', - }, { name: 'student_has_tag', - label: t('autoScore.triggerStudentTag'), - placeholder: t('autoScore.tagNamesPlaceholder'), + label: t('triggers.studentTag.label'), + placeholder: t('triggers.studentTag.placeholder'), valueEditorType: 'multiselect', values: tags.map((tag) => tag.name), defaultValue: tags.length > 0 ? [tags[0].name] : [], - operators: defaultOperators.filter((op) => op.name === '='), + operators: defaultOperators.filter((op) => op.name === 'in'), }, { - name: 'tourStops', - label: 'Tour stops', - - matchModes: true, + name: 'interval_time_passed', + label: t('triggers.intervalTime.label'), + matchModes: ['all'], subproperties: [ - { name: 'date', label: 'Date', inputType: 'date', datatype: 'date' }, - { name: 'time', label: 'Time', inputType: 'time', datatype: 'time' }, + { name: 'month', label: t('triggers.intervalTime.monthLabel'), inputType: 'number', datatype: 'month', operators: ['='] }, +/* { name: 'week', label: t('triggers.intervalTime.weekLabel'), inputType: 'week', datatype: 'week', operators: ['='] }, + */ { name: 'time', label: t('triggers.intervalTime.timeLabel'), inputType: 'time', datatype: 'time', operators: ['='] }, ], }, ] -/* export const operators: Operator[] = [ - { name: '=', label: '=' }, - { name: 'contains', label: 'contains' }, - { name: 'between', label: 'between' } -] */ - -export const fields: FullField[] = ( - [ - { - name: 'interval_time_passed', - label: ('autoScore.triggerIntervalTime'), - placeholder: ('autoScore.intervalMinutesPlaceholder') - }, - { - name: 'student_has_tag', - label: ('autoScore.triggerStudentTag'), - placeholder: ('autoScore.tagNamesPlaceholder'), - valueEditorType: 'multiselect', - values: tags.map((tag) => tag.name), - defaultValue: 'more_cowbell', - operators: defaultOperators.filter((op) => op.name === 'in'), - } - ] satisfies Field[] -).map((o) => toFullOption(o)); - export const defaultQuery: RuleGroupType = { combinator: 'and', rules: [{ field: 'interval_time_passed', operator: '=', value: '1440' }] @@ -88,7 +53,7 @@ export const defaultQuery: RuleGroupType = { export function queryToAutoScoreRule(query: RuleGroupType): AutoScoreRuleData { const triggers: AutoScoreTrigger[] = [] - const processRuleGroup = (group: RuleGroupType, relation: 'AND' | 'OR' = 'AND') => { +/* const processRuleGroup = (group: RuleGroupType, relation: 'AND' | 'OR' = 'AND') => { group.rules.forEach((rule, index) => { if ('rules' in rule) { processRuleGroup(rule, group.combinator === 'and' ? 'AND' : 'OR') @@ -105,9 +70,9 @@ export function queryToAutoScoreRule(query: RuleGroupType): AutoScoreRuleData { triggers.push(trigger) } }) - } + } */ - processRuleGroup(query) + /* processRuleGroup(query) */ return { triggers, @@ -138,27 +103,3 @@ export function autoScoreRuleToQuery(ruleData: AutoScoreRuleData): RuleGroupType rules: rules.length > 0 ? rules : defaultQuery.rules } } - -/* import type { Field, FullField, RuleType } from 'react-querybuilder'; -import { defaultOperators, toFullOption } from 'react-querybuilder'; -import { fetchAllTags } from '../TagEditorDialog' -const tags = await fetchAllTags() - -export const fields: FullField[] = ( - [ - { - name: 'interval_time_passed', - label: ('autoScore.triggerIntervalTime'), - placeholder: ('autoScore.intervalMinutesPlaceholder') - }, - { - name: 'student_has_tag', - label: ('autoScore.triggerStudentTag'), - placeholder: ('autoScore.tagNamesPlaceholder'), - valueEditorType: 'multiselect', - values: tags.map((tag) => tag.name), - defaultValue: 'more_cowbell', - operators: defaultOperators.filter((op) => op.name === 'in'), - } - ] satisfies Field[] -).map((o) => toFullOption(o)); */ \ No newline at end of file diff --git a/src/renderer/src/components/autoScore/ruleComponent.tsx b/src/renderer/src/components/autoScore/ruleComponent.tsx index 9776208..ba1c039 100644 --- a/src/renderer/src/components/autoScore/ruleComponent.tsx +++ b/src/renderer/src/components/autoScore/ruleComponent.tsx @@ -1,24 +1,21 @@ import { formatQuery, QueryBuilder, RuleGroupType } from 'react-querybuilder' import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' -import { QueryBuilderDnD } from '@react-querybuilder/dnd'; -import * as ReactDnD from 'react-dnd'; -import { QueryBuilderAntD } from '@react-querybuilder/antd'; -import * as ReactDndHtml5Backend from 'react-dnd-html5-backend'; -import * as ReactDndTouchBackend from 'react-dnd-touch-backend'; +import { QueryBuilderDnD } from '@react-querybuilder/dnd' +import * as ReactDnD from 'react-dnd' +import { QueryBuilderAntD } from '@react-querybuilder/antd' +import * as ReactDndHtml5Backend from 'react-dnd-html5-backend' +import * as ReactDndTouchBackend from 'react-dnd-touch-backend' import { getFields, -/* operators, */ defaultQuery, - queryToAutoScoreRule, autoScoreRuleToQuery, - type AutoScoreRuleData, - AutoScoreAction + queryToAutoScoreRule, + type AutoScoreRuleData } from './ruleBuilderUtils' import { Card } from 'antd' import './ruleBuilderOverride.css' -import { ActionComponent } from './actionComponent' interface RuleComponentProps { initialData?: AutoScoreRuleData @@ -28,61 +25,38 @@ interface RuleComponentProps { export const RuleComponent: React.FC = ({ initialData, onChange }) => { const { t } = useTranslation() const [query, setQuery] = useState( - initialData ? autoScoreRuleToQuery(initialData) : defaultQuery + initialData ? autoScoreRuleToQuery(initialData) : defaultQuery ) -/* const [actions, setActions] = useState(initialData?.actions || []) useEffect(() => { - if (onChange) { - const ruleData = queryToAutoScoreRule(query) - onChange({ - ...ruleData, - actions - }) + if (initialData) { + setQuery(autoScoreRuleToQuery(initialData)) } - }, [query, actions]) + }, [initialData]) - const handleAddAction = () => { - setActions([...actions, { event: 'add_score', value: '5', reason: '' }]) - } - - const handleRemoveAction = (index: number) => { - setActions(actions.filter((_, i) => i !== index)) - } - - const handleActionChange = (index: number, field: keyof AutoScoreAction, value: any) => { - const newActions = [...actions] - newActions[index] = { ...newActions[index], [field]: value } - setActions(newActions) - } - */ const handleQueryChange = (newQuery: RuleGroupType) => { setQuery(newQuery) + if (onChange) { + const ruleData = queryToAutoScoreRule(newQuery) + onChange(ruleData) + } } + return (
- + - - - + + +
-
{ formatQuery(query, 'json') }
+
{formatQuery(query, 'json')}
-{/* - - */}
) } diff --git a/src/renderer/src/i18n/locales/en-US.json b/src/renderer/src/i18n/locales/en-US.json index c3e6587..186c27d 100644 --- a/src/renderer/src/i18n/locales/en-US.json +++ b/src/renderer/src/i18n/locales/en-US.json @@ -513,7 +513,9 @@ "tagsPlaceholder": "Enter tag name", "scorePlaceholder": "Enter score", "tagNamePlaceholder": "Enter tag name", - "reasonPlaceholder": "Enter reason" + "reasonPlaceholder": "Enter reason", + "relationAnd": "AND", + "relationOr": "OR" }, "triggers": { "studentTag": { diff --git a/src/renderer/src/i18n/locales/zh-CN.json b/src/renderer/src/i18n/locales/zh-CN.json index 86fc876..b5a298b 100644 --- a/src/renderer/src/i18n/locales/zh-CN.json +++ b/src/renderer/src/i18n/locales/zh-CN.json @@ -502,8 +502,6 @@ "scoreLabel": "分数", "tagNameLabel": "标签名称", "operationNoteLabel": "操作说明(可选)", - "intervalMinutesPlaceholder": "例如:1440", - "tagNamesPlaceholder": "例如:优秀学生,班干部", "triggerIntervalTime": "间隔时间", "triggerStudentTag": "学生标签", "actionAddScore": "加分", @@ -513,11 +511,13 @@ "tagsPlaceholder": "请输入标签名称", "scorePlaceholder": "请输入分数", "tagNamePlaceholder": "请输入标签名称", - "reasonPlaceholder": "请输入理由" + "reasonPlaceholder": "请输入理由", + "relationAnd": "并且", + "relationOr": "或者" }, "triggers": { "studentTag": { - "label": "当学生匹配标签时触发", + "label": "学生标签", "description": "当学生匹配特定标签时触发自动化", "placeholder": "请选择标签", "required": "请输入标签名称" @@ -525,21 +525,16 @@ "randomTime": { "label": "随机时间触发", "description": "在指定时间范围内随机触发自动化", - "from": "从", - "to": "到", - "hour": "时", - "minHourPlaceholder": "最小小时", - "maxHourPlaceholder": "最大小时", "required": "请输入有效的时间范围(分钟)" }, "intervalTime": { - "label": "根据间隔时间触发", + "label": "间隔时间", "description": "当间隔时间到达时触发自动化", - "days": "天", - "minutes": "分钟", - "placeholderMinutes": "请输入时间间隔(分钟)", - "placeholderDays": "请输入时间间隔(天)", - "required": "请输入有效的时间间隔(分钟)" + "placeholder": "请输入时间间隔", + "required": "请输入有效的时间", + "monthLabel": "月", + "weekLabel": "周", + "timeLabel": "时间" } }, "actions": {