pukaad-ui-lib 1.317.0 → 1.318.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.json +1 -1
- package/dist/module.mjs +5 -0
- package/dist/runtime/assets/svg/flags/cn.svg +15 -0
- package/dist/runtime/assets/svg/flags/th.svg +12 -0
- package/dist/runtime/components/button.vue +10 -10
- package/dist/runtime/components/carousel.vue +31 -31
- package/dist/runtime/components/display/display-image-place.vue +98 -98
- package/dist/runtime/components/drawer/drawer-suggest-place/drawer-suggest-place.vue +2 -2
- package/dist/runtime/components/drawer/drawer-suggest-place/suggest-place-form.d.vue.ts +2 -2
- package/dist/runtime/components/drawer/drawer-suggest-place/suggest-place-form.vue +78 -43
- package/dist/runtime/components/drawer/drawer-suggest-place/suggest-place-form.vue.d.ts +2 -2
- package/dist/runtime/components/input/input-address.d.vue.ts +1 -1
- package/dist/runtime/components/input/input-address.vue.d.ts +1 -1
- package/dist/runtime/components/input/input-file.d.vue.ts +1 -1
- package/dist/runtime/components/input/input-file.vue.d.ts +1 -1
- package/dist/runtime/components/input/input-link.d.vue.ts +4 -0
- package/dist/runtime/components/input/input-link.vue +14 -38
- package/dist/runtime/components/input/input-link.vue.d.ts +4 -0
- package/dist/runtime/components/input/input-localized-name.d.vue.ts +21 -0
- package/dist/runtime/components/input/input-localized-name.vue +85 -0
- package/dist/runtime/components/input/input-localized-name.vue.d.ts +21 -0
- package/dist/runtime/components/input/input-password.d.vue.ts +1 -1
- package/dist/runtime/components/input/input-password.vue.d.ts +1 -1
- package/dist/runtime/components/input/input-rating.vue +6 -6
- package/dist/runtime/components/input/input-suggest.d.vue.ts +32 -0
- package/dist/runtime/components/input/input-suggest.vue +168 -0
- package/dist/runtime/components/input/input-suggest.vue.d.ts +32 -0
- package/dist/runtime/components/input/input-typed-field.d.vue.ts +29 -0
- package/dist/runtime/components/input/input-typed-field.vue +76 -0
- package/dist/runtime/components/input/input-typed-field.vue.d.ts +29 -0
- package/dist/runtime/components/modal/modal-media-view.vue +136 -136
- package/dist/runtime/components/modal/modal-password-confirmed.d.vue.ts +1 -1
- package/dist/runtime/components/modal/modal-password-confirmed.vue.d.ts +1 -1
- package/dist/runtime/components/modal/modal-password-verify.d.vue.ts +1 -1
- package/dist/runtime/components/modal/modal-password-verify.vue.d.ts +1 -1
- package/package.json +1 -1
- /package/dist/runtime/assets/svg/socials/{WhatsApp.svg → Whatsapp.svg} +0 -0
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -95,6 +95,11 @@ const module$1 = defineNuxtModule({
|
|
|
95
95
|
prefix: "pukaad-social",
|
|
96
96
|
dir: resolver.resolve("./runtime/assets/svg/socials"),
|
|
97
97
|
normalizeIconName: false
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
prefix: "pukaad-flag",
|
|
101
|
+
dir: resolver.resolve("./runtime/assets/svg/flags"),
|
|
102
|
+
normalizeIconName: false
|
|
98
103
|
}
|
|
99
104
|
]
|
|
100
105
|
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g clip-path="url(#clip0_8131_652)">
|
|
3
|
+
<path d="M0 0H16V16H0V0Z" fill="#EE1C25"/>
|
|
4
|
+
<path d="M6.39998 4.21748L2.31998 6.85748L3.99998 2.53748L5.43998 6.85748L1.59998 4.21748H6.39998Z" fill="#FFFF00"/>
|
|
5
|
+
<path d="M7.393 1.97026L8.84678 2.68229L7.32506 2.94376L8.3115 1.79144L8.21651 3.3408L7.393 1.97026Z" fill="#FFFF00"/>
|
|
6
|
+
<path d="M9.24584 3.38149L10.3087 4.60393L8.80415 4.25241L10.1622 3.57431L9.47128 4.96553L9.24584 3.38149Z" fill="#FFFF00"/>
|
|
7
|
+
<path d="M9.58044 5.7041L10.0517 7.2548L8.82121 6.31908L10.3385 6.25456L9.13918 7.24294L9.58044 5.7041Z" fill="#FFFF00"/>
|
|
8
|
+
<path d="M8.31278 7.36347L8.14918 8.97427L7.37598 7.63747L8.79918 8.16307L7.31278 8.61147L8.31278 7.36347Z" fill="#FFFF00"/>
|
|
9
|
+
</g>
|
|
10
|
+
<defs>
|
|
11
|
+
<clipPath id="clip0_8131_652">
|
|
12
|
+
<rect width="16" height="16" rx="8" fill="white"/>
|
|
13
|
+
</clipPath>
|
|
14
|
+
</defs>
|
|
15
|
+
</svg>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g clip-path="url(#clip0_8131_1050)">
|
|
3
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H16V16H0V0Z" fill="#F4F5F8"/>
|
|
4
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 5.4187H16V10.75H0V5.4187Z" fill="#2D2A4A"/>
|
|
5
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H16V2.75H0V0ZM0 13.3344H16V16H0V13.3344Z" fill="#A51931"/>
|
|
6
|
+
</g>
|
|
7
|
+
<defs>
|
|
8
|
+
<clipPath id="clip0_8131_1050">
|
|
9
|
+
<rect width="16" height="16" rx="8" fill="white"/>
|
|
10
|
+
</clipPath>
|
|
11
|
+
</defs>
|
|
12
|
+
</svg>
|
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
props.variant === 'text' || props.variant === 'link' ? '!p-0 !h-auto' : '',
|
|
5
5
|
props.circle && 'rounded-full aspect-square',
|
|
6
6
|
props.class
|
|
7
|
-
]" :variant="props.variant" :color="props.color" :size="resolvedSize" :type="props.type"
|
|
8
|
-
:disabled="props.loading || props.disabled">
|
|
9
|
-
<span class="inline-grid place-items-center">
|
|
10
|
-
<span class="inline-flex items-center justify-center gap-[8px] [grid-area:1/1]"
|
|
11
|
-
:class="{ invisible: props.loading }">
|
|
12
|
-
<slot />
|
|
13
|
-
</span>
|
|
14
|
-
<ShadSpinner v-if="props.loading" class="[grid-area:1/1]" />
|
|
15
|
-
</span>
|
|
16
|
-
</ShadButton>
|
|
7
|
+
]" :variant="props.variant" :color="props.color" :size="resolvedSize" :type="props.type"
|
|
8
|
+
:disabled="props.loading || props.disabled">
|
|
9
|
+
<span class="inline-grid place-items-center">
|
|
10
|
+
<span class="inline-flex items-center justify-center gap-[8px] [grid-area:1/1]"
|
|
11
|
+
:class="{ invisible: props.loading }">
|
|
12
|
+
<slot />
|
|
13
|
+
</span>
|
|
14
|
+
<ShadSpinner v-if="props.loading" class="[grid-area:1/1]" />
|
|
15
|
+
</span>
|
|
16
|
+
</ShadButton>
|
|
17
17
|
</template>
|
|
18
18
|
|
|
19
19
|
<script setup>
|
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="relative w-full h-full">
|
|
3
|
-
<div class="absolute top-[20px] left-1/2 transform -translate-x-1/2 z-[50]">
|
|
4
|
-
<div class="rounded-full py-[6px] px-[16px] bg-black/60 flex items-center justify-center">
|
|
5
|
-
<span class="text-white text-sm font-medium tracking-widest">
|
|
6
|
-
{{ currentIndex + 1 }} / {{ props.items.length }}
|
|
7
|
-
</span>
|
|
8
|
-
</div>
|
|
9
|
-
</div>
|
|
10
|
-
|
|
11
|
-
<ShadCarousel
|
|
12
|
-
:opts="{ startIndex: props.selectIndex }"
|
|
13
|
-
class="carousel-media w-full h-full"
|
|
14
|
-
@init-api="onInitApi"
|
|
15
|
-
>
|
|
16
|
-
<ShadCarouselContent class="h-full">
|
|
17
|
-
<ShadCarouselItem
|
|
18
|
-
v-for="(item, i) in props.items"
|
|
19
|
-
:key="i"
|
|
20
|
-
class="h-full flex items-center justify-center"
|
|
21
|
-
>
|
|
22
|
-
<img
|
|
23
|
-
:src="item.url"
|
|
24
|
-
class="w-full h-full object-contain select-none"
|
|
25
|
-
draggable="false"
|
|
26
|
-
/>
|
|
27
|
-
</ShadCarouselItem>
|
|
28
|
-
</ShadCarouselContent>
|
|
29
|
-
<ShadCarouselPrevious v-if="props.items.length > 1" class="!left-[12px]" />
|
|
30
|
-
<ShadCarouselNext v-if="props.items.length > 1" class="!right-[12px]" />
|
|
31
|
-
</ShadCarousel>
|
|
32
|
-
</div>
|
|
2
|
+
<div class="relative w-full h-full">
|
|
3
|
+
<div class="absolute top-[20px] left-1/2 transform -translate-x-1/2 z-[50]">
|
|
4
|
+
<div class="rounded-full py-[6px] px-[16px] bg-black/60 flex items-center justify-center">
|
|
5
|
+
<span class="text-white text-sm font-medium tracking-widest">
|
|
6
|
+
{{ currentIndex + 1 }} / {{ props.items.length }}
|
|
7
|
+
</span>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<ShadCarousel
|
|
12
|
+
:opts="{ startIndex: props.selectIndex }"
|
|
13
|
+
class="carousel-media w-full h-full"
|
|
14
|
+
@init-api="onInitApi"
|
|
15
|
+
>
|
|
16
|
+
<ShadCarouselContent class="h-full">
|
|
17
|
+
<ShadCarouselItem
|
|
18
|
+
v-for="(item, i) in props.items"
|
|
19
|
+
:key="i"
|
|
20
|
+
class="h-full flex items-center justify-center"
|
|
21
|
+
>
|
|
22
|
+
<img
|
|
23
|
+
:src="item.url"
|
|
24
|
+
class="w-full h-full object-contain select-none"
|
|
25
|
+
draggable="false"
|
|
26
|
+
/>
|
|
27
|
+
</ShadCarouselItem>
|
|
28
|
+
</ShadCarouselContent>
|
|
29
|
+
<ShadCarouselPrevious v-if="props.items.length > 1" class="!left-[12px]" />
|
|
30
|
+
<ShadCarouselNext v-if="props.items.length > 1" class="!right-[12px]" />
|
|
31
|
+
</ShadCarousel>
|
|
32
|
+
</div>
|
|
33
33
|
</template>
|
|
34
34
|
|
|
35
35
|
<script setup>
|
|
@@ -1,102 +1,102 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<!-- Personal state → Featured layout — 1 รูป: w-full -->
|
|
3
|
-
<div v-if="layoutMode === 'featured' && photos.length === 1" class="h-[360px]">
|
|
4
|
-
<div class="w-full h-full rounded-[8px] overflow-hidden cursor-pointer" @click="openLightbox('photo', 0)">
|
|
5
|
-
<Image :src="photos[0].url" class="w-full h-full object-cover" width="auto" height="auto" />
|
|
6
|
-
</div>
|
|
7
|
-
</div>
|
|
8
|
-
|
|
9
|
-
<!-- Personal state → Featured layout — 2 รูป: แบ่งครึ่ง w-full -->
|
|
10
|
-
<div v-else-if="layoutMode === 'featured' && photos.length === 2" class="flex gap-[8px] h-[360px]">
|
|
11
|
-
<div class="flex-1 rounded-[8px] overflow-hidden cursor-pointer" @click="openLightbox('photo', 0)">
|
|
12
|
-
<Image :src="photos[0].url" class="w-full h-full object-cover" width="auto" height="auto" />
|
|
13
|
-
</div>
|
|
14
|
-
<div class="flex-1 rounded-[8px] overflow-hidden cursor-pointer" @click="openLightbox('photo', 1)">
|
|
15
|
-
<Image :src="photos[1].url" class="w-full h-full object-cover" width="auto" height="auto" />
|
|
16
|
-
</div>
|
|
17
|
-
</div>
|
|
18
|
-
|
|
19
|
-
<!-- Personal state → Featured layout — 3+ รูป: layout เดิม -->
|
|
20
|
-
<div v-else-if="layoutMode === 'featured'" class="flex gap-[8px] h-[360px]">
|
|
21
|
-
<div v-if="photos[0]" class="w-[620px] shrink-0 rounded-[8px] overflow-hidden cursor-pointer"
|
|
22
|
-
@click="openLightbox('photo', 0)">
|
|
23
|
-
<Image :src="photos[0].url" class="w-full h-full object-cover" width="auto" height="auto" />
|
|
24
|
-
</div>
|
|
25
|
-
|
|
26
|
-
<div v-if="photos[1]" class="flex-1 rounded-[8px] overflow-hidden cursor-pointer" @click="openLightbox('photo', 1)">
|
|
27
|
-
<Image :src="photos[1].url" class="w-full h-full object-cover" width="auto" height="auto" />
|
|
28
|
-
</div>
|
|
29
|
-
|
|
30
|
-
<div class="w-[306px] shrink-0 flex flex-col gap-[8px]">
|
|
31
|
-
<div v-if="photos[2]" class="flex-1 min-h-0 relative rounded-[8px] overflow-hidden cursor-pointer"
|
|
32
|
-
@click="openLightbox('photo', 2)">
|
|
33
|
-
<Image :src="photos[2].url" class="w-full h-full object-cover" width="auto" height="auto" />
|
|
34
|
-
<div v-if="featuredPhoto2Overflow > 0"
|
|
35
|
-
class="absolute inset-0 bg-black/50 flex flex-col items-center justify-center text-white gap-[6px]">
|
|
36
|
-
<Icon name="fa6-solid:image" :size="42" />
|
|
37
|
-
<span class="font-body-large">รูปภาพ ({{ convertNumber(featuredPhoto2Overflow) }})</span>
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
40
|
-
|
|
41
|
-
<div v-if="videos[0]" class="flex-1 min-h-0 relative rounded-[8px] overflow-hidden cursor-pointer"
|
|
42
|
-
@click="openLightbox('video', 0)">
|
|
43
|
-
<Image :src="videos[0].url" class="w-full h-full object-cover" width="auto" height="auto" />
|
|
44
|
-
<div class="absolute inset-0 bg-black/50 flex flex-col items-center justify-center text-white gap-[6px]">
|
|
45
|
-
<Icon name="fa6-solid:video" :size="42" />
|
|
46
|
-
<span class="font-body-large">วิดีโอ ({{ videos.length }})</span>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
<div v-else-if="photos[3]" class="flex-1 min-h-0 relative rounded-[8px] overflow-hidden cursor-pointer"
|
|
50
|
-
@click="openLightbox('photo', 3)">
|
|
51
|
-
<Image :src="photos[3].url" class="w-full h-full object-cover" width="auto" height="auto" />
|
|
52
|
-
<div v-if="featuredPhoto3Overflow > 0"
|
|
53
|
-
class="absolute inset-0 bg-black/50 flex flex-col items-center justify-center text-white gap-[6px]">
|
|
54
|
-
<Icon name="fa6-solid:image" :size="42" />
|
|
55
|
-
<span class="font-body-large">รูปภาพ ({{ convertNumber(featuredPhoto3Overflow) }})</span>
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
|
|
61
|
-
<!-- Office / Business state → Album layout -->
|
|
62
|
-
<div v-else-if="layoutMode === 'album'" class="grid grid-cols-5 gap-[10px]">
|
|
63
|
-
<!-- Row 1: photos[0..3] -->
|
|
64
|
-
<div v-for="(photo, i) in galleryRow1" :key="`g1-${i}`"
|
|
65
|
-
class="w-[132px] h-[132px] rounded-[8px] overflow-hidden cursor-pointer" @click="openLightbox('photo', i)">
|
|
66
|
-
<Image :src="photo.url" class="w-full h-full object-cover" width="auto" height="auto" />
|
|
67
|
-
</div>
|
|
68
|
-
|
|
69
|
-
<!-- Row 1 col 5: image count overlay -->
|
|
70
|
-
<div v-show="galleryCountPhoto != null"
|
|
71
|
-
class="w-[132px] h-[132px] relative rounded-[8px] overflow-hidden cursor-pointer"
|
|
72
|
-
@click="openLightbox('photo', 8)">
|
|
73
|
-
<Image v-if="galleryCountPhoto" :src="galleryCountPhoto.url" class="w-full h-full object-cover" width="auto"
|
|
74
|
-
height="auto" />
|
|
75
|
-
<div class="absolute inset-0 bg-black/50 flex flex-col items-center justify-center text-white gap-[6px]">
|
|
76
|
-
<Icon name="fa6-solid:image" :size="35" />
|
|
77
|
-
<span class="font-label-medium">รูปภาพ ({{ convertNumber(photos.length - 8) }})</span>
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
|
|
81
|
-
<!-- Row 2: photos[4..7] -->
|
|
82
|
-
<div v-for="(photo, i) in galleryRow2" :key="`g2-${i}`"
|
|
83
|
-
class="w-[132px] h-[132px] rounded-[8px] overflow-hidden cursor-pointer" @click="openLightbox('photo', 4 + i)">
|
|
84
|
-
<Image :src="photo.url" class="w-full h-full object-cover" width="auto" height="auto" />
|
|
85
|
-
</div>
|
|
86
|
-
|
|
87
|
-
<!-- Row 2 col 5: video overlay -->
|
|
88
|
-
<div v-if="videos[0]" class="w-[132px] h-[132px] relative rounded-[8px] overflow-hidden cursor-pointer"
|
|
89
|
-
@click="openLightbox('video', 0)">
|
|
90
|
-
<Image :src="videos[0].url" class="w-full h-full object-cover" width="auto" height="auto" />
|
|
91
|
-
<div class="absolute inset-0 bg-black/50 flex flex-col items-center justify-center text-white gap-[6px]">
|
|
92
|
-
<Icon name="fa6-solid:video" :size="35" />
|
|
93
|
-
<span class="font-label-medium">วิดีโอ ({{ videos.length }})</span>
|
|
94
|
-
</div>
|
|
95
|
-
</div>
|
|
96
|
-
</div>
|
|
97
|
-
|
|
98
|
-
<!-- Lightbox modal -->
|
|
99
|
-
<ModalMediaView v-model="isMediaOpen" :items="mediaItems" :title="props.title" :start-index="mediaStartIndex" />
|
|
2
|
+
<!-- Personal state → Featured layout — 1 รูป: w-full -->
|
|
3
|
+
<div v-if="layoutMode === 'featured' && photos.length === 1" class="h-[360px]">
|
|
4
|
+
<div class="w-full h-full rounded-[8px] overflow-hidden cursor-pointer" @click="openLightbox('photo', 0)">
|
|
5
|
+
<Image :src="photos[0].url" class="w-full h-full object-cover" width="auto" height="auto" />
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<!-- Personal state → Featured layout — 2 รูป: แบ่งครึ่ง w-full -->
|
|
10
|
+
<div v-else-if="layoutMode === 'featured' && photos.length === 2" class="flex gap-[8px] h-[360px]">
|
|
11
|
+
<div class="flex-1 rounded-[8px] overflow-hidden cursor-pointer" @click="openLightbox('photo', 0)">
|
|
12
|
+
<Image :src="photos[0].url" class="w-full h-full object-cover" width="auto" height="auto" />
|
|
13
|
+
</div>
|
|
14
|
+
<div class="flex-1 rounded-[8px] overflow-hidden cursor-pointer" @click="openLightbox('photo', 1)">
|
|
15
|
+
<Image :src="photos[1].url" class="w-full h-full object-cover" width="auto" height="auto" />
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<!-- Personal state → Featured layout — 3+ รูป: layout เดิม -->
|
|
20
|
+
<div v-else-if="layoutMode === 'featured'" class="flex gap-[8px] h-[360px]">
|
|
21
|
+
<div v-if="photos[0]" class="w-[620px] shrink-0 rounded-[8px] overflow-hidden cursor-pointer"
|
|
22
|
+
@click="openLightbox('photo', 0)">
|
|
23
|
+
<Image :src="photos[0].url" class="w-full h-full object-cover" width="auto" height="auto" />
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div v-if="photos[1]" class="flex-1 rounded-[8px] overflow-hidden cursor-pointer" @click="openLightbox('photo', 1)">
|
|
27
|
+
<Image :src="photos[1].url" class="w-full h-full object-cover" width="auto" height="auto" />
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div class="w-[306px] shrink-0 flex flex-col gap-[8px]">
|
|
31
|
+
<div v-if="photos[2]" class="flex-1 min-h-0 relative rounded-[8px] overflow-hidden cursor-pointer"
|
|
32
|
+
@click="openLightbox('photo', 2)">
|
|
33
|
+
<Image :src="photos[2].url" class="w-full h-full object-cover" width="auto" height="auto" />
|
|
34
|
+
<div v-if="featuredPhoto2Overflow > 0"
|
|
35
|
+
class="absolute inset-0 bg-black/50 flex flex-col items-center justify-center text-white gap-[6px]">
|
|
36
|
+
<Icon name="fa6-solid:image" :size="42" />
|
|
37
|
+
<span class="font-body-large">รูปภาพ ({{ convertNumber(featuredPhoto2Overflow) }})</span>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div v-if="videos[0]" class="flex-1 min-h-0 relative rounded-[8px] overflow-hidden cursor-pointer"
|
|
42
|
+
@click="openLightbox('video', 0)">
|
|
43
|
+
<Image :src="videos[0].url" class="w-full h-full object-cover" width="auto" height="auto" />
|
|
44
|
+
<div class="absolute inset-0 bg-black/50 flex flex-col items-center justify-center text-white gap-[6px]">
|
|
45
|
+
<Icon name="fa6-solid:video" :size="42" />
|
|
46
|
+
<span class="font-body-large">วิดีโอ ({{ videos.length }})</span>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
<div v-else-if="photos[3]" class="flex-1 min-h-0 relative rounded-[8px] overflow-hidden cursor-pointer"
|
|
50
|
+
@click="openLightbox('photo', 3)">
|
|
51
|
+
<Image :src="photos[3].url" class="w-full h-full object-cover" width="auto" height="auto" />
|
|
52
|
+
<div v-if="featuredPhoto3Overflow > 0"
|
|
53
|
+
class="absolute inset-0 bg-black/50 flex flex-col items-center justify-center text-white gap-[6px]">
|
|
54
|
+
<Icon name="fa6-solid:image" :size="42" />
|
|
55
|
+
<span class="font-body-large">รูปภาพ ({{ convertNumber(featuredPhoto3Overflow) }})</span>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<!-- Office / Business state → Album layout -->
|
|
62
|
+
<div v-else-if="layoutMode === 'album'" class="grid grid-cols-5 gap-[10px]">
|
|
63
|
+
<!-- Row 1: photos[0..3] -->
|
|
64
|
+
<div v-for="(photo, i) in galleryRow1" :key="`g1-${i}`"
|
|
65
|
+
class="w-[132px] h-[132px] rounded-[8px] overflow-hidden cursor-pointer" @click="openLightbox('photo', i)">
|
|
66
|
+
<Image :src="photo.url" class="w-full h-full object-cover" width="auto" height="auto" />
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<!-- Row 1 col 5: image count overlay -->
|
|
70
|
+
<div v-show="galleryCountPhoto != null"
|
|
71
|
+
class="w-[132px] h-[132px] relative rounded-[8px] overflow-hidden cursor-pointer"
|
|
72
|
+
@click="openLightbox('photo', 8)">
|
|
73
|
+
<Image v-if="galleryCountPhoto" :src="galleryCountPhoto.url" class="w-full h-full object-cover" width="auto"
|
|
74
|
+
height="auto" />
|
|
75
|
+
<div class="absolute inset-0 bg-black/50 flex flex-col items-center justify-center text-white gap-[6px]">
|
|
76
|
+
<Icon name="fa6-solid:image" :size="35" />
|
|
77
|
+
<span class="font-label-medium">รูปภาพ ({{ convertNumber(photos.length - 8) }})</span>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<!-- Row 2: photos[4..7] -->
|
|
82
|
+
<div v-for="(photo, i) in galleryRow2" :key="`g2-${i}`"
|
|
83
|
+
class="w-[132px] h-[132px] rounded-[8px] overflow-hidden cursor-pointer" @click="openLightbox('photo', 4 + i)">
|
|
84
|
+
<Image :src="photo.url" class="w-full h-full object-cover" width="auto" height="auto" />
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<!-- Row 2 col 5: video overlay -->
|
|
88
|
+
<div v-if="videos[0]" class="w-[132px] h-[132px] relative rounded-[8px] overflow-hidden cursor-pointer"
|
|
89
|
+
@click="openLightbox('video', 0)">
|
|
90
|
+
<Image :src="videos[0].url" class="w-full h-full object-cover" width="auto" height="auto" />
|
|
91
|
+
<div class="absolute inset-0 bg-black/50 flex flex-col items-center justify-center text-white gap-[6px]">
|
|
92
|
+
<Icon name="fa6-solid:video" :size="35" />
|
|
93
|
+
<span class="font-label-medium">วิดีโอ ({{ videos.length }})</span>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<!-- Lightbox modal -->
|
|
99
|
+
<ModalMediaView v-model="isMediaOpen" :items="mediaItems" :title="props.title" :start-index="mediaStartIndex" />
|
|
100
100
|
</template>
|
|
101
101
|
|
|
102
102
|
<script setup>
|
|
@@ -152,6 +152,7 @@ watch(isOpen, (val) => {
|
|
|
152
152
|
extraPhones: [],
|
|
153
153
|
contactChannels: [],
|
|
154
154
|
categories: [],
|
|
155
|
+
localizedNames: [],
|
|
155
156
|
reviewPhotos: []
|
|
156
157
|
};
|
|
157
158
|
}, 300);
|
|
@@ -189,8 +190,7 @@ const onSubmit = async () => {
|
|
|
189
190
|
method,
|
|
190
191
|
body: {
|
|
191
192
|
business_name: d.businessName ?? "",
|
|
192
|
-
|
|
193
|
-
name_en: d.nameEn ?? "",
|
|
193
|
+
localized_names: (d.localizedNames ?? []).map((ln) => ({ language: ln.language, name: ln.name })),
|
|
194
194
|
description: d.description ?? "",
|
|
195
195
|
address: {
|
|
196
196
|
province_id: d.address?.province_id,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { InputAddressValue } from "#pukaad-ui/runtime/components/input/input-address.vue";
|
|
2
|
+
import type { LocalizedNameItem } from "#pukaad-ui/types/components/input/input-localized-name";
|
|
2
3
|
interface FileItem {
|
|
3
4
|
file?: File;
|
|
4
5
|
url: string;
|
|
@@ -14,8 +15,7 @@ export interface SuggestPlaceData {
|
|
|
14
15
|
lng: number;
|
|
15
16
|
} | null;
|
|
16
17
|
businessName?: string;
|
|
17
|
-
|
|
18
|
-
nameEn?: string;
|
|
18
|
+
localizedNames?: LocalizedNameItem[];
|
|
19
19
|
categories?: {
|
|
20
20
|
id?: string;
|
|
21
21
|
value?: string;
|
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
<div class="flex gap-[16px] w-full">
|
|
3
3
|
<!-- กรอกข้อมูล -->
|
|
4
4
|
<div class="flex flex-col gap-[16px] w-[490px]">
|
|
5
|
-
<InputAddress
|
|
5
|
+
<InputAddress
|
|
6
|
+
name="address"
|
|
7
|
+
v-model="modelValue.address"
|
|
8
|
+
:fixed-province-id="props.fixedProvinceId"
|
|
9
|
+
/>
|
|
6
10
|
<template v-if="isAddressCompleted">
|
|
7
11
|
<div class="font-body-large-prominent">รายละเอียด</div>
|
|
8
12
|
<div class="flex flex-col gap-[4px]">
|
|
9
|
-
<
|
|
13
|
+
<InputSuggest
|
|
10
14
|
name="businessName"
|
|
11
15
|
v-model="modelValue.businessName"
|
|
12
16
|
label="ชื่อสถานที่ธุรกิจ"
|
|
@@ -14,33 +18,14 @@
|
|
|
14
18
|
required
|
|
15
19
|
show-counter
|
|
16
20
|
:limit="180"
|
|
17
|
-
:free-text="true"
|
|
18
21
|
:fetch-fn="fetchApprovedPlaces"
|
|
19
22
|
value-key="business_name"
|
|
20
23
|
label-key="business_name"
|
|
21
24
|
/>
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
v-model="modelValue.nameTh"
|
|
26
|
-
label="ชื่อภาษาไทย"
|
|
27
|
-
placeholder="ใส่ในกรณีที่ต่างจากชื่อหลัก"
|
|
28
|
-
show-counter
|
|
29
|
-
:limit="180"
|
|
25
|
+
<InputLocalizedName
|
|
26
|
+
name="localizedNames"
|
|
27
|
+
v-model="modelValue.localizedNames"
|
|
30
28
|
/>
|
|
31
|
-
<InputTextField
|
|
32
|
-
v-if="extraNameCount >= 2"
|
|
33
|
-
name="nameEn"
|
|
34
|
-
v-model="modelValue.nameEn"
|
|
35
|
-
label="ชื่อภาษาอังกฤษ"
|
|
36
|
-
placeholder="ใส่ในกรณีที่ต่างจากชื่อหลัก"
|
|
37
|
-
show-counter
|
|
38
|
-
:limit="180"
|
|
39
|
-
/>
|
|
40
|
-
<Button v-if="extraNameCount < 2" variant="text" color="primary" class="w-[145px]" @click="extraNameCount++">
|
|
41
|
-
<Icon name="lucide:plus" />
|
|
42
|
-
เพิ่มชื่อสถานที่
|
|
43
|
-
</Button>
|
|
44
29
|
</div>
|
|
45
30
|
<InputCombobox
|
|
46
31
|
name="categories"
|
|
@@ -61,9 +46,17 @@
|
|
|
61
46
|
:limit="220"
|
|
62
47
|
show-counter
|
|
63
48
|
/>
|
|
64
|
-
<InputDateOpening
|
|
49
|
+
<InputDateOpening
|
|
50
|
+
name="openingHours"
|
|
51
|
+
v-model="modelValue.openingHours"
|
|
52
|
+
/>
|
|
65
53
|
<div class="flex flex-col gap-[8px]">
|
|
66
|
-
<InputTextField
|
|
54
|
+
<InputTextField
|
|
55
|
+
name="phone"
|
|
56
|
+
v-model="modelValue.phone"
|
|
57
|
+
label="เบอร์โทรศัพท์"
|
|
58
|
+
placeholder="กรอกเบอร์โทรศัพท์"
|
|
59
|
+
/>
|
|
67
60
|
<InputTextField
|
|
68
61
|
v-for="(_, index) in modelValue.extraPhones"
|
|
69
62
|
:key="index"
|
|
@@ -72,17 +65,35 @@
|
|
|
72
65
|
placeholder="กรอกเบอร์โทรศัพท์"
|
|
73
66
|
v-model="modelValue.extraPhones[index]"
|
|
74
67
|
/>
|
|
75
|
-
<Button
|
|
68
|
+
<Button
|
|
69
|
+
variant="text"
|
|
70
|
+
color="primary"
|
|
71
|
+
class="w-[145px]"
|
|
72
|
+
@click="modelValue.extraPhones.push('')"
|
|
73
|
+
>
|
|
76
74
|
<Icon name="lucide:plus" />
|
|
77
75
|
เพิ่มเบอร์โทรศัพท์
|
|
78
76
|
</Button>
|
|
79
|
-
<InputLink
|
|
77
|
+
<InputLink
|
|
78
|
+
name="contactChannels"
|
|
79
|
+
format="contact_channel"
|
|
80
|
+
default-first
|
|
81
|
+
v-model="modelValue.contactChannels"
|
|
82
|
+
/>
|
|
80
83
|
</div>
|
|
81
84
|
<template v-if="props.state === 'personal'">
|
|
82
85
|
<div class="flex flex-col gap-[16px]">
|
|
83
|
-
<InputCheckbox
|
|
86
|
+
<InputCheckbox
|
|
87
|
+
name="isReview"
|
|
88
|
+
v-model="modelValue.isReview"
|
|
89
|
+
label="คุณต้องการรีวิวสถานที่นี้"
|
|
90
|
+
/>
|
|
84
91
|
<template v-if="modelValue.isReview">
|
|
85
|
-
<InputRating
|
|
92
|
+
<InputRating
|
|
93
|
+
name="rating"
|
|
94
|
+
v-model="modelValue.rating"
|
|
95
|
+
class="flex py-4 justify-center"
|
|
96
|
+
/>
|
|
86
97
|
<InputTextarea
|
|
87
98
|
name="reviewDescription"
|
|
88
99
|
v-model="modelValue.reviewDescription"
|
|
@@ -92,12 +103,20 @@
|
|
|
92
103
|
/>
|
|
93
104
|
<div class="flex flex-col gap-[8px]">
|
|
94
105
|
<div class="flex flex-col gap-[4px]">
|
|
95
|
-
<div class="font-body-large-prominent text-gray"
|
|
106
|
+
<div class="font-body-large-prominent text-gray">
|
|
107
|
+
เพิ่มภาพถ่าย
|
|
108
|
+
</div>
|
|
96
109
|
<div class="font-body-small text-gray">สูงสุด 9 รายการ</div>
|
|
97
110
|
</div>
|
|
98
|
-
<InputFile
|
|
111
|
+
<InputFile
|
|
112
|
+
name="reviewPhotos"
|
|
113
|
+
v-model="modelValue.reviewPhotos"
|
|
114
|
+
accept="image/*"
|
|
115
|
+
:limit="9"
|
|
116
|
+
/>
|
|
99
117
|
<div class="font-body-small text-gray w-[250px]">
|
|
100
|
-
รองรับไฟล์ *.jpg *.jpeg *.png *.webp *.bmp *.gif
|
|
118
|
+
รองรับไฟล์ *.jpg *.jpeg *.png *.webp *.bmp *.gif
|
|
119
|
+
ขนาดไฟล์ไม่เกิน 30 mb
|
|
101
120
|
</div>
|
|
102
121
|
</div>
|
|
103
122
|
</template>
|
|
@@ -128,9 +147,10 @@
|
|
|
128
147
|
</template>
|
|
129
148
|
|
|
130
149
|
<script setup>
|
|
131
|
-
import {
|
|
150
|
+
import { computed, onMounted, watch, reactive, ref } from "vue";
|
|
132
151
|
import { useApi } from "#pukaad-ui/runtime/composables/useApi";
|
|
133
152
|
import SuggestPlaceMap from "./suggest-place-map.vue";
|
|
153
|
+
import InputLocalizedName from "../../input/input-localized-name.vue";
|
|
134
154
|
const props = defineProps({
|
|
135
155
|
state: { type: String, required: false, default: "personal" },
|
|
136
156
|
fixedProvinceId: { type: Number, required: false }
|
|
@@ -141,18 +161,26 @@ const modelValue = reactive({
|
|
|
141
161
|
address: {},
|
|
142
162
|
extraPhones: [],
|
|
143
163
|
categories: [],
|
|
164
|
+
localizedNames: [],
|
|
144
165
|
reviewPhotos: [],
|
|
145
166
|
photos: [],
|
|
146
167
|
videos: [],
|
|
147
168
|
..._model.value
|
|
148
169
|
});
|
|
149
|
-
watch(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
170
|
+
watch(
|
|
171
|
+
_model,
|
|
172
|
+
(val) => {
|
|
173
|
+
if (val) Object.assign(modelValue, val);
|
|
174
|
+
},
|
|
175
|
+
{ deep: false }
|
|
176
|
+
);
|
|
177
|
+
watch(
|
|
178
|
+
modelValue,
|
|
179
|
+
(val) => {
|
|
180
|
+
_model.value = { ...val };
|
|
181
|
+
},
|
|
182
|
+
{ deep: true }
|
|
183
|
+
);
|
|
156
184
|
const isAddressCompleted = computed(() => {
|
|
157
185
|
const v = modelValue.address || {};
|
|
158
186
|
return !!(v.province_id && v.amphur_id && v.tambon_id && v.zipcode);
|
|
@@ -180,7 +208,12 @@ const categoryEndpointMap = {
|
|
|
180
208
|
const fetchApprovedPlaces = async (page, pageSize, search) => {
|
|
181
209
|
const base = listEndpointMap[props.state] ?? "/personal/suggest-places";
|
|
182
210
|
const res = await api(base, {
|
|
183
|
-
query: {
|
|
211
|
+
query: {
|
|
212
|
+
q: search ?? "",
|
|
213
|
+
status: "PENDING,APPROVED",
|
|
214
|
+
page,
|
|
215
|
+
page_size: pageSize
|
|
216
|
+
}
|
|
184
217
|
});
|
|
185
218
|
const data = (res.data ?? []).map((item) => ({
|
|
186
219
|
...item,
|
|
@@ -195,7 +228,9 @@ const categoryOptions = ref([]);
|
|
|
195
228
|
onMounted(async () => {
|
|
196
229
|
try {
|
|
197
230
|
const endpoint = categoryEndpointMap[props.state] ?? "/personal/suggest-places/categories";
|
|
198
|
-
const res = await api(
|
|
231
|
+
const res = await api(
|
|
232
|
+
endpoint
|
|
233
|
+
);
|
|
199
234
|
categoryOptions.value = (res.data ?? []).map((c) => ({
|
|
200
235
|
label: c.category_name,
|
|
201
236
|
value: c.id
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { InputAddressValue } from "#pukaad-ui/runtime/components/input/input-address.vue";
|
|
2
|
+
import type { LocalizedNameItem } from "#pukaad-ui/types/components/input/input-localized-name";
|
|
2
3
|
interface FileItem {
|
|
3
4
|
file?: File;
|
|
4
5
|
url: string;
|
|
@@ -14,8 +15,7 @@ export interface SuggestPlaceData {
|
|
|
14
15
|
lng: number;
|
|
15
16
|
} | null;
|
|
16
17
|
businessName?: string;
|
|
17
|
-
|
|
18
|
-
nameEn?: string;
|
|
18
|
+
localizedNames?: LocalizedNameItem[];
|
|
19
19
|
categories?: {
|
|
20
20
|
id?: string;
|
|
21
21
|
value?: string;
|
|
@@ -49,11 +49,11 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
49
49
|
label: string;
|
|
50
50
|
name: string;
|
|
51
51
|
required: boolean;
|
|
52
|
+
gap: string;
|
|
52
53
|
placeholder: string;
|
|
53
54
|
labelDetail: string;
|
|
54
55
|
placeholderDetail: string;
|
|
55
56
|
requiredDetail: boolean;
|
|
56
|
-
gap: string;
|
|
57
57
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
58
58
|
declare const _default: typeof __VLS_export;
|
|
59
59
|
export default _default;
|
|
@@ -49,11 +49,11 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
49
49
|
label: string;
|
|
50
50
|
name: string;
|
|
51
51
|
required: boolean;
|
|
52
|
+
gap: string;
|
|
52
53
|
placeholder: string;
|
|
53
54
|
labelDetail: string;
|
|
54
55
|
placeholderDetail: string;
|
|
55
56
|
requiredDetail: boolean;
|
|
56
|
-
gap: string;
|
|
57
57
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
58
58
|
declare const _default: typeof __VLS_export;
|
|
59
59
|
export default _default;
|