|
|
|
<script setup lang="ts">
|
|
|
|
import type { ComponentPublicInstance } from '@vue/runtime-core'
|
|
|
|
import type { Form as AntForm, SelectProps } from 'ant-design-vue'
|
|
|
|
import type { FormType, GalleryType, GridType, KanbanType } from 'nocodb-sdk'
|
|
|
|
import { message } from 'ant-design-vue'
|
|
|
|
import { capitalize, inject } from '@vue/runtime-core'
|
|
|
|
import { UITypes, ViewTypes } from 'nocodb-sdk'
|
|
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
import {
|
|
|
|
FieldsInj,
|
|
|
|
MetaInj,
|
|
|
|
ViewListInj,
|
|
|
|
computed,
|
|
|
|
generateUniqueTitle,
|
|
|
|
nextTick,
|
|
|
|
reactive,
|
|
|
|
unref,
|
|
|
|
useApi,
|
|
|
|
useVModel,
|
|
|
|
watch,
|
|
|
|
} from '#imports'
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
modelValue: boolean
|
|
|
|
type: ViewTypes
|
|
|
|
title?: string
|
|
|
|
selectedViewId?: string
|
|
|
|
}
|
|
|
|
|
|
|
|
interface Emits {
|
|
|
|
(event: 'update:modelValue', value: boolean): void
|
|
|
|
(event: 'created', value: GridType | KanbanType | GalleryType | FormType): void
|
|
|
|
}
|
|
|
|
|
|
|
|
interface Form {
|
|
|
|
title: string
|
|
|
|
type: ViewTypes
|
|
|
|
copy_from_id: string | null
|
|
|
|
// for kanban view only
|
|
|
|
grp_column_id: string | null
|
|
|
|
}
|
|
|
|
|
|
|
|
const props = defineProps<Props>()
|
|
|
|
|
|
|
|
const emits = defineEmits<Emits>()
|
|
|
|
|
|
|
|
const inputEl = $ref<ComponentPublicInstance>()
|
|
|
|
|
|
|
|
const formValidator = $ref<typeof AntForm>()
|
|
|
|
|
|
|
|
const vModel = useVModel(props, 'modelValue', emits)
|
|
|
|
|
|
|
|
const { t } = useI18n()
|
|
|
|
|
|
|
|
const { isLoading: loading, api } = useApi()
|
|
|
|
|
|
|
|
const meta = inject(MetaInj, ref())
|
|
|
|
|
|
|
|
const viewList = inject(ViewListInj)
|
|
|
|
|
|
|
|
const fields = inject(FieldsInj, ref([]))
|
|
|
|
|
|
|
|
const form = reactive<Form>({
|
|
|
|
title: props.title || '',
|
|
|
|
type: props.type,
|
|
|
|
copy_from_id: null,
|
|
|
|
grp_column_id: null,
|
|
|
|
})
|
|
|
|
|
|
|
|
const viewNameRules = [
|
|
|
|
// name is required
|
|
|
|
{ required: true, message: `${t('labels.viewName')} ${t('general.required')}` },
|
|
|
|
// name is unique
|
|
|
|
{
|
|
|
|
validator: (_: unknown, v: string) =>
|
|
|
|
new Promise((resolve, reject) => {
|
|
|
|
;(unref(viewList) || []).every((v1) => ((v1 as GridType | KanbanType | GalleryType).alias || v1.title) !== v)
|
|
|
|
? resolve(true)
|
|
|
|
: reject(new Error(`View name should be unique`))
|
|
|
|
}),
|
|
|
|
message: 'View name should be unique',
|
|
|
|
},
|
|
|
|
]
|
|
|
|
|
|
|
|
const groupingFieldColumnRules = [
|
|
|
|
// name is required
|
|
|
|
{ required: true, message: `${t('general.groupingField')} ${t('general.required')}` },
|
|
|
|
]
|
|
|
|
|
|
|
|
const typeAlias = computed(
|
|
|
|
() =>
|
|
|
|
({
|
|
|
|
[ViewTypes.GRID]: 'grid',
|
|
|
|
[ViewTypes.GALLERY]: 'gallery',
|
|
|
|
[ViewTypes.FORM]: 'form',
|
|
|
|
[ViewTypes.KANBAN]: 'kanban',
|
|
|
|
}[props.type]),
|
|
|
|
)
|
|
|
|
|
|
|
|
watch(vModel, (value) => value && init())
|
|
|
|
|
|
|
|
watch(
|
|
|
|
() => props.type,
|
|
|
|
(newType) => (form.type = newType),
|
|
|
|
)
|
|
|
|
|
|
|
|
function init() {
|
|
|
|
form.title = generateUniqueTitle(capitalize(ViewTypes[props.type].toLowerCase()), viewList?.value || [], 'title')
|
|
|
|
|
|
|
|
if (props.selectedViewId) {
|
|
|
|
form.copy_from_id = props.selectedViewId
|
|
|
|
}
|
|
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
const el = inputEl?.$el as HTMLInputElement
|
|
|
|
|
|
|
|
if (el) {
|
|
|
|
el.focus()
|
|
|
|
el.select()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
async function onSubmit() {
|
|
|
|
const isValid = await formValidator?.validateFields()
|
|
|
|
|
|
|
|
if (isValid && form.type) {
|
|
|
|
const _meta = unref(meta)
|
|
|
|
|
|
|
|
if (!_meta || !_meta.id) return
|
|
|
|
|
|
|
|
try {
|
|
|
|
let data: GridType | KanbanType | GalleryType | FormType | null = null
|
|
|
|
|
|
|
|
switch (form.type) {
|
|
|
|
case ViewTypes.GRID:
|
|
|
|
data = await api.dbView.gridCreate(_meta.id, form)
|
|
|
|
break
|
|
|
|
case ViewTypes.GALLERY:
|
|
|
|
data = await api.dbView.galleryCreate(_meta.id, form)
|
|
|
|
break
|
|
|
|
case ViewTypes.FORM:
|
|
|
|
data = await api.dbView.formCreate(_meta.id, form)
|
|
|
|
break
|
|
|
|
case ViewTypes.KANBAN:
|
|
|
|
data = await api.dbView.kanbanCreate(_meta.id, form)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data) {
|
|
|
|
// View created successfully
|
|
|
|
message.success(t('msg.toast.createView'))
|
|
|
|
|
|
|
|
emits('created', data)
|
|
|
|
}
|
|
|
|
} catch (e: any) {
|
|
|
|
message.error(e.message)
|
|
|
|
}
|
|
|
|
|
|
|
|
vModel.value = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const singleSelectFieldOptions = computed<SelectProps['options']>(() => {
|
|
|
|
return fields.value
|
|
|
|
.filter((el) => el.uidt === UITypes.SingleSelect)
|
|
|
|
.map((field) => {
|
|
|
|
return {
|
|
|
|
value: field.id,
|
|
|
|
label: field.title,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
if (form.type === ViewTypes.KANBAN) {
|
|
|
|
// preset the grouping field column
|
|
|
|
form.grp_column_id = singleSelectFieldOptions.value?.[0]?.value as string
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
|
|
|
<a-modal v-model:visible="vModel" class="!top-[35%]" :confirm-loading="loading" wrap-class-name="nc-modal-view-create">
|
|
|
|
<template #title>
|
|
|
|
{{ $t('general.create') }} <span class="text-capitalize">{{ typeAlias }}</span> {{ $t('objects.view') }}
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<a-form ref="formValidator" layout="vertical" :model="form">
|
|
|
|
<a-form-item :label="$t('labels.viewName')" name="title" :rules="viewNameRules">
|
|
|
|
<a-input ref="inputEl" v-model:value="form.title" autofocus @keydown.enter="onSubmit" />
|
|
|
|
</a-form-item>
|
|
|
|
<a-form-item v-if="form.type === ViewTypes.KANBAN" name="grp_column_id" :rules="groupingFieldColumnRules">
|
|
|
|
<a-select
|
|
|
|
v-model:value="form.grp_column_id"
|
|
|
|
class="w-full nc-kanban-grouping-field-select"
|
|
|
|
:options="singleSelectFieldOptions"
|
|
|
|
placeholder="Select a Grouping Field"
|
|
|
|
not-found-content="No Single Select Field can be found. Please create one first."
|
|
|
|
/>
|
|
|
|
</a-form-item>
|
|
|
|
</a-form>
|
|
|
|
|
|
|
|
<template #footer>
|
|
|
|
<a-button key="back" @click="vModel = false">{{ $t('general.cancel') }}</a-button>
|
|
|
|
<a-button key="submit" type="primary" :loading="loading" @click="onSubmit">{{ $t('general.submit') }}</a-button>
|
|
|
|
</template>
|
|
|
|
</a-modal>
|
|
|
|
</template>
|