feat: allow edit
This commit is contained in:
+39
-7
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { ref, computed } from 'vue';
|
||||
import { useStore } from 'dashboard/composables/store';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
@@ -8,22 +8,49 @@ import { parseAPIErrorResponse } from 'dashboard/store/utils/api';
|
||||
import Dialog from 'dashboard/components-next/dialog/Dialog.vue';
|
||||
import CustomToolForm from './CustomToolForm.vue';
|
||||
|
||||
const props = defineProps({
|
||||
selectedTool: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'create',
|
||||
validator: value => ['create', 'edit'].includes(value),
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
const { t } = useI18n();
|
||||
const store = useStore();
|
||||
|
||||
const dialogRef = ref(null);
|
||||
|
||||
const i18nKey = 'CAPTAIN.CUSTOM_TOOLS.CREATE';
|
||||
const updateTool = toolDetails =>
|
||||
store.dispatch('captainCustomTools/update', {
|
||||
id: props.selectedTool.id,
|
||||
...toolDetails,
|
||||
});
|
||||
|
||||
const handleSubmit = async newTool => {
|
||||
const i18nKey = computed(
|
||||
() => `CAPTAIN.CUSTOM_TOOLS.${props.type.toUpperCase()}`
|
||||
);
|
||||
|
||||
const createTool = toolDetails =>
|
||||
store.dispatch('captainCustomTools/create', toolDetails);
|
||||
|
||||
const handleSubmit = async updatedTool => {
|
||||
try {
|
||||
await store.dispatch('captainCustomTools/create', newTool);
|
||||
useAlert(t(`${i18nKey}.SUCCESS_MESSAGE`));
|
||||
if (props.type === 'edit') {
|
||||
await updateTool(updatedTool);
|
||||
} else {
|
||||
await createTool(updatedTool);
|
||||
}
|
||||
useAlert(t(`${i18nKey.value}.SUCCESS_MESSAGE`));
|
||||
dialogRef.value.close();
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
parseAPIErrorResponse(error) || t(`${i18nKey}.ERROR_MESSAGE`);
|
||||
parseAPIErrorResponse(error) || t(`${i18nKey.value}.ERROR_MESSAGE`);
|
||||
useAlert(errorMessage);
|
||||
}
|
||||
};
|
||||
@@ -49,7 +76,12 @@ defineExpose({ dialogRef });
|
||||
:show-confirm-button="false"
|
||||
@close="handleClose"
|
||||
>
|
||||
<CustomToolForm @submit="handleSubmit" @cancel="handleCancel" />
|
||||
<CustomToolForm
|
||||
:mode="type"
|
||||
:tool="selectedTool"
|
||||
@submit="handleSubmit"
|
||||
@cancel="handleCancel"
|
||||
/>
|
||||
<template #footer />
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
+40
-3
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { reactive, computed, useTemplateRef } from 'vue';
|
||||
import { reactive, computed, useTemplateRef, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { required, url } from '@vuelidate/validators';
|
||||
@@ -12,6 +12,18 @@ import ComboBox from 'dashboard/components-next/combobox/ComboBox.vue';
|
||||
import ParamRow from './ParamRow.vue';
|
||||
import AuthConfig from './AuthConfig.vue';
|
||||
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'create',
|
||||
validator: value => ['create', 'edit'].includes(value),
|
||||
},
|
||||
tool: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['submit', 'cancel']);
|
||||
|
||||
const { t } = useI18n();
|
||||
@@ -34,6 +46,25 @@ const initialState = {
|
||||
|
||||
const state = reactive({ ...initialState });
|
||||
|
||||
// Populate form when in edit mode
|
||||
watch(
|
||||
() => props.tool,
|
||||
newTool => {
|
||||
if (props.mode === 'edit' && newTool && newTool.id) {
|
||||
state.title = newTool.title || '';
|
||||
state.description = newTool.description || '';
|
||||
state.endpoint_url = newTool.endpoint_url || '';
|
||||
state.http_method = newTool.http_method || 'GET';
|
||||
state.request_template = newTool.request_template || '';
|
||||
state.response_template = newTool.response_template || '';
|
||||
state.auth_type = newTool.auth_type || 'none';
|
||||
state.auth_config = newTool.auth_config || {};
|
||||
state.param_schema = newTool.param_schema || [];
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const DEFAULT_PARAM = {
|
||||
name: '',
|
||||
type: 'string',
|
||||
@@ -65,7 +96,11 @@ const authTypeOptions = computed(() => [
|
||||
|
||||
const v$ = useVuelidate(validationRules, state);
|
||||
|
||||
const isLoading = computed(() => formState.uiFlags.value.creatingItem);
|
||||
const isLoading = computed(() =>
|
||||
props.mode === 'edit'
|
||||
? formState.uiFlags.value.updatingItem
|
||||
: formState.uiFlags.value.creatingItem
|
||||
);
|
||||
|
||||
const getErrorMessage = (field, errorKey) => {
|
||||
return v$.value[field].$error
|
||||
@@ -221,7 +256,9 @@ const handleSubmit = async () => {
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
:label="t('CAPTAIN.FORM.CREATE')"
|
||||
:label="
|
||||
t(mode === 'edit' ? 'CAPTAIN.FORM.EDIT' : 'CAPTAIN.FORM.CREATE')
|
||||
"
|
||||
class="w-full"
|
||||
:is-loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
|
||||
+4
-6
@@ -69,17 +69,17 @@ defineExpose({ validate });
|
||||
}"
|
||||
>
|
||||
<div class="flex flex-col flex-1 gap-3">
|
||||
<div class="flex gap-2">
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<Input
|
||||
v-model="name"
|
||||
:placeholder="t('CAPTAIN.CUSTOM_TOOLS.FORM.PARAM_NAME.PLACEHOLDER')"
|
||||
class="flex-1 [&>input]:h-8 [&>input]:py-1.5 [&>input]:outline-offset-0"
|
||||
class="col-span-2"
|
||||
/>
|
||||
<ComboBox
|
||||
v-model="type"
|
||||
:options="paramTypeOptions"
|
||||
:placeholder="t('CAPTAIN.CUSTOM_TOOLS.FORM.PARAM_TYPE.PLACEHOLDER')"
|
||||
class="w-36 [&>div>button]:h-8 [&>div>button]:py-1.5"
|
||||
class="[&>div>button]:bg-n-alpha-black2"
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
@@ -87,7 +87,6 @@ defineExpose({ validate });
|
||||
:placeholder="
|
||||
t('CAPTAIN.CUSTOM_TOOLS.FORM.PARAM_DESCRIPTION.PLACEHOLDER')
|
||||
"
|
||||
class="[&>input]:h-8 [&>input]:py-1.5 [&>input]:outline-offset-0"
|
||||
/>
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<Checkbox v-model="required" />
|
||||
@@ -97,11 +96,10 @@ defineExpose({ validate });
|
||||
</label>
|
||||
</div>
|
||||
<Button
|
||||
sm
|
||||
solid
|
||||
slate
|
||||
icon="i-lucide-trash"
|
||||
class="flex-shrink-0 mt-0.5"
|
||||
class="flex-shrink-0"
|
||||
@click.stop="emit('remove')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -771,6 +771,11 @@
|
||||
"SUCCESS_MESSAGE": "Custom tool created successfully",
|
||||
"ERROR_MESSAGE": "Failed to create custom tool"
|
||||
},
|
||||
"EDIT": {
|
||||
"TITLE": "Edit Custom Tool",
|
||||
"SUCCESS_MESSAGE": "Custom tool updated successfully",
|
||||
"ERROR_MESSAGE": "Failed to update custom tool"
|
||||
},
|
||||
"FORM": {
|
||||
"TITLE": {
|
||||
"LABEL": "Tool Name",
|
||||
@@ -786,7 +791,7 @@
|
||||
},
|
||||
"ENDPOINT_URL": {
|
||||
"LABEL": "Endpoint URL",
|
||||
"PLACEHOLDER": "https://api.example.com/orders/'{' order_id '}'",
|
||||
"PLACEHOLDER": "https://api.example.com/orders/{'{{'} order_id {'}}'}",
|
||||
"ERROR": "Valid URL is required"
|
||||
},
|
||||
"AUTH_TYPE": {
|
||||
@@ -836,11 +841,11 @@
|
||||
},
|
||||
"REQUEST_TEMPLATE": {
|
||||
"LABEL": "Request Body Template (Optional)",
|
||||
"PLACEHOLDER": "'{'\n \"order_id\": \"'{' order_id '}'\"\n'}'"
|
||||
"PLACEHOLDER": "{\n \"order_id\": \"{'{{'} order_id {'}}'}\"\n}"
|
||||
},
|
||||
"RESPONSE_TEMPLATE": {
|
||||
"LABEL": "Response Template (Optional)",
|
||||
"PLACEHOLDER": "Order '{' order_id '}' status: '{' status '}'"
|
||||
"PLACEHOLDER": "Order {'{{'} order_id {'}}'} status: {'{{'} status {'}}'}"
|
||||
},
|
||||
"ERRORS": {
|
||||
"PARAM_NAME_REQUIRED": "Parameter name is required"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { computed, onMounted, ref, nextTick } from 'vue';
|
||||
import { useMapGetter, useStore } from 'dashboard/composables/store';
|
||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
|
||||
@@ -17,6 +17,8 @@ const isFetching = computed(() => uiFlags.value.fetchingList);
|
||||
const customToolsMeta = useMapGetter('captainCustomTools/getMeta');
|
||||
|
||||
const createDialogRef = ref(null);
|
||||
const selectedTool = ref(null);
|
||||
const dialogType = ref('');
|
||||
|
||||
const fetchCustomTools = (page = 1) => {
|
||||
store.dispatch('captainCustomTools/get', { page });
|
||||
@@ -25,13 +27,31 @@ const fetchCustomTools = (page = 1) => {
|
||||
const onPageChange = page => fetchCustomTools(page);
|
||||
|
||||
const openCreateDialog = () => {
|
||||
createDialogRef.value.dialogRef.open();
|
||||
dialogType.value = 'create';
|
||||
selectedTool.value = null;
|
||||
nextTick(() => createDialogRef.value.dialogRef.open());
|
||||
};
|
||||
|
||||
const handleEdit = tool => {
|
||||
dialogType.value = 'edit';
|
||||
selectedTool.value = tool;
|
||||
nextTick(() => createDialogRef.value.dialogRef.open());
|
||||
};
|
||||
|
||||
const handleAction = ({ action, id }) => {
|
||||
// TODO: Implement edit and delete actions
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Action:', action, 'ID:', id);
|
||||
const tool = customTools.value.find(t => t.id === id);
|
||||
if (action === 'edit') {
|
||||
handleEdit(tool);
|
||||
} else if (action === 'delete') {
|
||||
// TODO: Implement delete
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Delete:', id);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDialogClose = () => {
|
||||
dialogType.value = '';
|
||||
selectedTool.value = null;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
@@ -82,5 +102,11 @@ onMounted(() => {
|
||||
</template>
|
||||
</PageLayout>
|
||||
|
||||
<CreateCustomToolDialog ref="createDialogRef" />
|
||||
<CreateCustomToolDialog
|
||||
v-if="dialogType"
|
||||
ref="createDialogRef"
|
||||
:type="dialogType"
|
||||
:selected-tool="selectedTool"
|
||||
@close="handleDialogClose"
|
||||
/>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user