|
|
@ -3,10 +3,21 @@ import type { ColumnType } from 'nocodb-sdk' |
|
|
|
import { RelationTypes, UITypes, isVirtualCol } from 'nocodb-sdk' |
|
|
|
import { RelationTypes, UITypes, isVirtualCol } from 'nocodb-sdk' |
|
|
|
import { ref } from 'vue' |
|
|
|
import { ref } from 'vue' |
|
|
|
import { StreamBarcodeReader } from 'vue-barcode-reader' |
|
|
|
import { StreamBarcodeReader } from 'vue-barcode-reader' |
|
|
|
import { iconMap, useGlobal, useSharedFormStoreOrThrow } from '#imports' |
|
|
|
import { iconMap, useSharedFormStoreOrThrow } from '#imports' |
|
|
|
|
|
|
|
|
|
|
|
const { sharedFormView, submitForm, v$, formState, notFound, formColumns, submitted, secondsRemain, isLoading } = |
|
|
|
const { |
|
|
|
useSharedFormStoreOrThrow() |
|
|
|
sharedFormView, |
|
|
|
|
|
|
|
submitForm, |
|
|
|
|
|
|
|
clearForm, |
|
|
|
|
|
|
|
v$, |
|
|
|
|
|
|
|
formState, |
|
|
|
|
|
|
|
notFound, |
|
|
|
|
|
|
|
formColumns, |
|
|
|
|
|
|
|
submitted, |
|
|
|
|
|
|
|
secondsRemain, |
|
|
|
|
|
|
|
isLoading, |
|
|
|
|
|
|
|
progress, |
|
|
|
|
|
|
|
} = useSharedFormStoreOrThrow() |
|
|
|
|
|
|
|
|
|
|
|
function isRequired(_columnObj: Record<string, any>, required = false) { |
|
|
|
function isRequired(_columnObj: Record<string, any>, required = false) { |
|
|
|
let columnObj = _columnObj |
|
|
|
let columnObj = _columnObj |
|
|
@ -21,8 +32,6 @@ function isRequired(_columnObj: Record<string, any>, required = false) { |
|
|
|
return !!(required || (columnObj && columnObj.rqd && !columnObj.cdf)) |
|
|
|
return !!(required || (columnObj && columnObj.rqd && !columnObj.cdf)) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const { isMobileMode } = useGlobal() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const fieldTitleForCurrentScan = ref('') |
|
|
|
const fieldTitleForCurrentScan = ref('') |
|
|
|
|
|
|
|
|
|
|
|
const scannerIsReady = ref(false) |
|
|
|
const scannerIsReady = ref(false) |
|
|
@ -70,32 +79,37 @@ const onDecode = async (scannedCodeValue: string) => { |
|
|
|
</script> |
|
|
|
</script> |
|
|
|
|
|
|
|
|
|
|
|
<template> |
|
|
|
<template> |
|
|
|
<div class="h-full flex flex-col items-center" :class="isMobileMode ? 'mobile' : 'desktop'"> |
|
|
|
<div class="h-full flex flex-col items-center"> |
|
|
|
|
|
|
|
<GeneralFormBanner |
|
|
|
|
|
|
|
v-if="sharedFormView" |
|
|
|
|
|
|
|
:banner-image-url="sharedFormView.banner_image_url" |
|
|
|
|
|
|
|
class="flex-none dark:border-none" |
|
|
|
|
|
|
|
/> |
|
|
|
|
|
|
|
|
|
|
|
<div |
|
|
|
<div |
|
|
|
class="color-transition flex flex-col justify-center gap-2 w-full max-w-[max(33%,600px)] m-auto py-4 pb-8 px-16 md:(bg-white dark:bg-slate-700 rounded-lg border-1 border-gray-200 shadow-xl)" |
|
|
|
class="transition-all duration-300 ease-in relative flex flex-col justify-center gap-2 w-full max-w-[max(33%,688px)] mx-auto my-6 bg-white dark:bg-transparent rounded-3xl border-1 border-gray-200 px-4 py-8 lg:p-12 md:(p-8 dark:bg-slate-700)" |
|
|
|
> |
|
|
|
> |
|
|
|
<template v-if="sharedFormView"> |
|
|
|
<template v-if="sharedFormView"> |
|
|
|
<h1 class="prose-2xl font-bold self-center my-4 break-words"> |
|
|
|
<div class="mb-4"> |
|
|
|
|
|
|
|
<h1 class="text-2xl font-bold text-gray-900 mb-4"> |
|
|
|
{{ sharedFormView.heading }} |
|
|
|
{{ sharedFormView.heading }} |
|
|
|
</h1> |
|
|
|
</h1> |
|
|
|
|
|
|
|
|
|
|
|
<h2 |
|
|
|
<h2 v-if="sharedFormView.subheading" class="font-medium text-base text-gray-500 dark:text-slate-300 mb-4"> |
|
|
|
v-if="sharedFormView.subheading" |
|
|
|
|
|
|
|
class="prose-lg text-slate-500 dark:text-slate-300 self-center mb-4 leading-6 break-words" |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
{{ sharedFormView.subheading }} |
|
|
|
{{ sharedFormView.subheading }} |
|
|
|
</h2> |
|
|
|
</h2> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<a-alert v-if="notFound" type="warning" class="my-4 text-center" :message="$t('general.notFound')" /> |
|
|
|
<a-alert v-if="notFound" type="warning" class="my-4 text-center" message="Not found" /> |
|
|
|
|
|
|
|
|
|
|
|
<template v-else-if="submitted"> |
|
|
|
<template v-else-if="submitted"> |
|
|
|
<div class="flex justify-center"> |
|
|
|
<div class="flex justify-center"> |
|
|
|
<div v-if="sharedFormView" class="min-w-350px mt-3"> |
|
|
|
<div v-if="sharedFormView" class="w-full lg:w-[95%]"> |
|
|
|
<a-alert |
|
|
|
<a-alert |
|
|
|
type="success" |
|
|
|
type="success" |
|
|
|
class="my-4 text-center" |
|
|
|
class="!my-4 text-center !rounded-lg" |
|
|
|
outlined |
|
|
|
outlined |
|
|
|
:message="sharedFormView.success_msg || $t('msg.successfullySubmittedFormData')" |
|
|
|
:message="sharedFormView.success_msg || 'Successfully submitted form data'" |
|
|
|
/> |
|
|
|
/> |
|
|
|
|
|
|
|
|
|
|
|
<p v-if="sharedFormView.show_blank_form" class="text-xs text-slate-500 dark:text-slate-300 text-center my-4"> |
|
|
|
<p v-if="sharedFormView.show_blank_form" class="text-xs text-slate-500 dark:text-slate-300 text-center my-4"> |
|
|
@ -103,7 +117,9 @@ const onDecode = async (scannedCodeValue: string) => { |
|
|
|
</p> |
|
|
|
</p> |
|
|
|
|
|
|
|
|
|
|
|
<div v-if="sharedFormView.submit_another_form" class="text-center"> |
|
|
|
<div v-if="sharedFormView.submit_another_form" class="text-center"> |
|
|
|
<a-button type="primary" @click="submitted = false"> {{ $t('activity.submitAnotherForm') }}</a-button> |
|
|
|
<NcButton type="primary" size="medium" @click="submitted = false"> |
|
|
|
|
|
|
|
{{ $t('activity.submitAnotherForm') }}</NcButton |
|
|
|
|
|
|
|
> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
@ -124,7 +140,7 @@ const onDecode = async (scannedCodeValue: string) => { |
|
|
|
<StreamBarcodeReader v-show="scannerIsReady" @decode="onDecode" @loaded="onLoaded"> </StreamBarcodeReader> |
|
|
|
<StreamBarcodeReader v-show="scannerIsReady" @decode="onDecode" @loaded="onLoaded"> </StreamBarcodeReader> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</a-modal> |
|
|
|
</a-modal> |
|
|
|
<GeneralOverlay class="bg-gray-400/75" :model-value="isLoading" inline transition> |
|
|
|
<GeneralOverlay class="bg-gray-50/75 rounded-3xl" :model-value="isLoading" inline transition> |
|
|
|
<div class="w-full h-full flex items-center justify-center"> |
|
|
|
<div class="w-full h-full flex items-center justify-center"> |
|
|
|
<a-spin size="large" /> |
|
|
|
<a-spin size="large" /> |
|
|
|
</div> |
|
|
|
</div> |
|
|
@ -132,22 +148,16 @@ const onDecode = async (scannedCodeValue: string) => { |
|
|
|
|
|
|
|
|
|
|
|
<div class="nc-form-wrapper"> |
|
|
|
<div class="nc-form-wrapper"> |
|
|
|
<div class="nc-form h-full"> |
|
|
|
<div class="nc-form h-full"> |
|
|
|
<div class="flex flex-col gap-6"> |
|
|
|
<div class="flex flex-col gap-3 md:gap-6"> |
|
|
|
<div v-for="(field, index) in formColumns" :key="index" class="flex flex-col gap-2"> |
|
|
|
<div v-for="(field, index) in formColumns" :key="index" class="flex flex-col gap-2"> |
|
|
|
<div class="flex nc-form-column-label"> |
|
|
|
<div class="nc-form-column-label text-sm font-semibold text-gray-800"> |
|
|
|
<LazySmartsheetHeaderVirtualCell |
|
|
|
<span> |
|
|
|
v-if="isVirtualCol(field)" |
|
|
|
{{ field.label || field.title }} |
|
|
|
:column="{ ...field, title: field.label || field.title }" |
|
|
|
</span> |
|
|
|
:required="isRequired(field, field.required)" |
|
|
|
<span v-if="isRequired(field, field.required)" class="text-red-500 text-base leading-[18px]"> *</span> |
|
|
|
:hide-menu="true" |
|
|
|
</div> |
|
|
|
/> |
|
|
|
<div v-if="field?.description" class="nc-form-column-description text-gray-500 text-sm"> |
|
|
|
|
|
|
|
{{ field?.description }} |
|
|
|
<LazySmartsheetHeaderCell |
|
|
|
|
|
|
|
v-else |
|
|
|
|
|
|
|
:column="{ ...field, title: field.label || field.title }" |
|
|
|
|
|
|
|
:required="isRequired(field, field.required)" |
|
|
|
|
|
|
|
:hide-menu="true" |
|
|
|
|
|
|
|
/> |
|
|
|
|
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div> |
|
|
|
<div> |
|
|
@ -182,34 +192,59 @@ const onDecode = async (scannedCodeValue: string) => { |
|
|
|
</a-button> |
|
|
|
</a-button> |
|
|
|
</LazySmartsheetDivDataCell> |
|
|
|
</LazySmartsheetDivDataCell> |
|
|
|
|
|
|
|
|
|
|
|
<div |
|
|
|
<div class="flex flex-col gap-2 text-slate-500 dark:text-slate-300 text-sm mt-2"> |
|
|
|
class="flex flex-col gap-2 text-slate-500 dark:text-slate-300 text-[0.75rem] my-2 px-1 leading-[18px]" |
|
|
|
<template v-if="isVirtualCol(field)"> |
|
|
|
style="word-break: break-word" |
|
|
|
<div v-for="error of v$.virtual[field.title]?.$errors" :key="`${error}virtual`" class="text-red-500"> |
|
|
|
> |
|
|
|
{{ error.$message }} |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</template> |
|
|
|
|
|
|
|
<template v-else> |
|
|
|
<div v-for="error of v$.localState[field.title]?.$errors" :key="error" class="text-red-500"> |
|
|
|
<div v-for="error of v$.localState[field.title]?.$errors" :key="error" class="text-red-500"> |
|
|
|
{{ error.$message }} |
|
|
|
{{ error.$message }} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
</template> |
|
|
|
{{ field.description }} |
|
|
|
|
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div class="text-center mt-4"> |
|
|
|
<div class="flex justify-between items-center mt-6"> |
|
|
|
<NcButton type="primary" html-type="submit" data-testid="shared-form-submit-button" @click="submitForm"> |
|
|
|
<NcButton |
|
|
|
|
|
|
|
html-type="reset" |
|
|
|
|
|
|
|
type="secondary" |
|
|
|
|
|
|
|
size="small" |
|
|
|
|
|
|
|
:disabled="isLoading" |
|
|
|
|
|
|
|
class="nc-shared-form-button shared-form-clear-button" |
|
|
|
|
|
|
|
data-testid="shared-form-clear-button" |
|
|
|
|
|
|
|
@click="clearForm" |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
{{ $t('activity.clearForm') }} |
|
|
|
|
|
|
|
</NcButton> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<NcButton |
|
|
|
|
|
|
|
html-type="submit" |
|
|
|
|
|
|
|
:disabled="progress" |
|
|
|
|
|
|
|
type="primary" |
|
|
|
|
|
|
|
size="small" |
|
|
|
|
|
|
|
class="nc-shared-form-button shared-form-submit-button" |
|
|
|
|
|
|
|
data-testid="shared-form-submit-button" |
|
|
|
|
|
|
|
@click="submitForm" |
|
|
|
|
|
|
|
> |
|
|
|
{{ $t('general.submit') }} |
|
|
|
{{ $t('general.submit') }} |
|
|
|
</NcButton> |
|
|
|
</NcButton> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div> |
|
|
|
|
|
|
|
<a-divider class="!my-6 !md:my-8" /> |
|
|
|
|
|
|
|
<div class="inline-block"> |
|
|
|
|
|
|
|
<GeneralFormBranding /> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</div> |
|
|
|
</template> |
|
|
|
</template> |
|
|
|
</template> |
|
|
|
</template> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div> </div> |
|
|
|
<div class="flex items-end"> |
|
|
|
|
|
|
|
<GeneralPoweredBy /> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
</template> |
|
|
|
|
|
|
|
|
|
|
@ -221,4 +256,10 @@ const onDecode = async (scannedCodeValue: string) => { |
|
|
|
@apply h-auto; |
|
|
|
@apply h-auto; |
|
|
|
@apply ml-1; |
|
|
|
@apply ml-1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.nc-shared-form-button { |
|
|
|
|
|
|
|
&.nc-button.ant-btn:focus { |
|
|
|
|
|
|
|
box-shadow: 0px 0px 0px 2px #fff, 0px 0px 0px 4px #3069fe; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
</style> |
|
|
|
</style> |
|
|
|