feat: translate automations

This commit is contained in:
Abhinav Raut
2025-03-31 16:31:17 +05:30
parent 9d18d3d08d
commit 497b54fc49
9 changed files with 193 additions and 85 deletions

View File

@@ -16,7 +16,7 @@
@update:modelValue="(value) => handleFieldChange(value, index)" @update:modelValue="(value) => handleFieldChange(value, index)"
> >
<SelectTrigger class="m-auto"> <SelectTrigger class="m-auto">
<SelectValue placeholder="Select action" /> <SelectValue :placeholder="t('form.field.selectAction')" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
@@ -40,7 +40,7 @@
<SelectTag <SelectTag
v-model="action.value" v-model="action.value"
:items="tagsStore.tagNames.map((tag) => ({ label: tag, value: tag }))" :items="tagsStore.tagNames.map((tag) => ({ label: tag, value: tag }))"
placeholder="Select tag" :placeholder="t('form.field.selectTag')"
/> />
</div> </div>
@@ -51,7 +51,7 @@
<ComboBox <ComboBox
v-model="action.value[0]" v-model="action.value[0]"
:items="conversationActions[action.type]?.options" :items="conversationActions[action.type]?.options"
placeholder="Select" :placeholder="t('form.field.select')"
@select="handleValueChange($event, index)" @select="handleValueChange($event, index)"
> >
<template #item="{ item }"> <template #item="{ item }">
@@ -75,7 +75,7 @@
{{ selected.emoji }} {{ selected.emoji }}
<span>{{ selected.label }}</span> <span>{{ selected.label }}</span>
</div> </div>
<span v-else>Select team</span> <span v-else>{{ $t('form.field.selectTeam') }}</span>
</div> </div>
<div v-else-if="action.type === 'assign_user'" class="flex items-center gap-2"> <div v-else-if="action.type === 'assign_user'" class="flex items-center gap-2">
@@ -91,10 +91,10 @@
</Avatar> </Avatar>
<span>{{ selected.label }}</span> <span>{{ selected.label }}</span>
</div> </div>
<span v-else>Select user</span> <span v-else>{{ $t('form.field.selectUser') }}</span>
</div> </div>
<span v-else> <span v-else>
<span v-if="!selected"> Select</span> <span v-if="!selected"> {{ $t('form.field.select') }}</span>
<span v-else>{{ selected.label }} </span> <span v-else>{{ selected.label }} </span>
</span> </span>
</template> </template>
@@ -114,14 +114,18 @@
<Editor <Editor
v-model:htmlContent="action.value[0]" v-model:htmlContent="action.value[0]"
@update:htmlContent="(value) => handleEditorChange(value, index)" @update:htmlContent="(value) => handleEditorChange(value, index)"
:placeholder="'Shift + Enter to add new line'" :placeholder="t('admin.macro.message_content.placeholder')"
/> />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div> <div>
<Button variant="outline" @click.prevent="addAction">Add action</Button> <Button variant="outline" @click.prevent="addAction">{{
$t('globals.messages.add', {
name: $t('globals.entities.action')
})
}}</Button>
</div> </div>
</div> </div>
</template> </template>
@@ -144,6 +148,7 @@ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { SelectTag } from '@/components/ui/select' import { SelectTag } from '@/components/ui/select'
import { useConversationFilters } from '@/composables/useConversationFilters' import { useConversationFilters } from '@/composables/useConversationFilters'
import { getTextFromHTML } from '@/utils/strings.js' import { getTextFromHTML } from '@/utils/strings.js'
import { useI18n } from 'vue-i18n'
import Editor from '@/features/conversation/ConversationTextEditor.vue' import Editor from '@/features/conversation/ConversationTextEditor.vue'
const props = defineProps({ const props = defineProps({
@@ -154,6 +159,7 @@ const props = defineProps({
}) })
const { actions } = toRefs(props) const { actions } = toRefs(props)
const { t } = useI18n()
const emit = defineEmits(['update-actions', 'add-action', 'remove-action']) const emit = defineEmits(['update-actions', 'add-action', 'remove-action'])
const tagsStore = useTagStore() const tagsStore = useTagStore()
const { conversationActions } = useConversationFilters() const { conversationActions } = useConversationFilters()

View File

@@ -1,26 +1,38 @@
<template> <template>
<Tabs default-value="new_conversation" v-model="selectedTab"> <Tabs default-value="new_conversation" v-model="selectedTab">
<TabsList class="grid w-full grid-cols-3 mb-5"> <TabsList class="grid w-full grid-cols-3 mb-5">
<TabsTrigger value="new_conversation">New conversation</TabsTrigger> <TabsTrigger value="new_conversation">{{
<TabsTrigger value="conversation_update">Conversation update</TabsTrigger> $t('admin.automation.newConversation')
<TabsTrigger value="time_trigger">Time triggers</TabsTrigger> }}</TabsTrigger>
<TabsTrigger value="conversation_update">{{
$t('admin.automation.conversationUpdate')
}}</TabsTrigger>
<TabsTrigger value="time_trigger">{{ $t('admin.automation.timeTriggers') }}</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="new_conversation"> <TabsContent value="new_conversation">
<RuleTab type="new_conversation" helptext="Rules that run when a new conversation is created, drag and drop to reorder rules." /> <RuleTab
type="new_conversation"
:helptext="t('admin.automation.newConversation.description')"
/>
</TabsContent> </TabsContent>
<TabsContent value="conversation_update"> <TabsContent value="conversation_update">
<RuleTab type="conversation_update" helptext="Rules that run when a conversation is updated." /> <RuleTab
type="conversation_update"
:helptext="t('admin.automation.conversationUpdate.description')"
/>
</TabsContent> </TabsContent>
<TabsContent value="time_trigger"> <TabsContent value="time_trigger">
<RuleTab type="time_trigger" helptext="Rules that run once an hour." /> <RuleTab type="time_trigger" :helptext="t('admin.automation.timeTriggers.description')" />
</TabsContent> </TabsContent>
</Tabs> </Tabs>
</template> </template>
<script setup> <script setup>
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { useI18n } from 'vue-i18n'
import RuleTab from './RuleTab.vue' import RuleTab from './RuleTab.vue'
const { t } = useI18n()
const selectedTab = defineModel('automationsTab', { const selectedTab = defineModel('automationsTab', {
default: 'new_conversation', default: 'new_conversation',
type: String, type: String,

View File

@@ -8,11 +8,17 @@
> >
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<RadioGroupItem value="OR" /> <RadioGroupItem value="OR" />
<Label>Match <b>ANY</b> of below.</Label> <Label
>{{ $t('admin.automation.match') }} <b>{{ $t('admin.automation.any') }}</b>
{{ $t('admin.automation.below') }}.</Label
>
</div> </div>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<RadioGroupItem value="AND" /> <RadioGroupItem value="AND" />
<Label>Match <b>ALL</b> of below.</Label> <Label
>{{ $t('admin.automation.match') }} <b>{{ $t('admin.automation.all') }}</b>
{{ $t('admin.automation.below') }}.</Label
>
</div> </div>
</RadioGroup> </RadioGroup>
</div> </div>
@@ -31,11 +37,11 @@
@update:modelValue="(value) => handleFieldChange(value, index)" @update:modelValue="(value) => handleFieldChange(value, index)"
> >
<SelectTrigger class="w-56"> <SelectTrigger class="w-56">
<SelectValue placeholder="Select field" /> <SelectValue :placeholder="t('form.field.selectField')" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
<SelectLabel>Conversation</SelectLabel> <SelectLabel>{{ $t('globals.entities.conversation') }}</SelectLabel>
<SelectItem v-for="(field, key) in currentFilters" :key="key" :value="key"> <SelectItem v-for="(field, key) in currentFilters" :key="key" :value="key">
{{ field.label }} {{ field.label }}
</SelectItem> </SelectItem>
@@ -49,7 +55,7 @@
@update:modelValue="(value) => handleOperatorChange(value, index)" @update:modelValue="(value) => handleOperatorChange(value, index)"
> >
<SelectTrigger class="w-56"> <SelectTrigger class="w-56">
<SelectValue placeholder="Select operator" /> <SelectValue :placeholder="t('form.field.selectOperator')" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
@@ -69,7 +75,7 @@
<!-- Plain text input --> <!-- Plain text input -->
<Input <Input
type="text" type="text"
placeholder="Set value" :placeholder="t('form.fields.setValue')"
v-if="inputType(index) === 'text'" v-if="inputType(index) === 'text'"
v-model="rule.value" v-model="rule.value"
@update:modelValue="(value) => handleValueChange(value, index)" @update:modelValue="(value) => handleValueChange(value, index)"
@@ -78,7 +84,7 @@
<!-- Number input --> <!-- Number input -->
<Input <Input
type="number" type="number"
placeholder="Set value" :placeholder="t('form.fields.setValue')"
v-if="inputType(index) === 'number'" v-if="inputType(index) === 'number'"
v-model="rule.value" v-model="rule.value"
@update:modelValue="(value) => handleValueChange(value, index)" @update:modelValue="(value) => handleValueChange(value, index)"
@@ -112,7 +118,7 @@
{{ selected.emoji }} {{ selected.emoji }}
<span>{{ selected.label }}</span> <span>{{ selected.label }}</span>
</div> </div>
<span v-else>Select team</span> <span v-else>{{ $t('form.field.selectTeam') }}</span>
</div> </div>
<div <div
@@ -131,10 +137,10 @@
</Avatar> </Avatar>
<span>{{ selected.label }}</span> <span>{{ selected.label }}</span>
</div> </div>
<span v-else>Select user</span> <span v-else>{{ $t('form.field.selectUser') }}</span>
</div> </div>
<span v-else> <span v-else>
<span v-if="!selected"> Select</span> <span v-if="!selected"> {{ $t('form.field.select') }}</span>
<span v-else>{{ selected.label }} </span> <span v-else>{{ selected.label }} </span>
</span> </span>
</template> </template>
@@ -155,9 +161,11 @@
<TagsInputItemText /> <TagsInputItemText />
<TagsInputItemDelete /> <TagsInputItemDelete />
</TagsInputItem> </TagsInputItem>
<TagsInputInput placeholder="Select values" /> <TagsInputInput :placeholder="t('form.field.selectValue')" />
</TagsInput> </TagsInput>
<p class="text-xs text-gray-500 mt-1">Press enter to select a value</p> <p class="text-xs text-gray-500 mt-1">
{{ $t('globals.messages.pressEnterToSelectAValue') }}
</p>
</div> </div>
</div> </div>
@@ -173,12 +181,18 @@
:defaultChecked="rule.case_sensitive_match" :defaultChecked="rule.case_sensitive_match"
@update:checked="(value) => handleCaseSensitiveCheck(value, index)" @update:checked="(value) => handleCaseSensitiveCheck(value, index)"
/> />
<label> Case sensitive match </label> <label> {{ $t('globals.messages.caseSensitiveMatch') }} </label>
</div> </div>
</div> </div>
</div> </div>
<div> <div>
<Button variant="outline" size="sm" @click.prevent="addCondition">Add condition</Button> <Button variant="outline" size="sm" @click.prevent="addCondition">
{{
$t('globals.messages.add', {
name: $t('globals.entities.condition')
})
}}
</Button>
</div> </div>
</div> </div>
</div> </div>
@@ -210,6 +224,7 @@ import { Label } from '@/components/ui/label'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import ComboBox from '@/components/ui/combobox/ComboBox.vue' import ComboBox from '@/components/ui/combobox/ComboBox.vue'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { useI18n } from 'vue-i18n'
import { useConversationFilters } from '@/composables/useConversationFilters' import { useConversationFilters } from '@/composables/useConversationFilters'
const props = defineProps({ const props = defineProps({
@@ -230,6 +245,7 @@ const props = defineProps({
const { conversationFilters, newConversationFilters } = useConversationFilters() const { conversationFilters, newConversationFilters } = useConversationFilters()
const { ruleGroup } = toRefs(props) const { ruleGroup } = toRefs(props)
const emit = defineEmits(['update-group', 'add-condition', 'remove-condition']) const emit = defineEmits(['update-group', 'add-condition', 'remove-condition'])
const { t } = useI18n()
// Computed property to get the correct filters based on type // Computed property to get the correct filters based on type
const currentFilters = computed(() => { const currentFilters = computed(() => {

View File

@@ -7,8 +7,8 @@
{{ rule.name }} {{ rule.name }}
</div> </div>
<div class="mb-1"> <div class="mb-1">
<Badge v-if="rule.enabled" class="text-[9px]">Enabled</Badge> <Badge v-if="rule.enabled" class="text-[9px]">{{ $t('form.field.enabled') }}</Badge>
<Badge v-else variant="secondary">Disabled</Badge> <Badge v-else variant="secondary">{{ $t('form.field.disabled') }}</Badge>
</div> </div>
</span> </span>
</div> </div>
@@ -21,16 +21,16 @@
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent>
<DropdownMenuItem @click="navigateToEditRule(rule.id)"> <DropdownMenuItem @click="navigateToEditRule(rule.id)">
<span>Edit</span> <span>{{ $t('globals.buttons.edit') }}</span>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem @click="() => (alertOpen = true)"> <DropdownMenuItem @click="() => (alertOpen = true)">
<span>Delete</span> <span>{{ $t('globals.buttons.delete') }}</span>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem @click="$emit('toggle-rule', rule.id)" v-if="rule.enabled"> <DropdownMenuItem @click="$emit('toggle-rule', rule.id)" v-if="rule.enabled">
<span>Disable</span> <span>{{ $t('globals.buttons.disable') }}</span>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem @click="$emit('toggle-rule', rule.id)" v-else> <DropdownMenuItem @click="$emit('toggle-rule', rule.id)" v-else>
<span>Enable</span> <span>{{ $t('globals.buttons.enable') }}</span>
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
@@ -42,14 +42,16 @@
<AlertDialog :open="alertOpen" @update:open="alertOpen = $event"> <AlertDialog :open="alertOpen" @update:open="alertOpen = $event">
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>Delete Rule</AlertDialogTitle> <AlertDialogTitle>{{ $t('globals.messages.areYouAbsolutelySure') }}</AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
This action cannot be undone. This will permanently delete the automation rule. {{ $t('admin.automation.deleteConfirmation') }}
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogCancel>{{ $t('globals.buttons.cancel') }}</AlertDialogCancel>
<AlertDialogAction @click="handleDelete">Delete</AlertDialogAction> <AlertDialogAction @click="handleDelete">{{
$t('globals.buttons.delete')
}}</AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>

View File

@@ -15,8 +15,10 @@
}}</SelectValue> }}</SelectValue>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="first_match">Execute the first matching rule</SelectItem> <SelectItem value="first_match">{{
<SelectItem value="all">Execute all matching rules</SelectItem> $t('admin.automation.executeFirstMatchingRule')
}}</SelectItem>
<SelectItem value="all">{{ $t('admin.automation.executeAllMatchingRules') }}</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>

View File

@@ -1,16 +1,16 @@
import * as z from 'zod'; import * as z from 'zod';
export const formSchema = z export const createFormSchema = (t) => z
.object({ .object({
name: z.string({ name: z.string({
required_error: 'Rule name is required.', required_error: t('globals.messages.required'),
}), }),
description: z.string({ description: z.string({
required_error: 'Rule description is required.', required_error: t('globals.messages.required'),
}), }),
enabled: z.boolean().default(true), enabled: z.boolean().default(true),
type: z.string({ type: z.string({
required_error: 'Rule type is required.', required_error: t('globals.messages.required'),
}), }),
events: z.array(z.string()).optional(), events: z.array(z.string()).optional(),
}) })
@@ -18,7 +18,9 @@ export const formSchema = z
if (data.type === 'conversation_update' && (!data.events || data.events.length === 0)) { if (data.type === 'conversation_update' && (!data.events || data.events.length === 0)) {
ctx.addIssue({ ctx.addIssue({
path: ['events'], path: ['events'],
message: 'Please select at least one event.', message: t('globals.messages.pleaseSelectAtLeastOne', {
name: t('globals.entities.event')
}),
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
}); });
} }

View File

@@ -4,11 +4,15 @@
<div v-if="router.currentRoute.value.name === 'automations'"> <div v-if="router.currentRoute.value.name === 'automations'">
<div class="flex justify-between mb-5"> <div class="flex justify-between mb-5">
<div class="ml-auto"> <div class="ml-auto">
<Button @click="newRule">New rule</Button> <Button @click="newRule">{{
$t('globals.messages.new', {
name: $t('globals.entities.rule')
})
}}</Button>
</div> </div>
</div> </div>
<div v-if="selectedTab"> <div v-if="selectedTab">
<AutomationTabs v-model="selectedTab" /> <AutomationTabs v-model:automationsTab="selectedTab" />
</div> </div>
</div> </div>
<router-view /> <router-view />

View File

@@ -20,7 +20,7 @@
<Checkbox :checked="value" @update:checked="handleChange" /> <Checkbox :checked="value" @update:checked="handleChange" />
</FormControl> </FormControl>
<div class="space-y-1 leading-none"> <div class="space-y-1 leading-none">
<FormLabel> Enabled </FormLabel> <FormLabel> {{ $t('form.field.enabled') }} </FormLabel>
<FormMessage /> <FormMessage />
</div> </div>
</FormItem> </FormItem>
@@ -28,22 +28,24 @@
<FormField v-slot="{ field }" name="name"> <FormField v-slot="{ field }" name="name">
<FormItem> <FormItem>
<FormLabel>Name</FormLabel> <FormLabel>{{ $t('form.field.name') }}</FormLabel>
<FormControl> <FormControl>
<Input type="text" placeholder="My new rule" v-bind="field" /> <Input type="text" placeholder="" v-bind="field" />
</FormControl> </FormControl>
<FormDescription>Name for the rule.</FormDescription> <FormDescription>{{ $t('admin.automation.name.description') }}</FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
</FormField> </FormField>
<FormField v-slot="{ field }" name="description"> <FormField v-slot="{ field }" name="description">
<FormItem> <FormItem>
<FormLabel>Description</FormLabel> <FormLabel>{{ $t('form.field.description') }}</FormLabel>
<FormControl> <FormControl>
<Input type="text" placeholder="Description for new rule" v-bind="field" /> <Input type="text" placeholder="" v-bind="field" />
</FormControl> </FormControl>
<FormDescription>Description for the rule.</FormDescription> <FormDescription>{{
$t('admin.automation.description.description')
}}</FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
</FormField> </FormField>
@@ -54,18 +56,28 @@
<FormControl> <FormControl>
<Select v-bind="componentField" @update:modelValue="handleInput"> <Select v-bind="componentField" @update:modelValue="handleInput">
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select a type" /> <SelectValue :placeholder="t('form.field.selectType')" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
<SelectItem value="new_conversation"> New conversation </SelectItem> <SelectItem value="new_conversation">
<SelectItem value="conversation_update"> Conversation update </SelectItem> {{ $t('admin.automation.newConversation') }}
<SelectItem value="time_trigger"> Time trigger </SelectItem> </SelectItem>
<SelectItem value="conversation_update">
{{ $t('admin.automation.conversationUpdate') }}
</SelectItem>
<SelectItem value="time_trigger">
{{ $t('admin.automation.timeTriggers') }}
</SelectItem>
</SelectGroup> </SelectGroup>
</SelectContent> </SelectContent>
</Select> </Select>
</FormControl> </FormControl>
<FormDescription>Type of rule.</FormDescription> <FormDescription>{{
$t('globals.messages.typeOf', {
name: $t('globals.entities.rule')
})
}}</FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
</FormField> </FormField>
@@ -73,24 +85,26 @@
<div :class="{ hidden: form.values.type !== 'conversation_update' }"> <div :class="{ hidden: form.values.type !== 'conversation_update' }">
<FormField v-slot="{ componentField, handleChange }" name="events"> <FormField v-slot="{ componentField, handleChange }" name="events">
<FormItem> <FormItem>
<FormLabel>Events</FormLabel> <FormLabel>{{ $t('globals.entities.event', 2) }}</FormLabel>
<FormControl> <FormControl>
<SelectTag <SelectTag
v-model="componentField.modelValue" v-model="componentField.modelValue"
@update:modelValue="handleChange" @update:modelValue="handleChange"
:items="conversationEventOptions" :items="conversationEventOptions"
placeholder="Select events" :placeholder="t('form.fields.selectEvents')"
> >
</SelectTag> </SelectTag>
</FormControl> </FormControl>
<FormDescription>Evaluate rule on these events.</FormDescription> <FormDescription>{{
$t('admin.automation.evaluateRuleOnTheseEvents')
}}</FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
</FormField> </FormField>
</div> </div>
</div> </div>
<p class="font-semibold">Match these rules</p> <p class="font-semibold">{{ $t('admin.automation.matchTheseRules') }}</p>
<RuleBox <RuleBox
:ruleGroup="firstRuleGroup" :ruleGroup="firstRuleGroup"
@@ -107,13 +121,13 @@
:class="[groupOperator === 'AND' ? 'bg-black' : 'bg-gray-100 text-black']" :class="[groupOperator === 'AND' ? 'bg-black' : 'bg-gray-100 text-black']"
@click.prevent="toggleGroupOperator('AND')" @click.prevent="toggleGroupOperator('AND')"
> >
AND {{ $t('admin.automation.and') }}
</Button> </Button>
<Button <Button
:class="[groupOperator === 'OR' ? 'bg-black' : 'bg-gray-100 text-black']" :class="[groupOperator === 'OR' ? 'bg-black' : 'bg-gray-100 text-black']"
@click.prevent="toggleGroupOperator('OR')" @click.prevent="toggleGroupOperator('OR')"
> >
OR {{ $t('admin.automation.or') }}
</Button> </Button>
</div> </div>
</div> </div>
@@ -126,7 +140,7 @@
:type="form.values.type" :type="form.values.type"
:groupIndex="1" :groupIndex="1"
/> />
<p class="font-semibold">Perform these actions</p> <p class="font-semibold">{{ $t('admin.automation.performTheseActions') }}</p>
<ActionBox <ActionBox
:actions="getActions()" :actions="getActions()"
@@ -134,7 +148,7 @@
@add-action="handleAddAction" @add-action="handleAddAction"
@remove-action="handleRemoveAction" @remove-action="handleRemoveAction"
/> />
<Button type="submit" :isLoading="isLoading">Save</Button> <Button type="submit" :isLoading="isLoading">{{ $t('globals.buttons.save') }}</Button>
</div> </div>
</form> </form>
</div> </div>
@@ -151,12 +165,13 @@ import api from '@/api'
import { Checkbox } from '@/components/ui/checkbox' import { Checkbox } from '@/components/ui/checkbox'
import { useForm } from 'vee-validate' import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod' import { toTypedSchema } from '@vee-validate/zod'
import { formSchema } from '@/features/admin/automation/formSchema.js' import { createFormSchema } from '@/features/admin/automation/formSchema.js'
import { EMITTER_EVENTS } from '@/constants/emitterEvents.js' import { EMITTER_EVENTS } from '@/constants/emitterEvents.js'
import { useEmitter } from '@/composables/useEmitter' import { useEmitter } from '@/composables/useEmitter'
import { handleHTTPError } from '@/utils/http' import { handleHTTPError } from '@/utils/http'
import { SelectTag } from '@/components/ui/select' import { SelectTag } from '@/components/ui/select'
import { OPERATOR } from '@/constants/filterConfig' import { OPERATOR } from '@/constants/filterConfig'
import { useI18n } from 'vue-i18n'
import { import {
Select, Select,
SelectContent, SelectContent,
@@ -179,6 +194,7 @@ import { useRoute } from 'vue-router'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const isLoading = ref(false) const isLoading = ref(false)
const { t } = useI18n()
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const emitter = useEmitter() const emitter = useEmitter()
@@ -206,12 +222,12 @@ const rule = ref({
}) })
const conversationEventOptions = [ const conversationEventOptions = [
{ label: 'User assigned', value: 'conversation.user.assigned' }, { label: t('admin.automation.event.user.assigned'), value: 'conversation.user.assigned' },
{ label: 'Team assigned', value: 'conversation.team.assigned' }, { label: t('admin.automation.event.team.assigned'), value: 'conversation.team.assigned' },
{ label: 'Priority change', value: 'conversation.priority.change' }, { label: t('admin.automation.event.priority.change'), value: 'conversation.priority.change' },
{ label: 'Status change', value: 'conversation.status.change' }, { label: t('admin.automation.event.status.change'), value: 'conversation.status.change' },
{ label: 'Outgoing message', value: 'conversation.message.outgoing' }, { label: t('admin.automation.event.message.outgoing'), value: 'conversation.message.outgoing' },
{ label: 'Incoming message', value: 'conversation.message.incoming' } { label: t('admin.automation.event.message.incoming'), value: 'conversation.message.incoming' }
] ]
const props = defineProps({ const props = defineProps({
@@ -222,8 +238,13 @@ const props = defineProps({
}) })
const breadcrumbPageLabel = () => { const breadcrumbPageLabel = () => {
if (props.id > 0) return 'Edit rule' if (props.id > 0)
return 'New rule' return t('globals.messages.edit', {
name: t('globals.entities.rule')
})
return t('globals.messages.new', {
name: t('globals.entities.rule')
})
} }
const formTitle = computed(() => { const formTitle = computed(() => {
@@ -237,7 +258,7 @@ const isNewForm = computed(() => {
}) })
const breadcrumbLinks = [ const breadcrumbLinks = [
{ path: 'automations', label: 'Automations' }, { path: 'automations', label: t('admin.automation') },
{ path: '', label: breadcrumbPageLabel() } { path: '', label: breadcrumbPageLabel() }
] ]
@@ -305,7 +326,7 @@ const handleRemoveAction = (index) => {
} }
const form = useForm({ const form = useForm({
validationSchema: toTypedSchema(formSchema) validationSchema: toTypedSchema(createFormSchema(t))
}) })
const onSubmit = form.handleSubmit(async (values) => { const onSubmit = form.handleSubmit(async (values) => {
@@ -315,10 +336,8 @@ const onSubmit = form.handleSubmit(async (values) => {
const handleSave = async (values) => { const handleSave = async (values) => {
if (!areRulesValid()) { if (!areRulesValid()) {
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, { emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
title: 'Invalid rules',
variant: 'destructive', variant: 'destructive',
description: description: t('admin.automation.invalid')
'Make sure you have atleast one action and one rule and their values are not empty.'
}) })
return return
} }
@@ -336,12 +355,12 @@ const handleSave = async (values) => {
router.push({ name: 'automations' }) router.push({ name: 'automations' })
} }
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, { emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
title: 'Success', description: t('globals.messages.savedSuccessfully', {
description: 'Rule saved successfully' name: t('globals.entities.rule')
})
}) })
} catch (error) { } catch (error) {
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, { emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
title: 'Could not save rule',
variant: 'destructive', variant: 'destructive',
description: handleHTTPError(error).message description: handleHTTPError(error).message
}) })
@@ -416,7 +435,6 @@ onMounted(async () => {
form.setValues(resp.data.data) form.setValues(resp.data.data)
} catch (error) { } catch (error) {
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, { emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
title: 'Could not fetch rule',
variant: 'destructive', variant: 'destructive',
description: handleHTTPError(error).message description: handleHTTPError(error).message
}) })

View File

@@ -38,6 +38,8 @@
"globals.entities.config": "Config | Configs", "globals.entities.config": "Config | Configs",
"globals.entities.macro": "Macro | Macros", "globals.entities.macro": "Macro | Macros",
"globals.entities.macroAction": "Macro Action | Macro Actions", "globals.entities.macroAction": "Macro Action | Macro Actions",
"globals.entities.action": "Action | Actions",
"globals.entities.event": "Event | Events",
"globals.entities.automation": "Automation | Automations", "globals.entities.automation": "Automation | Automations",
"globals.entities.oidc": "OIDC | OIDCs", "globals.entities.oidc": "OIDC | OIDCs",
"globals.entities.oidcProvider": "OIDC Provider | OIDC Providers", "globals.entities.oidcProvider": "OIDC Provider | OIDC Providers",
@@ -45,6 +47,7 @@
"globals.entities.avatar": "Avatar | Avatars", "globals.entities.avatar": "Avatar | Avatars",
"globals.entities.view": "View | Views", "globals.entities.view": "View | Views",
"globals.entities.email": "Email | Emails", "globals.entities.email": "Email | Emails",
"globals.entities.condition": "Condition | Conditions",
"globals.messages.adjustFilters": "Try adjusting your filters", "globals.messages.adjustFilters": "Try adjusting your filters",
"globals.messages.errorUploadingFile": "Error uploading file", "globals.messages.errorUploadingFile": "Error uploading file",
"globals.messages.errorUpdating": "Error updating {name}", "globals.messages.errorUpdating": "Error updating {name}",
@@ -73,12 +76,15 @@
"globals.messages.tooLong": "{name} is too long, should be at most {max} characters", "globals.messages.tooLong": "{name} is too long, should be at most {max} characters",
"globals.messages.deletedSuccessfully": "{name} deleted successfully", "globals.messages.deletedSuccessfully": "{name} deleted successfully",
"globals.messages.updatedSuccessfully": "{name} updated successfully", "globals.messages.updatedSuccessfully": "{name} updated successfully",
"globals.messages.savedSuccessfully": "{name} saved successfully",
"globals.messages.edit": "Edit {name}", "globals.messages.edit": "Edit {name}",
"globals.messages.delete": "Delete {name}", "globals.messages.delete": "Delete {name}",
"globals.messages.create": "Create {name}", "globals.messages.create": "Create {name}",
"globals.messages.new": "New {name}", "globals.messages.new": "New {name}",
"globals.messages.add": "Add {name}",
"globals.messages.yes": "Yes", "globals.messages.yes": "Yes",
"globals.messages.no": "No", "globals.messages.no": "No",
"globals.messages.typeOf": "Type of {name}",
"globals.messages.createdSuccessfully": "{name} created successfully", "globals.messages.createdSuccessfully": "{name} created successfully",
"globals.messages.invalidEmailAddress": "Invalid email address", "globals.messages.invalidEmailAddress": "Invalid email address",
"globals.messages.pleaseSelectAtLeastOne": "Please select at least one {name}", "globals.messages.pleaseSelectAtLeastOne": "Please select at least one {name}",
@@ -99,6 +105,8 @@
"globals.messages.somethingWentWrong": "Something went wrong", "globals.messages.somethingWentWrong": "Something went wrong",
"globals.messages.done": "Done", "globals.messages.done": "Done",
"globals.messages.emptyState": "Nothing here", "globals.messages.emptyState": "Nothing here",
"globals.messages.pressEnterToSelectAValue": "Press enter to select a value",
"globals.messages.caseSensitiveMatch": "Case sensitive match",
"auth.csrfTokenMismatch": "CSRF token mismatch", "auth.csrfTokenMismatch": "CSRF token mismatch",
"authz.permissionDenied": "Permission denied", "authz.permissionDenied": "Permission denied",
"user.userAlreadyLoggedIn": "User already logged in", "user.userAlreadyLoggedIn": "User already logged in",
@@ -234,7 +242,17 @@
"form.field.pickDate": "Pick a date", "form.field.pickDate": "Pick a date",
"form.field.selectTLS": "Select TLS", "form.field.selectTLS": "Select TLS",
"form.field.selectRoles": "Select roles", "form.field.selectRoles": "Select roles",
"form.field.selectField": "Select field",
"form.field.selectTeam": "Select team",
"form.field.selectTag": "Select tag",
"form.field.selectAction": "Select action",
"form.field.selectUser": "Select user",
"form.field.selectValue": "Select value",
"form.field.selectTeams": "Select teams", "form.field.selectTeams": "Select teams",
"form.field.selectType": "Select type",
"form.fields.setValue": "Set value",
"form.fields.selectEvents": "Select events",
"form.field.selectOperator": "Select operator",
"form.error.min": "Should be at least {min} characters", "form.error.min": "Should be at least {min} characters",
"form.error.max": "Should be at most {max} characters", "form.error.max": "Should be at most {max} characters",
"form.error.minmax": "Should be between {min} and {max} characters", "form.error.minmax": "Should be between {min} and {max} characters",
@@ -427,6 +445,34 @@
"admin.role.business_hours.manage": "Manage Business Hours", "admin.role.business_hours.manage": "Manage Business Hours",
"admin.role.sla.manage": "Manage SLA Policies", "admin.role.sla.manage": "Manage SLA Policies",
"admin.role.ai.manage": "Manage AI Features", "admin.role.ai.manage": "Manage AI Features",
"admin.automation": "Automations",
"admin.automation.newConversation": "New Conversation",
"admin.automation.newConversation.description": "Rules that run when a new conversation is created, drag and drop to reorder rules.",
"admin.automation.conversationUpdate": "Conversation Update",
"admin.automation.conversationUpdate.description": "Rules that run when a conversation is updated.",
"admin.automation.timeTriggers": "Time Triggers",
"admin.automation.timeTriggers.description": "Rules that once an hour",
"admin.automation.match": "Match",
"admin.automation.any": "ANY",
"admin.automation.all": "ALL",
"admin.automation.below": "below",
"admin.automation.deleteConfirmation": "This action cannot be undone. This will permanently delete this automation rule.",
"admin.automation.executeFirstMatchingRule": "Execute first matching rule",
"admin.automation.executeAllMatchingRules": "Execute all matching rules",
"admin.automation.name.description": "Name for this automation rule.",
"admin.automation.description.description": "Short description of what this rule does.",
"admin.automation.evaluateRuleOnTheseEvents": "Evaluate rule on these events.",
"admin.automation.matchTheseRules": "Match these rules",
"admin.automation.and": "AND",
"admin.automation.or": "OR",
"admin.automation.performTheseActions": "Perform these actions",
"admin.automation.event.user.assigned": "User assigned",
"admin.automation.event.team.assigned": "Team assigned",
"admin.automation.event.priority.change": "Priority change",
"admin.automation.event.status.change": "Status change",
"admin.automation.event.message.outgoing": "Outgoing message",
"admin.automation.event.message.incoming": "Incoming message",
"admin.automation.invalid": "Make sure you have atleast one action and one rule and their values are not empty.",
"globals.buttons.save": "Save", "globals.buttons.save": "Save",
"globals.buttons.save_changes": "Save changes", "globals.buttons.save_changes": "Save changes",
"globals.buttons.cancel": "Cancel", "globals.buttons.cancel": "Cancel",