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> |