Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 570 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 631 B |
@ -0,0 +1,114 @@
|
||||
<script lang="ts" setup> |
||||
import HasManyIcon from '~icons/nc-icons/hasmany' |
||||
import ManytoManyIcon from '~icons/nc-icons/manytomany' |
||||
import OnetoOneIcon from '~icons/nc-icons/onetoone' |
||||
import BelongsToIcon from '~icons/nc-icons/belongsto' |
||||
import InfoIcon from '~icons/nc-icons/info' |
||||
import FileIcon from '~icons/nc-icons/file' |
||||
|
||||
const { relation, relatedTableTitle, displayValue, showHeader, tableTitle } = defineProps<{ |
||||
relation: string |
||||
showHeader?: boolean |
||||
tableTitle: string |
||||
relatedTableTitle: string |
||||
displayValue?: string |
||||
}>() |
||||
|
||||
const relationMeta = computed(() => { |
||||
if (relation === 'hm') { |
||||
return { |
||||
title: 'Has Many Relation', |
||||
icon: HasManyIcon, |
||||
tooltip_desc: 'A single record from table ', |
||||
tooltip_desc2: ' can be linked with a multiple records from table ', |
||||
} |
||||
} else if (relation === 'mm') { |
||||
return { |
||||
title: 'Many to Many Relation', |
||||
icon: ManytoManyIcon, |
||||
tooltip_desc: 'Multiple records from table ', |
||||
tooltip_desc2: ' can be linked with multiple records from table ', |
||||
} |
||||
} else if (relation === 'bt') { |
||||
return { |
||||
title: 'Belongs to Relation', |
||||
icon: BelongsToIcon, |
||||
tooltip_desc: 'A single record from table ', |
||||
tooltip_desc2: ' can be linked with a record from table ', |
||||
} |
||||
} else { |
||||
return { |
||||
title: 'One to One Relation', |
||||
icon: OnetoOneIcon, |
||||
tooltip_desc: 'A single record from table ', |
||||
tooltip_desc2: ' can be linked with a single record from table ', |
||||
} |
||||
} |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="flex justify-between relative pb-2 items-center"> |
||||
<h2 class="text-md font-semibold"> |
||||
{{ showHeader ? 'Linked Records' : '' }} |
||||
</h2> |
||||
<div class="grid grid-cols-[1fr,auto,1fr] justify-center items-center gap-2 absolute inset-0 m-auto"> |
||||
<div class="flex justify-end"> |
||||
<div class="flex flex-shrink-0 rounded-md gap-1 text-brand-500 items-center bg-gray-100 px-2 py-1"> |
||||
<FileIcon class="w-4 h-4" /> |
||||
{{ displayValue }} |
||||
</div> |
||||
</div> |
||||
<NcTooltip class="flex-shrink-0"> |
||||
<template #title> {{ relationMeta.title }} </template> |
||||
<component |
||||
:is="relationMeta.icon" |
||||
class="w-7 h-7 p-1 rounded-md" |
||||
:class="{ |
||||
'!bg-orange-500': relation === 'hm', |
||||
'!bg-pink-500': relation === 'mm', |
||||
'!bg-blue-500': relation === 'bt', |
||||
}" |
||||
/> |
||||
</NcTooltip> |
||||
<div class="flex justify-start"> |
||||
<div |
||||
class="flex rounded-md flex-shrink-0 gap-1 items-center px-2 py-1" |
||||
:class="{ |
||||
'!bg-orange-50 !text-orange-500': relation === 'hm', |
||||
'!bg-pink-50 !text-pink-500': relation === 'mm', |
||||
'!bg-blue-50 !text-blue-500': relation === 'bt', |
||||
}" |
||||
> |
||||
<MdiFileDocumentMultipleOutline |
||||
class="w-4 h-4" |
||||
:class="{ |
||||
'!text-orange-500': relation === 'hm', |
||||
'!text-pink-500': relation === 'mm', |
||||
'!text-blue-500': relation === 'bt', |
||||
}" |
||||
/> |
||||
{{ relatedTableTitle }} Records |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<NcTooltip class="z-10" placement="bottom"> |
||||
<template #title> |
||||
<div class="p-1"> |
||||
<h1 class="text-white font-bold">{{ relationMeta.title }}</h1> |
||||
<div class="text-white"> |
||||
{{ relationMeta.tooltip_desc }} |
||||
<span class="bg-gray-700 px-2 rounded-md"> |
||||
{{ tableTitle }} |
||||
</span> |
||||
{{ relationMeta.tooltip_desc2 }} |
||||
<span class="bg-gray-700 px-2 rounded-md"> |
||||
{{ relatedTableTitle }} |
||||
</span> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<InfoIcon class="w-4 h-4" /> |
||||
</NcTooltip> |
||||
</div> |
||||
</template> |
@ -0,0 +1,130 @@
|
||||
<script lang="ts" setup> |
||||
import { isVirtualCol } from 'nocodb-sdk' |
||||
import { IsFormInj, isImage, useAttachment } from '#imports' |
||||
import MaximizeIcon from '~icons/nc-icons/maximize' |
||||
|
||||
const { row, fields, relatedTableDisplayValueProp, isLoading, isLinked, attachment } = defineProps<{ |
||||
row: any |
||||
fields: any[] |
||||
attachment: any |
||||
relatedTableDisplayValueProp: string |
||||
isLoading: boolean |
||||
isLinked: boolean |
||||
}>() |
||||
defineEmits(['expand']) |
||||
|
||||
provide(IsExpandedFormOpenInj, ref(true)) |
||||
|
||||
const isForm = inject(IsFormInj, ref(false)) |
||||
|
||||
provide(RowHeightInj, ref(1 as const)) |
||||
|
||||
const isPublic = inject(IsPublicInj, ref(false)) |
||||
|
||||
const { getPossibleAttachmentSrc } = useAttachment() |
||||
|
||||
interface Attachment { |
||||
url: string |
||||
title: string |
||||
type: string |
||||
mimetype: string |
||||
} |
||||
|
||||
const attachments: Attachment[] = computed(() => { |
||||
try { |
||||
if (attachment && row[attachment.title]) { |
||||
return typeof row[attachment.title] === 'string' ? JSON.parse(row[attachment.title]) : row[attachment.title] |
||||
} |
||||
return [] |
||||
} catch (e) { |
||||
return [] |
||||
} |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<a-card |
||||
class="!border-1 group transition-all !rounded-xl relative !mb-4 !border-gray-200 hover:bg-gray-50" |
||||
:class="{ |
||||
'!bg-white !border-blue-500': isLoading, |
||||
'!border-brand-500 !bg-brand-50 !border-2 !hover:bg-brand-50 !hover:border-brand-500': isLinked, |
||||
'!hover:border-gray-400': !isLinked, |
||||
}" |
||||
:body-style="{ padding: 0 }" |
||||
:hoverable="false" |
||||
> |
||||
<div class="flex flex-row items-center gap-2 w-full"> |
||||
<a-carousel |
||||
v-if="attachment && attachments && attachments.length" |
||||
autoplay |
||||
class="!w-24 border-1 bg-white border-gray-200 !rounded-md !h-24" |
||||
> |
||||
<template #customPaging> </template> |
||||
<template v-for="(attachmen, index) in attachments"> |
||||
<LazyCellAttachmentImage |
||||
v-if="isImage(attachmen.title, attachmen.mimetype ?? attachmen.type)" |
||||
:key="`carousel-${attachmen.title}-${index}`" |
||||
class="!h-24 !w-24 object-cover !rounded-md" |
||||
:srcs="getPossibleAttachmentSrc(attachmen)" |
||||
/> |
||||
</template> |
||||
</a-carousel> |
||||
<div v-else-if="attachment" class="h-24 w-24 w-full !flex flex-row items-center justify-center"> |
||||
<img class="object-contain h-24 w-24" src="~assets/icons/FileIconImageBox.png" /> |
||||
</div> |
||||
<div class="flex flex-col gap-1 flex-grow justify-center"> |
||||
<div class="flex justify-between"> |
||||
<span class="font-bold text-gray-800 ml-1 nc-display-value"> {{ row[relatedTableDisplayValueProp] }} </span> |
||||
<MdiCheck |
||||
v-if="isLinked" |
||||
:class="{ |
||||
'!group-hover:mr-8': fields.length === 0, |
||||
}" |
||||
class="w-6 h-6 !text-brand-500" |
||||
/> |
||||
<MdiLoading |
||||
v-else-if="isLoading" |
||||
:class="{ |
||||
'!group-hover:mr-8': fields.length === 0, |
||||
}" |
||||
class="w-6 h-6 !text-brand-500 animate-spin" |
||||
/> |
||||
</div> |
||||
|
||||
<div v-if="fields.length > 0 && !isPublic && !isForm" class="flex flex-row gap-4 w-10/12"> |
||||
<div v-for="field in fields" :key="field.id" :class="attachment ? 'w-1/3' : 'w-1/4'"> |
||||
<div class="flex flex-col gap-[-1] max-w-72"> |
||||
<LazySmartsheetHeaderVirtualCell |
||||
v-if="isVirtualCol(field)" |
||||
class="!scale-60" |
||||
:column="field" |
||||
:hide-menu="true" |
||||
:hide-icon="true" |
||||
/> |
||||
<LazySmartsheetHeaderCell v-else class="!scale-70" :column="field" :hide-menu="true" :hide-icon="true" /> |
||||
|
||||
<LazySmartsheetVirtualCell v-if="isVirtualCol(field)" v-model="row[field.title]" :row="row" :column="field" /> |
||||
<LazySmartsheetCell |
||||
v-else |
||||
v-model="row[field.title]" |
||||
class="!text-gray-600 ml-1" |
||||
:column="field" |
||||
:edit-enabled="false" |
||||
:read-only="true" |
||||
/> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<NcButton |
||||
v-if="!isForm && !isPublic" |
||||
type="text" |
||||
size="lg" |
||||
class="!px-2 nc-expand-item !group-hover:block !hidden !absolute right-1 bottom-1" |
||||
@click.stop="$emit('expand', row)" |
||||
> |
||||
<MaximizeIcon class="w-4 h-4" /> |
||||
</NcButton> |
||||
</a-card> |
||||
</template> |