pukaad-ui-lib 1.340.0 → 1.342.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.
Files changed (38) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/assets/css/core.css +1 -1
  3. package/dist/runtime/components/button.vue +10 -10
  4. package/dist/runtime/components/carousel.vue +31 -31
  5. package/dist/runtime/components/display/display-image-place.vue +98 -98
  6. package/dist/runtime/components/input/input-address.d.vue.ts +1 -1
  7. package/dist/runtime/components/input/input-address.vue.d.ts +1 -1
  8. package/dist/runtime/components/input/input-autocomplete.d.vue.ts +1 -1
  9. package/dist/runtime/components/input/input-autocomplete.vue.d.ts +1 -1
  10. package/dist/runtime/components/input/input-combobox.d.vue.ts +1 -1
  11. package/dist/runtime/components/input/input-combobox.vue.d.ts +1 -1
  12. package/dist/runtime/components/input/input-file.d.vue.ts +1 -1
  13. package/dist/runtime/components/input/input-file.vue.d.ts +1 -1
  14. package/dist/runtime/components/input/input-localized-name.d.vue.ts +1 -1
  15. package/dist/runtime/components/input/input-localized-name.vue.d.ts +1 -1
  16. package/dist/runtime/components/input/input-password.d.vue.ts +1 -1
  17. package/dist/runtime/components/input/input-password.vue.d.ts +1 -1
  18. package/dist/runtime/components/input/input-rating.vue +6 -6
  19. package/dist/runtime/components/input/input-suggest.d.vue.ts +1 -1
  20. package/dist/runtime/components/input/input-suggest.vue.d.ts +1 -1
  21. package/dist/runtime/components/input/input-tag.d.vue.ts +1 -1
  22. package/dist/runtime/components/input/input-tag.vue.d.ts +1 -1
  23. package/dist/runtime/components/input/input-textarea.d.vue.ts +1 -1
  24. package/dist/runtime/components/input/input-textarea.vue.d.ts +1 -1
  25. package/dist/runtime/components/modal/modal-email-OTP.vue +14 -10
  26. package/dist/runtime/components/modal/modal-media-view.vue +136 -136
  27. package/dist/runtime/components/modal/modal-password-confirmed.d.vue.ts +1 -1
  28. package/dist/runtime/components/modal/modal-password-confirmed.vue.d.ts +1 -1
  29. package/dist/runtime/components/modal/modal-password-verify.d.vue.ts +1 -1
  30. package/dist/runtime/components/modal/modal-password-verify.vue.d.ts +1 -1
  31. package/dist/runtime/components/modal/modal-phone-OTP.vue +16 -12
  32. package/dist/runtime/components/modal/modal-province-domain.d.vue.ts +3 -2
  33. package/dist/runtime/components/modal/modal-province-domain.vue +39 -30
  34. package/dist/runtime/components/modal/modal-province-domain.vue.d.ts +3 -2
  35. package/dist/runtime/composables/useProvinceRedirect.d.ts +4 -0
  36. package/dist/runtime/composables/useProvinceRedirect.js +8 -0
  37. package/package.json +1 -1
  38. /package/dist/runtime/assets/svg/socials/{Whatsapp.svg → WhatsApp.svg} +0 -0
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pukaad-ui-lib",
3
3
  "configKey": "pukaadUI",
4
- "version": "1.340.0",
4
+ "version": "1.342.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -1 +1 @@
1
- @import "tailwindcss";@import "tw-animate-css";@import "./system.css";@import "./scroll-bar.css";@custom-variant dark (&:is(.dark *));@theme inline{--radius-sm:calc(var(--radius) - 4px);--radius-md:calc(var(--radius) - 2px);--radius-lg:var(--radius);--radius-xl:calc(var(--radius) + 4px);--color-background:var(--background);--color-foreground:var(--foreground);--color-card:var(--card);--color-card-foreground:var(--card-foreground);--color-popover:var(--popover);--color-popover-foreground:var(--popover-foreground);--color-primary-foreground:var(--primary-foreground);--color-secondary-foreground:var(--secondary-foreground);--color-muted:var(--muted);--color-muted-foreground:var(--muted-foreground);--color-accent:var(--accent);--color-accent-foreground:var(--accent-foreground);--color-destructive:var(--destructive);--color-destructive-foreground:var(--destructive-foreground);--color-border:var(--border);--color-input:var(--input);--color-ring:var(--ring);--color-chart-1:var(--chart-1);--color-chart-2:var(--chart-2);--color-chart-3:var(--chart-3);--color-chart-4:var(--chart-4);--color-chart-5:var(--chart-5);--color-sidebar:var(--sidebar);--color-sidebar-foreground:var(--sidebar-foreground);--color-sidebar-primary:var(--sidebar-primary);--color-sidebar-primary-foreground:var(--sidebar-primary-foreground);--color-sidebar-accent:var(--sidebar-accent);--color-sidebar-accent-foreground:var(--sidebar-accent-foreground);--color-sidebar-border:var(--sidebar-border);--color-sidebar-ring:var(--sidebar-ring);--color-red:var(--red);--color-error:var(--error);--color-success:var(--success);--color-green:var(--green);--color-warning:var(--warning);--color-yellow:var(--yellow);--color-pink:var(--pink);--color-primary:var(--primary);--color-secondary:var(--secondary);--color-info:var(--info);--color-info-foreground:var(--info-foreground);--color-black:var(--black);--color-dark:var(--dark);--color-gray:var(--gray);--color-cloud:var(--cloud);--color-mercury:var(--mercury);--color-green-light:var(--green-light);--color-red-light:var(--red-light);--color-yellow-light:var(--yellow-light);--color-bright:var(--bright);--color-silver:var(--silver);--color-smoke:var(--smoke);--z-index-announce-tool:10;--z-index-navbar:11;--z-index-dropdown:12;--z-index-overlay:13;--z-index-loading:14;--z-index-drawer:15;--z-index-modal:16;--z-index-toast:100;--z-index-loading-page:101}:root{--radius:0.625rem;--red:#b71c1c;--error:var(--red);--success:#43a047;--green:#1b5e20;--warning:#f9a825;--yellow:#fbc02d;--info:#e3f2fd;--info-foreground:var(--primary);--black:#212121;--dark:#424242;--gray:#616161;--cloud:#c4c4c4;--mercury:#e0e0e0;--green-light:#e3f5e3;--red-light:#fde3e3;--yellow-light:#fef3c7;--bright:#f5f5f5;--silver:#f7f7f7;--smoke:#fafafa;--white:#fff;--pink:#ffe6f4;--background:var(--white);--foreground:var(--black);--card:var(--background);--card-foreground:var(--foreground);--popover:var(--background);--popover-foreground:var(--foreground);--primary:#1976b8;--primary-foreground:var(--white);--secondary:#22a7ef;--secondary-foreground:var(--white);--muted:oklch(0.97 0 0);--muted-foreground:var(--cloud);--accent:oklch(0.97 0 0);--accent-foreground:oklch(0.205 0 0);--destructive:#d32f2f;--destructive-foreground:var(--white);--border:var(--color-mercury);--input:var(--color-mercury);--ring:var(--mercury);--chart-1:oklch(0.646 0.222 41.116);--chart-2:oklch(0.6 0.118 184.704);--chart-3:oklch(0.398 0.07 227.392);--chart-4:oklch(0.828 0.189 84.429);--chart-5:oklch(0.769 0.188 70.08);--sidebar:oklch(0.985 0 0);--sidebar-foreground:var(--foreground);--sidebar-primary:oklch(0.205 0 0);--sidebar-primary-foreground:oklch(0.985 0 0);--sidebar-accent:oklch(0.97 0 0);--sidebar-accent-foreground:oklch(0.205 0 0);--sidebar-border:var(--color-mercury);--sidebar-ring:var(--color-mercury)}.dark{--background:oklch(0.145 0 0);--foreground:oklch(0.985 0 0);--card:oklch(0.145 0 0);--card-foreground:oklch(0.985 0 0);--popover:oklch(0.145 0 0);--popover-foreground:oklch(0.985 0 0);--primary:oklch(0.985 0 0);--primary-foreground:oklch(0.205 0 0);--secondary:oklch(0.269 0 0);--secondary-foreground:oklch(0.985 0 0);--muted:oklch(0.269 0 0);--muted-foreground:oklch(0.708 0 0);--accent:oklch(0.269 0 0);--accent-foreground:oklch(0.985 0 0);--destructive:oklch(0.396 0.141 25.723);--destructive-foreground:oklch(0.637 0.237 25.331);--border:oklch(0.269 0 0);--input:oklch(0.269 0 0);--ring:oklch(0.439 0 0);--chart-1:oklch(0.488 0.243 264.376);--chart-2:oklch(0.696 0.17 162.48);--chart-3:oklch(0.769 0.188 70.08);--chart-4:oklch(0.627 0.265 303.9);--chart-5:oklch(0.645 0.246 16.439);--sidebar:oklch(0.205 0 0);--sidebar-foreground:oklch(0.985 0 0);--sidebar-primary:oklch(0.488 0.243 264.376);--sidebar-primary-foreground:oklch(0.985 0 0);--sidebar-accent:oklch(0.269 0 0);--sidebar-accent-foreground:oklch(0.985 0 0);--sidebar-border:oklch(0.269 0 0);--sidebar-ring:oklch(0.439 0 0)}@layer base{*{@apply border-border outline-ring/50}body{@apply bg-background text-foreground}}
1
+ @import "tailwindcss";@import "tw-animate-css";@import "./system.css";@import "./scroll-bar.css";@custom-variant dark (&:is(.dark *));@theme inline{--radius-sm:calc(var(--radius) - 4px);--radius-md:calc(var(--radius) - 2px);--radius-lg:var(--radius);--radius-xl:calc(var(--radius) + 4px);--color-background:var(--background);--color-foreground:var(--foreground);--color-card:var(--card);--color-card-foreground:var(--card-foreground);--color-popover:var(--popover);--color-popover-foreground:var(--popover-foreground);--color-primary-foreground:var(--primary-foreground);--color-secondary-foreground:var(--secondary-foreground);--color-muted:var(--muted);--color-muted-foreground:var(--muted-foreground);--color-accent:var(--accent);--color-accent-foreground:var(--accent-foreground);--color-destructive:var(--destructive);--color-destructive-foreground:var(--destructive-foreground);--color-border:var(--border);--color-input:var(--input);--color-ring:var(--ring);--color-chart-1:var(--chart-1);--color-chart-2:var(--chart-2);--color-chart-3:var(--chart-3);--color-chart-4:var(--chart-4);--color-chart-5:var(--chart-5);--color-sidebar:var(--sidebar);--color-sidebar-foreground:var(--sidebar-foreground);--color-sidebar-primary:var(--sidebar-primary);--color-sidebar-primary-foreground:var(--sidebar-primary-foreground);--color-sidebar-accent:var(--sidebar-accent);--color-sidebar-accent-foreground:var(--sidebar-accent-foreground);--color-sidebar-border:var(--sidebar-border);--color-sidebar-ring:var(--sidebar-ring);--color-red:var(--red);--color-error:var(--error);--color-success:var(--success);--color-green:var(--green);--color-warning:var(--warning);--color-yellow:var(--yellow);--color-pink:var(--pink);--color-primary:var(--primary);--color-secondary:var(--secondary);--color-info:var(--info);--color-info-foreground:var(--info-foreground);--color-black:var(--black);--color-dark:var(--dark);--color-gray:var(--gray);--color-cloud:var(--cloud);--color-mercury:var(--mercury);--color-green-light:var(--green-light);--color-red-light:var(--red-light);--color-yellow-light:var(--yellow-light);--color-bright:var(--bright);--color-silver:var(--silver);--color-smoke:var(--smoke);--z-index-announce-tool:10;--z-index-navbar:11;--z-index-dropdown:12;--z-index-overlay:13;--z-index-loading:14;--z-index-drawer:15;--z-index-modal:16;--z-index-toast:100;--z-index-loading-page:101}:root{--radius:0.625rem;--red:#b71c1c;--success:#43a047;--green:#1b5e20;--warning:#f9a825;--yellow:#fbc02d;--info:#e3f2fd;--info-foreground:var(--primary);--black:#212121;--dark:#424242;--gray:#616161;--cloud:#c4c4c4;--mercury:#e0e0e0;--green-light:#e3f5e3;--red-light:#fde3e3;--yellow-light:#fef3c7;--bright:#f5f5f5;--silver:#f7f7f7;--smoke:#fafafa;--white:#fff;--pink:#ffe6f4;--background:var(--white);--foreground:var(--black);--card:var(--background);--card-foreground:var(--foreground);--popover:var(--background);--popover-foreground:var(--foreground);--primary:#1976b8;--primary-foreground:var(--white);--secondary:#22a7ef;--secondary-foreground:var(--white);--muted:oklch(0.97 0 0);--muted-foreground:var(--cloud);--accent:oklch(0.97 0 0);--accent-foreground:oklch(0.205 0 0);--destructive:#d32f2f;--destructive-foreground:var(--white);--border:var(--color-mercury);--input:var(--color-mercury);--ring:var(--mercury);--chart-1:oklch(0.646 0.222 41.116);--chart-2:oklch(0.6 0.118 184.704);--chart-3:oklch(0.398 0.07 227.392);--chart-4:oklch(0.828 0.189 84.429);--chart-5:oklch(0.769 0.188 70.08);--sidebar:oklch(0.985 0 0);--sidebar-foreground:var(--foreground);--sidebar-primary:oklch(0.205 0 0);--sidebar-primary-foreground:oklch(0.985 0 0);--sidebar-accent:oklch(0.97 0 0);--sidebar-accent-foreground:oklch(0.205 0 0);--sidebar-border:var(--color-mercury);--sidebar-ring:var(--color-mercury)}.dark{--background:oklch(0.145 0 0);--foreground:oklch(0.985 0 0);--card:oklch(0.145 0 0);--card-foreground:oklch(0.985 0 0);--popover:oklch(0.145 0 0);--popover-foreground:oklch(0.985 0 0);--primary:oklch(0.985 0 0);--primary-foreground:oklch(0.205 0 0);--secondary:oklch(0.269 0 0);--secondary-foreground:oklch(0.985 0 0);--muted:oklch(0.269 0 0);--muted-foreground:oklch(0.708 0 0);--accent:oklch(0.269 0 0);--accent-foreground:oklch(0.985 0 0);--destructive:oklch(0.396 0.141 25.723);--destructive-foreground:oklch(0.637 0.237 25.331);--border:oklch(0.269 0 0);--input:oklch(0.269 0 0);--ring:oklch(0.439 0 0);--chart-1:oklch(0.488 0.243 264.376);--chart-2:oklch(0.696 0.17 162.48);--chart-3:oklch(0.769 0.188 70.08);--chart-4:oklch(0.627 0.265 303.9);--chart-5:oklch(0.645 0.246 16.439);--sidebar:oklch(0.205 0 0);--sidebar-foreground:oklch(0.985 0 0);--sidebar-primary:oklch(0.488 0.243 264.376);--sidebar-primary-foreground:oklch(0.985 0 0);--sidebar-accent:oklch(0.269 0 0);--sidebar-accent-foreground:oklch(0.985 0 0);--sidebar-border:oklch(0.269 0 0);--sidebar-ring:oklch(0.439 0 0)}@layer base{*{@apply border-border outline-ring/50}body{@apply bg-background text-foreground}}
@@ -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>
@@ -50,10 +50,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
50
50
  name: string;
51
51
  required: boolean;
52
52
  placeholder: string;
53
- gap: string;
54
53
  labelDetail: string;
55
54
  placeholderDetail: string;
56
55
  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;
@@ -50,10 +50,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
50
50
  name: string;
51
51
  required: boolean;
52
52
  placeholder: string;
53
- gap: string;
54
53
  labelDetail: string;
55
54
  placeholderDetail: string;
56
55
  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;
@@ -50,9 +50,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
50
50
  required: boolean;
51
51
  options: AutocompleteOption[] | string[] | number[];
52
52
  description: string;
53
+ limit: number;
53
54
  labelKey: string;
54
55
  placeholder: string;
55
- limit: number;
56
56
  disabledErrorMessage: boolean;
57
57
  disabledBorder: boolean;
58
58
  showCounter: boolean;
@@ -50,9 +50,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
50
50
  required: boolean;
51
51
  options: AutocompleteOption[] | string[] | number[];
52
52
  description: string;
53
+ limit: number;
53
54
  labelKey: string;
54
55
  placeholder: string;
55
- limit: number;
56
56
  disabledErrorMessage: boolean;
57
57
  disabledBorder: boolean;
58
58
  showCounter: boolean;
@@ -55,8 +55,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
55
55
  }>, {
56
56
  pageSize: number;
57
57
  name: string;
58
- labelKey: string;
59
58
  limit: number;
59
+ labelKey: string;
60
60
  valueKey: string;
61
61
  multiple: boolean;
62
62
  placeholderSearch: string;
@@ -55,8 +55,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
55
55
  }>, {
56
56
  pageSize: number;
57
57
  name: string;
58
- labelKey: string;
59
58
  limit: number;
59
+ labelKey: string;
60
60
  valueKey: string;
61
61
  multiple: boolean;
62
62
  placeholderSearch: string;
@@ -35,8 +35,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
35
35
  fullHeight: boolean;
36
36
  fullWidth: boolean;
37
37
  limit: number;
38
- accept: string;
39
38
  disabledErrorMessage: boolean;
39
+ accept: string;
40
40
  labelIcon: string;
41
41
  disabledDrop: boolean;
42
42
  column: boolean;
@@ -35,8 +35,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
35
35
  fullHeight: boolean;
36
36
  fullWidth: boolean;
37
37
  limit: number;
38
- accept: string;
39
38
  disabledErrorMessage: boolean;
39
+ accept: string;
40
40
  labelIcon: string;
41
41
  disabledDrop: boolean;
42
42
  column: boolean;
@@ -12,8 +12,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
12
12
  name: string;
13
13
  modelValue: LocalizedNameItem[];
14
14
  options: InputSelectItem[];
15
- placeholder: string;
16
15
  limit: number;
16
+ placeholder: string;
17
17
  showCounter: boolean;
18
18
  addLabel: string;
19
19
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -12,8 +12,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
12
12
  name: string;
13
13
  modelValue: LocalizedNameItem[];
14
14
  options: InputSelectItem[];
15
- placeholder: string;
16
15
  limit: number;
16
+ placeholder: string;
17
17
  showCounter: boolean;
18
18
  addLabel: string;
19
19
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -22,8 +22,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
22
22
  }>, {
23
23
  id: string;
24
24
  name: string;
25
- disabledForgotPassword: boolean;
26
25
  new: boolean;
26
+ disabledForgotPassword: boolean;
27
27
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
28
28
  declare const _default: typeof __VLS_export;
29
29
  export default _default;
@@ -22,8 +22,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
22
22
  }>, {
23
23
  id: string;
24
24
  name: string;
25
- disabledForgotPassword: boolean;
26
25
  new: boolean;
26
+ disabledForgotPassword: boolean;
27
27
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
28
28
  declare const _default: typeof __VLS_export;
29
29
  export default _default;
@@ -1,10 +1,10 @@
1
1
  <template>
2
- <!-- v-html เพื่อหลีกเลี่ยง Vue patch SVG attributes เป็น DOM property (read-only getter bug) -->
3
- <div
4
- class="inline-flex items-center gap-0"
5
- v-html="starsHtml"
6
- @click="handleClick"
7
- />
2
+ <!-- v-html เพื่อหลีกเลี่ยง Vue patch SVG attributes เป็น DOM property (read-only getter bug) -->
3
+ <div
4
+ class="inline-flex items-center gap-0"
5
+ v-html="starsHtml"
6
+ @click="handleClick"
7
+ />
8
8
  </template>
9
9
 
10
10
  <script setup>
@@ -20,9 +20,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
20
20
  name: string;
21
21
  required: boolean;
22
22
  description: string;
23
+ limit: number;
23
24
  labelKey: string;
24
25
  placeholder: string;
25
- limit: number;
26
26
  disabledErrorMessage: boolean;
27
27
  disabledBorder: boolean;
28
28
  showCounter: boolean;
@@ -20,9 +20,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
20
20
  name: string;
21
21
  required: boolean;
22
22
  description: string;
23
+ limit: number;
23
24
  labelKey: string;
24
25
  placeholder: string;
25
- limit: number;
26
26
  disabledErrorMessage: boolean;
27
27
  disabledBorder: boolean;
28
28
  showCounter: boolean;
@@ -26,8 +26,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
26
26
  }>, {
27
27
  name: string;
28
28
  state: "user" | "admin";
29
- placeholder: string;
30
29
  limit: number;
30
+ placeholder: string;
31
31
  ignore: string[];
32
32
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
33
33
  declare const _default: typeof __VLS_export;
@@ -26,8 +26,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
26
26
  }>, {
27
27
  name: string;
28
28
  state: "user" | "admin";
29
- placeholder: string;
30
29
  limit: number;
30
+ placeholder: string;
31
31
  ignore: string[];
32
32
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
33
33
  declare const _default: typeof __VLS_export;
@@ -46,8 +46,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
46
46
  resize: "none" | "both" | "horizontal" | "vertical";
47
47
  fullHeight: boolean;
48
48
  fullWidth: boolean;
49
- rows: number;
50
49
  limit: number;
50
+ rows: number;
51
51
  disabledErrorMessage: boolean;
52
52
  disabledBorder: boolean;
53
53
  showCounter: boolean;
@@ -46,8 +46,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
46
46
  resize: "none" | "both" | "horizontal" | "vertical";
47
47
  fullHeight: boolean;
48
48
  fullWidth: boolean;
49
- rows: number;
50
49
  limit: number;
50
+ rows: number;
51
51
  disabledErrorMessage: boolean;
52
52
  disabledBorder: boolean;
53
53
  showCounter: boolean;
@@ -99,16 +99,20 @@ const handleSendEmail = async () => {
99
99
  isLoading.value = false;
100
100
  }
101
101
  };
102
- watch(isOpen, (v) => {
103
- if (v) {
104
- valueOTP.value = "";
105
- if (props.initialExpiredTime) {
106
- timeExp.value = props.initialExpiredTime;
102
+ watch(
103
+ isOpen,
104
+ (v) => {
105
+ if (v) {
106
+ valueOTP.value = "";
107
+ if (props.initialExpiredTime) {
108
+ timeExp.value = props.initialExpiredTime;
109
+ } else {
110
+ handleSendEmail();
111
+ }
107
112
  } else {
108
- handleSendEmail();
113
+ timeExp.value = "";
109
114
  }
110
- } else {
111
- timeExp.value = "";
112
- }
113
- });
115
+ },
116
+ { immediate: true }
117
+ );
114
118
  </script>
@@ -1,140 +1,140 @@
1
1
  <template>
2
- <Modal v-model="isOpen" class="sm:max-w-[1280px]" disable-padding>
3
- <template #header>
4
- <ShadDialogTitle class="p-4">{{ props.title }}</ShadDialogTitle>
5
- </template>
6
- <div class="relative h-[824px] bg-black overflow-hidden">
7
- <Carousel
8
- :items="props.items"
9
- :select-index="props.startIndex"
10
- class="h-full"
11
- @update:current-index="currentIndex = $event"
12
- />
13
-
14
- <!-- description overlay (แสดงเฉพาะเมื่อ item ปัจจุบันมี description และไม่มี review) -->
15
- <div
16
- v-if="!props.review && currentItem?.description"
17
- class="absolute bottom-0 w-full z-10"
18
- style="background: linear-gradient(to top, rgba(0,0,0,0.75) 0%, transparent 100%);"
19
- >
20
- <div class="p-[16px] flex flex-col gap-[4px] text-white font-body-large">
21
- <div class="font-body-large-prominent">ผู้ดูแลระบบ</div>
22
- <div>{{ currentItem.description }}</div>
23
- </div>
24
- </div>
25
-
26
- <!-- Review info overlay (แสดงเมื่อมีข้อมูล review) -->
27
- <div
28
- v-if="props.review"
29
- class="absolute bottom-0 w-full z-10"
30
- style="background: linear-gradient(to top, rgba(0,0,0,0.75) 0%, transparent 100%);"
31
- >
32
- <div class="flex flex-col w-full">
33
- <div
34
- v-if="!showMoreDescription"
35
- class="flex gap-[16px] p-[16px] w-full text-white"
36
- >
37
- <Avatar
38
- :src="props.review.user?.avatar"
39
- :alt="props.review.user?.name"
40
- :size="36"
41
- class="shrink-0"
42
- />
43
- <div class="flex flex-col gap-[8px] w-full">
44
- <div class="flex flex-col gap-[8px]">
45
- <div class="flex justify-between w-full">
46
- <span class="font-body-large">
47
- {{ props.review.user?.name }}
48
- </span>
49
- <Icon name="lucide:flag" />
50
- </div>
51
- <div class="flex flex-col">
52
- <span class="font-body-small">
53
- {{ convertNumber(props.review.user?.review_count ?? 0) }}
54
- รีวิว •
55
- {{ convertNumber(props.review.user?.like_count ?? 0) }}
56
- ชื่นชอบรีวิว
57
- </span>
58
- <div
59
- v-if="props.review.review"
60
- class="flex items-center gap-[8px]"
61
- >
62
- <InputRating
63
- :size="14"
64
- readonly
65
- :model-value="props.review.review.rating"
66
- />
67
- <span class="font-body-small opacity-75">
68
- {{ convertDateTorelativeText(props.review.review.created_at ?? "") }}
69
- </span>
70
- </div>
71
- </div>
72
- </div>
73
- <p
74
- v-if="props.review.review?.description"
75
- class="font-body-large line-clamp-1 cursor-pointer"
76
- @click="showMoreDescription = true"
77
- >
78
- {{ props.review.review.description }}
79
- </p>
80
- </div>
81
- </div>
82
- <div
83
- v-if="showMoreDescription"
84
- class="flex gap-[16px] bg-white rounded-t-[16px] w-full p-[16px] max-h-[322px]"
85
- >
86
- <Avatar
87
- :src="props.review.user?.avatar"
88
- :alt="props.review.user?.name"
89
- :size="36"
90
- class="shrink-0"
91
- />
92
- <div class="flex flex-col gap-[8px] w-full">
93
- <div class="flex flex-col gap-[8px]">
94
- <div class="flex justify-between w-full">
95
- <span class="font-body-large">
96
- {{ props.review.user?.name }}
97
- </span>
98
- <Icon
99
- name="lucide:x"
100
- class="cursor-pointer"
101
- @click="showMoreDescription = false"
102
- />
103
- </div>
104
- <div class="flex flex-col">
105
- <span class="font-body-small">
106
- {{ convertNumber(props.review.user?.review_count ?? 0) }}
107
- รีวิว •
108
- {{ convertNumber(props.review.user?.like_count ?? 0) }}
109
- ชื่นชอบรีวิว
110
- </span>
111
- <div
112
- v-if="props.review.review"
113
- class="flex items-center gap-[8px]"
114
- >
115
- <InputRating
116
- :size="14"
117
- readonly
118
- :model-value="props.review.review.rating"
119
- />
120
- <span class="font-body-small opacity-75">
121
- {{ convertDateTorelativeText(props.review.review.created_at ?? "") }}
122
- </span>
123
- </div>
124
- </div>
125
- </div>
126
- <p
127
- v-if="props.review.review?.description"
128
- class="font-body-large max-h-[280px] overflow-y-auto"
129
- >
130
- {{ props.review.review.description }}
131
- </p>
132
- </div>
133
- </div>
134
- </div>
135
- </div>
136
- </div>
137
- </Modal>
2
+ <Modal v-model="isOpen" class="sm:max-w-[1280px]" disable-padding>
3
+ <template #header>
4
+ <ShadDialogTitle class="p-4">{{ props.title }}</ShadDialogTitle>
5
+ </template>
6
+ <div class="relative h-[824px] bg-black overflow-hidden">
7
+ <Carousel
8
+ :items="props.items"
9
+ :select-index="props.startIndex"
10
+ class="h-full"
11
+ @update:current-index="currentIndex = $event"
12
+ />
13
+
14
+ <!-- description overlay (แสดงเฉพาะเมื่อ item ปัจจุบันมี description และไม่มี review) -->
15
+ <div
16
+ v-if="!props.review && currentItem?.description"
17
+ class="absolute bottom-0 w-full z-10"
18
+ style="background: linear-gradient(to top, rgba(0,0,0,0.75) 0%, transparent 100%);"
19
+ >
20
+ <div class="p-[16px] flex flex-col gap-[4px] text-white font-body-large">
21
+ <div class="font-body-large-prominent">ผู้ดูแลระบบ</div>
22
+ <div>{{ currentItem.description }}</div>
23
+ </div>
24
+ </div>
25
+
26
+ <!-- Review info overlay (แสดงเมื่อมีข้อมูล review) -->
27
+ <div
28
+ v-if="props.review"
29
+ class="absolute bottom-0 w-full z-10"
30
+ style="background: linear-gradient(to top, rgba(0,0,0,0.75) 0%, transparent 100%);"
31
+ >
32
+ <div class="flex flex-col w-full">
33
+ <div
34
+ v-if="!showMoreDescription"
35
+ class="flex gap-[16px] p-[16px] w-full text-white"
36
+ >
37
+ <Avatar
38
+ :src="props.review.user?.avatar"
39
+ :alt="props.review.user?.name"
40
+ :size="36"
41
+ class="shrink-0"
42
+ />
43
+ <div class="flex flex-col gap-[8px] w-full">
44
+ <div class="flex flex-col gap-[8px]">
45
+ <div class="flex justify-between w-full">
46
+ <span class="font-body-large">
47
+ {{ props.review.user?.name }}
48
+ </span>
49
+ <Icon name="lucide:flag" />
50
+ </div>
51
+ <div class="flex flex-col">
52
+ <span class="font-body-small">
53
+ {{ convertNumber(props.review.user?.review_count ?? 0) }}
54
+ รีวิว •
55
+ {{ convertNumber(props.review.user?.like_count ?? 0) }}
56
+ ชื่นชอบรีวิว
57
+ </span>
58
+ <div
59
+ v-if="props.review.review"
60
+ class="flex items-center gap-[8px]"
61
+ >
62
+ <InputRating
63
+ :size="14"
64
+ readonly
65
+ :model-value="props.review.review.rating"
66
+ />
67
+ <span class="font-body-small opacity-75">
68
+ {{ convertDateTorelativeText(props.review.review.created_at ?? "") }}
69
+ </span>
70
+ </div>
71
+ </div>
72
+ </div>
73
+ <p
74
+ v-if="props.review.review?.description"
75
+ class="font-body-large line-clamp-1 cursor-pointer"
76
+ @click="showMoreDescription = true"
77
+ >
78
+ {{ props.review.review.description }}
79
+ </p>
80
+ </div>
81
+ </div>
82
+ <div
83
+ v-if="showMoreDescription"
84
+ class="flex gap-[16px] bg-white rounded-t-[16px] w-full p-[16px] max-h-[322px]"
85
+ >
86
+ <Avatar
87
+ :src="props.review.user?.avatar"
88
+ :alt="props.review.user?.name"
89
+ :size="36"
90
+ class="shrink-0"
91
+ />
92
+ <div class="flex flex-col gap-[8px] w-full">
93
+ <div class="flex flex-col gap-[8px]">
94
+ <div class="flex justify-between w-full">
95
+ <span class="font-body-large">
96
+ {{ props.review.user?.name }}
97
+ </span>
98
+ <Icon
99
+ name="lucide:x"
100
+ class="cursor-pointer"
101
+ @click="showMoreDescription = false"
102
+ />
103
+ </div>
104
+ <div class="flex flex-col">
105
+ <span class="font-body-small">
106
+ {{ convertNumber(props.review.user?.review_count ?? 0) }}
107
+ รีวิว •
108
+ {{ convertNumber(props.review.user?.like_count ?? 0) }}
109
+ ชื่นชอบรีวิว
110
+ </span>
111
+ <div
112
+ v-if="props.review.review"
113
+ class="flex items-center gap-[8px]"
114
+ >
115
+ <InputRating
116
+ :size="14"
117
+ readonly
118
+ :model-value="props.review.review.rating"
119
+ />
120
+ <span class="font-body-small opacity-75">
121
+ {{ convertDateTorelativeText(props.review.review.created_at ?? "") }}
122
+ </span>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ <p
127
+ v-if="props.review.review?.description"
128
+ class="font-body-large max-h-[280px] overflow-y-auto"
129
+ >
130
+ {{ props.review.review.description }}
131
+ </p>
132
+ </div>
133
+ </div>
134
+ </div>
135
+ </div>
136
+ </div>
137
+ </Modal>
138
138
  </template>
139
139
 
140
140
  <script setup>
@@ -24,8 +24,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
24
24
  }) => any) | undefined;
25
25
  }>, {
26
26
  title: string;
27
- confirmText: string;
28
27
  disabledForgotPassword: boolean;
28
+ confirmText: string;
29
29
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
30
30
  declare const _default: typeof __VLS_export;
31
31
  export default _default;
@@ -24,8 +24,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
24
24
  }) => any) | undefined;
25
25
  }>, {
26
26
  title: string;
27
- confirmText: string;
28
27
  disabledForgotPassword: boolean;
28
+ confirmText: string;
29
29
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
30
30
  declare const _default: typeof __VLS_export;
31
31
  export default _default;
@@ -32,8 +32,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
32
32
  }>, {
33
33
  title: string;
34
34
  mode: "login" | "secure";
35
- confirmText: string;
36
35
  disabledForgotPassword: boolean;
36
+ confirmText: string;
37
37
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
38
38
  declare const _default: typeof __VLS_export;
39
39
  export default _default;
@@ -32,8 +32,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
32
32
  }>, {
33
33
  title: string;
34
34
  mode: "login" | "secure";
35
- confirmText: string;
36
35
  disabledForgotPassword: boolean;
36
+ confirmText: string;
37
37
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
38
38
  declare const _default: typeof __VLS_export;
39
39
  export default _default;
@@ -132,18 +132,22 @@ const onVerifyOTP = async () => {
132
132
  isLoading.value = false;
133
133
  }
134
134
  };
135
- watch(isOpen, (value) => {
136
- textRef.value = "";
137
- valueOTP.value = "";
138
- otpError.value = "";
139
- if (value) {
140
- if (props.initialExpiredTime) {
141
- expireAt.value = props.initialExpiredTime;
135
+ watch(
136
+ isOpen,
137
+ (value) => {
138
+ textRef.value = "";
139
+ valueOTP.value = "";
140
+ otpError.value = "";
141
+ if (value) {
142
+ if (props.initialExpiredTime) {
143
+ expireAt.value = props.initialExpiredTime;
144
+ } else {
145
+ handleSendOTP();
146
+ }
142
147
  } else {
143
- handleSendOTP();
148
+ expireAt.value = "";
144
149
  }
145
- } else {
146
- expireAt.value = "";
147
- }
148
- });
150
+ },
151
+ { immediate: true }
152
+ );
149
153
  </script>
@@ -1,3 +1,4 @@
1
+ import { type Province } from "#pukaad-ui/runtime/composables/useThaiAddress";
1
2
  import type { ModalProviceDomainProps } from "#pukaad-ui/types/components/modal/modal-province-domain";
2
3
  type __VLS_Props = ModalProviceDomainProps;
3
4
  type __VLS_ModelProps = {
@@ -7,10 +8,10 @@ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
7
8
  declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
8
9
  "update:modelValue": (value: boolean) => any;
9
10
  } & {
10
- confirmed: (value: string) => any;
11
+ confirmed: (province: Province) => any;
11
12
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
12
13
  "onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
13
- onConfirmed?: ((value: string) => any) | undefined;
14
+ onConfirmed?: ((province: Province) => any) | undefined;
14
15
  }>, {
15
16
  confirmedText: string;
16
17
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -1,23 +1,25 @@
1
1
  <template>
2
2
  <Modal
3
3
  title="เลือกจังหวัดที่คุณต้องการใช้งาน"
4
- :column-gap="24"
4
+ description="เลือกจังหวัดที่คุณต้องการจัดการข้อมูลธุรกิจ
5
+ เพื่อให้เข้าถึงเครื่องมือและข้อมูลในพื้นที่ได้ถูกต้อง"
6
+ class="w-[425px]"
5
7
  v-model="modelValue"
6
8
  >
7
- <div class="flex flex-col gap-[16px] w-full">
8
- <div class="flex flex-col font-body-large text-center">
9
- <div>เลือกจังหวัดที่คุณต้องการจัดการข้อมูลธุรกิจ</div>
10
- <div>เพื่อให้เข้าถึงเครื่องมือและข้อมูลในพื้นที่ได้ถูกต้อง</div>
11
- </div>
12
- <InputAutocomplete
13
- :items="provinces"
14
- full-width
15
- placeholder="เลือกจังหวัด"
16
- v-model="selected"
17
- />
18
- </div>
9
+ <InputCombobox
10
+ placeholder="เลือกจังหวัด"
11
+ label="จังหวัด"
12
+ :options="provinceOptions"
13
+ v-model="selected"
14
+ placeholder-search="ค้นหาจังหวัด"
15
+ />
19
16
  <template #footer>
20
- <Button full-width color="primary" :disabled="!selected" @click="onNext">
17
+ <Button
18
+ class="w-full"
19
+ color="primary"
20
+ :disabled="!selected"
21
+ @click="onNext"
22
+ >
21
23
  {{ props.confirmedText }}
22
24
  </Button>
23
25
  </template>
@@ -25,31 +27,38 @@
25
27
  </template>
26
28
 
27
29
  <script setup>
28
- import { ref } from "vue";
30
+ import { ref, computed, onMounted } from "vue";
31
+ import {
32
+ useThaiAddress
33
+ } from "#pukaad-ui/runtime/composables/useThaiAddress";
29
34
  const emit = defineEmits(["confirmed"]);
30
35
  const props = defineProps({
31
36
  confirmedText: { type: String, required: false, default: "\u0E22\u0E37\u0E19\u0E22\u0E31\u0E19" }
32
37
  });
38
+ const { loadProvinces } = useThaiAddress();
33
39
  const selected = ref("");
34
- const provinces = ref([
35
- {
36
- label: "\u0E01\u0E23\u0E38\u0E07\u0E40\u0E17\u0E1E\u0E21\u0E2B\u0E32\u0E19\u0E04\u0E23",
37
- value: "https://www.someting_eie.com"
38
- },
39
- {
40
- label: "\u0E1A\u0E38\u0E23\u0E35\u0E23\u0E31\u0E21\u0E22\u0E4C",
41
- value: "https://www.someting_eie.com"
42
- },
43
- {
44
- label: "\u0E01\u0E23\u0E30\u0E1A\u0E35\u0E48",
45
- value: "\u0E01\u0E23\u0E30\u0E1A\u0E35\u0E48"
46
- }
47
- ]);
40
+ const provincesData = ref([]);
41
+ const activeProvinces = computed(
42
+ () => provincesData.value.filter((province) => province.is_active)
43
+ );
44
+ const provinceOptions = computed(
45
+ () => activeProvinces.value.map((province) => ({
46
+ label: province.name_th,
47
+ value: String(province.id)
48
+ }))
49
+ );
50
+ onMounted(async () => {
51
+ provincesData.value = await loadProvinces();
52
+ });
48
53
  const modelValue = defineModel({ type: Boolean, ...{
49
54
  default: false
50
55
  } });
51
56
  const onNext = () => {
52
- emit("confirmed", selected.value);
57
+ const province = activeProvinces.value.find(
58
+ (p) => String(p.id) === selected.value
59
+ );
60
+ if (!province) return;
61
+ emit("confirmed", province);
53
62
  modelValue.value = false;
54
63
  };
55
64
  </script>
@@ -1,3 +1,4 @@
1
+ import { type Province } from "#pukaad-ui/runtime/composables/useThaiAddress";
1
2
  import type { ModalProviceDomainProps } from "#pukaad-ui/types/components/modal/modal-province-domain";
2
3
  type __VLS_Props = ModalProviceDomainProps;
3
4
  type __VLS_ModelProps = {
@@ -7,10 +8,10 @@ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
7
8
  declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
8
9
  "update:modelValue": (value: boolean) => any;
9
10
  } & {
10
- confirmed: (value: string) => any;
11
+ confirmed: (province: Province) => any;
11
12
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
12
13
  "onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
13
- onConfirmed?: ((value: string) => any) | undefined;
14
+ onConfirmed?: ((province: Province) => any) | undefined;
14
15
  }>, {
15
16
  confirmedText: string;
16
17
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -0,0 +1,4 @@
1
+ import type { Province } from "./useThaiAddress.js";
2
+ export declare function useProvinceRedirect(): {
3
+ goToProvinceDomain: (province: Province, path?: string) => any;
4
+ };
@@ -0,0 +1,8 @@
1
+ import { navigateTo } from "#imports";
2
+ export function useProvinceRedirect() {
3
+ const goToProvinceDomain = (province, path = "") => {
4
+ if (!province?.domain) return;
5
+ return navigateTo(`//${province.domain}${path}`, { external: true });
6
+ };
7
+ return { goToProvinceDomain };
8
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pukaad-ui-lib",
3
- "version": "1.340.0",
3
+ "version": "1.342.0",
4
4
  "description": "pukaad-ui for MeMSG",
5
5
  "repository": {
6
6
  "type": "git",