feat: allow edit

This commit is contained in:
Shivam Mishra
2025-10-03 19:48:50 +05:30
parent 5b8554d216
commit e4631e4294
5 changed files with 123 additions and 25 deletions
@@ -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>
@@ -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"
@@ -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>