pukaad-ui-lib 1.298.0 → 1.300.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pukaad-ui-lib",
3
3
  "configKey": "pukaadUI",
4
- "version": "1.298.0",
4
+ "version": "1.300.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -0,0 +1,24 @@
1
+ export interface PlaceItem {
2
+ id?: string;
3
+ name?: string;
4
+ cover_image?: string;
5
+ rating?: number;
6
+ review_count?: number;
7
+ like_count?: number;
8
+ address?: string;
9
+ description?: string;
10
+ is_liked?: boolean;
11
+ }
12
+ type __VLS_ModelProps = {
13
+ modelValue?: PlaceItem;
14
+ };
15
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
16
+ "update:modelValue": (value: PlaceItem) => any;
17
+ } & {
18
+ "require-auth": () => any;
19
+ }, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
20
+ "onUpdate:modelValue"?: ((value: PlaceItem) => any) | undefined;
21
+ "onRequire-auth"?: (() => any) | undefined;
22
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
23
+ declare const _default: typeof __VLS_export;
24
+ export default _default;
@@ -0,0 +1,108 @@
1
+ <template>
2
+ <div class="flex gap-[16px] items-start justify-between" @click="onClickCard">
3
+ <div class="flex gap-[8px] cursor-pointer flex-1 min-w-0">
4
+ <Image
5
+ v-if="modelValue?.cover_image"
6
+ :src="modelValue.cover_image"
7
+ width="114"
8
+ height="64"
9
+ class="rounded-[4px] object-cover flex-shrink-0"
10
+ />
11
+ <div
12
+ v-else
13
+ class="w-[114px] h-[64px] bg-mercury flex items-center justify-center rounded-[4px] flex-shrink-0"
14
+ >
15
+ <Icon name="fa6-solid:map-location-dot" size="24" class="text-gray" />
16
+ </div>
17
+
18
+ <div class="flex flex-col gap-[4px] min-w-0">
19
+ <div class="font-body-large-prominent truncate">
20
+ {{ modelValue?.name }}
21
+ </div>
22
+ <div
23
+ class="flex gap-[4px] items-center text-gray font-body-small flex-wrap"
24
+ >
25
+ <InputRating
26
+ :size="10"
27
+ readonly
28
+ :model-value="modelValue?.rating || 0"
29
+ />
30
+ <div>
31
+ {{ $convert.convertNumber(modelValue?.review_count || 0) }}
32
+ </div>
33
+ <div>•</div>
34
+ <div>
35
+ ถูกใจ {{ $convert.convertNumber(modelValue?.like_count || 0) }}
36
+ </div>
37
+ <div>•</div>
38
+ <div class="truncate">{{ modelValue?.address }}</div>
39
+ </div>
40
+ <div class="font-body-small text-gray line-clamp-2">
41
+ {{ modelValue?.description }}
42
+ </div>
43
+ </div>
44
+ </div>
45
+
46
+ <Button
47
+ :variant="modelValue?.is_liked ? 'default' : 'outline'"
48
+ :color="modelValue?.is_liked ? 'default' : 'primary'"
49
+ class="flex-shrink-0"
50
+ @click.stop="onLikeToggle"
51
+ >
52
+ {{ modelValue?.is_liked ? "\u0E16\u0E39\u0E01\u0E43\u0E08\u0E41\u0E25\u0E49\u0E27" : "\u0E16\u0E39\u0E01\u0E43\u0E08" }}
53
+ </Button>
54
+ </div>
55
+ </template>
56
+
57
+ <script setup>
58
+ import { ref } from "vue";
59
+ import { useRouter } from "vue-router";
60
+ import { useCookie, useRuntimeConfig } from "nuxt/app";
61
+ import { useApi } from "../../composables/useApi";
62
+ const modelValue = defineModel({ type: Object, ...{ default: () => ({}) } });
63
+ const emit = defineEmits(["require-auth"]);
64
+ const router = useRouter();
65
+ const api = useApi();
66
+ const config = useRuntimeConfig();
67
+ const isLoading = ref(false);
68
+ const onClickCard = () => {
69
+ if (modelValue.value?.id) {
70
+ router.push(`/place/${modelValue.value.id}`);
71
+ }
72
+ };
73
+ const onLikeToggle = async () => {
74
+ const { APP_TYPE } = config.public;
75
+ const secId = useCookie(APP_TYPE === "OFFICE" ? "OFFICE_SEC_ID" : "SEC_ID");
76
+ if (!secId.value) {
77
+ emit("require-auth");
78
+ return;
79
+ }
80
+ if (!modelValue.value?.id) return;
81
+ if (isLoading.value) return;
82
+ const placeId = modelValue.value.id;
83
+ const currentLiked = !!modelValue.value.is_liked;
84
+ modelValue.value.is_liked = !currentLiked;
85
+ if (modelValue.value.like_count !== void 0) {
86
+ modelValue.value.like_count += !currentLiked ? 1 : -1;
87
+ }
88
+ isLoading.value = true;
89
+ try {
90
+ const method = currentLiked ? "DELETE" : "POST";
91
+ const response = await api(`/personal/suggest-places/${placeId}/like`, { method });
92
+ if (response.data) {
93
+ modelValue.value.is_liked = response.data.is_liked;
94
+ if (modelValue.value.like_count !== void 0) {
95
+ modelValue.value.like_count = response.data.like_count;
96
+ }
97
+ }
98
+ } catch (error) {
99
+ modelValue.value.is_liked = currentLiked;
100
+ if (modelValue.value.like_count !== void 0) {
101
+ modelValue.value.like_count += currentLiked ? 1 : -1;
102
+ }
103
+ console.error("Like toggle failed:", error);
104
+ } finally {
105
+ isLoading.value = false;
106
+ }
107
+ };
108
+ </script>
@@ -0,0 +1,24 @@
1
+ export interface PlaceItem {
2
+ id?: string;
3
+ name?: string;
4
+ cover_image?: string;
5
+ rating?: number;
6
+ review_count?: number;
7
+ like_count?: number;
8
+ address?: string;
9
+ description?: string;
10
+ is_liked?: boolean;
11
+ }
12
+ type __VLS_ModelProps = {
13
+ modelValue?: PlaceItem;
14
+ };
15
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
16
+ "update:modelValue": (value: PlaceItem) => any;
17
+ } & {
18
+ "require-auth": () => any;
19
+ }, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
20
+ "onUpdate:modelValue"?: ((value: PlaceItem) => any) | undefined;
21
+ "onRequire-auth"?: (() => any) | undefined;
22
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
23
+ declare const _default: typeof __VLS_export;
24
+ export default _default;
@@ -1,98 +1,98 @@
1
1
  <template>
2
- <div class="py-[16px] border-b-[1px] border-mercury flex gap-[16px] w-full">
3
- <div v-if="props.item.user.avatar">
4
- <Avatar
5
- :src="props.item.user.avatar"
6
- alt="profile_myProfile"
7
- :size="30"
8
- class="cursor-pointer"
9
- @click="navigateToProfile(props.item.user.path_name)"
10
- />
11
- </div>
12
- <div class="flex flex-col gap-[24px] w-full">
13
- <div class="flex flex-col gap-[8px]">
14
- <div class="flex flex-col gap-[6px]">
15
- <div class="flex flex-col gap-[4px]">
16
- <div class="flex justify-between items-center">
17
- <div
18
- class="font-body-large cursor-pointer"
19
- @click="navigateToProfile(props.item.user.path_name)"
20
- >
21
- {{ props.item.user?.name }}
22
- </div>
23
- <div class="flex gap-[8px] items-center">
24
- <Button
25
- variant="text"
26
- :color="liked ? 'primary' : 'default'"
27
- :disabled="props.disabledLike"
28
- :aria-pressed="liked"
29
- @click="toggleLike"
30
- >
31
- <Icon
2
+ <div class="py-[16px] border-b-[1px] border-mercury flex gap-[16px] w-full">
3
+ <div v-if="props.item.user.avatar">
4
+ <Avatar
5
+ :src="props.item.user.avatar"
6
+ alt="profile_myProfile"
7
+ :size="30"
8
+ class="cursor-pointer"
9
+ @click="navigateToProfile(props.item.user.path_name)"
10
+ />
11
+ </div>
12
+ <div class="flex flex-col gap-[24px] w-full">
13
+ <div class="flex flex-col gap-[8px]">
14
+ <div class="flex flex-col gap-[6px]">
15
+ <div class="flex flex-col gap-[4px]">
16
+ <div class="flex justify-between items-center">
17
+ <div
18
+ class="font-body-large cursor-pointer"
19
+ @click="navigateToProfile(props.item.user.path_name)"
20
+ >
21
+ {{ props.item.user?.name }}
22
+ </div>
23
+ <div class="flex gap-[8px] items-center">
24
+ <Button
25
+ variant="text"
26
+ :color="liked ? 'primary' : 'default'"
27
+ :disabled="props.disabledLike"
28
+ :aria-pressed="liked"
29
+ @click="toggleLike"
30
+ >
31
+ <Icon
32
32
  :name="
33
33
  liked ? 'pukaad:thumbs-up-solid' : 'pukaad:thumbs-up-regular'
34
- "
35
- :size="20"
36
- />
37
- {{ convertNumber(likeCount) }}
38
- </Button>
39
- <PickerOptionMenuUser
40
- v-if="!props.disabledMenu"
41
- :state="menuType"
42
- disabled-padding
43
- @review-edit="emit('edit', props.item)"
44
- @review-delete="emit('delete', props.item)"
45
- />
46
- </div>
47
- </div>
48
- <div class="text-gray font-body-small">
49
- {{ convertNumber(props.item.user?.review_count ?? 0) }} รีวิว •
50
- {{ convertNumber(props.item.user?.like_count ?? 0) }} ชื่นชอบรีวิว
51
- </div>
52
- </div>
53
- <div class="flex gap-[8px] items-center">
54
- <InputRating
55
- :size="11"
56
- readonly
57
- :model-value="props.item.review?.rating"
58
- />
59
- <div class="text-gray font-body-small">
60
- {{ convertDate(props.item.review?.created_at ?? "") }}
61
- </div>
62
- </div>
63
- </div>
64
-
65
- <div v-if="props.item.review?.description" class="font-body-large">
66
- {{ props.item.review?.description }}
67
- </div>
68
-
69
- <div v-if="props.item.review?.images?.length" class="flex gap-[8px]">
70
- <DisplayImageReview
71
- :items="props.item.review?.images"
72
- @select="selectImage"
73
- />
74
- </div>
75
- </div>
76
-
77
- <div v-if="props.item.replies">
78
- <div class="p-[16px] rounded-sm bg-bright">
79
- <div class="text-gray font-body-large">
80
- การตอบกลับจาก {{ props.item.replies?.author }}
81
- </div>
82
- <div class="font-body-large">
83
- {{ props.item.replies?.description }}
84
- </div>
85
- </div>
86
- </div>
87
- </div>
88
- </div>
89
-
90
- <ModalMediaView
91
- v-model="isOpen"
92
- :items="props.item.review?.images"
93
- :start-index="startIndex"
94
- title="รูปภาพรีวิว"
95
- />
34
+ "
35
+ :size="20"
36
+ />
37
+ {{ convertNumber(likeCount) }}
38
+ </Button>
39
+ <PickerOptionMenuUser
40
+ v-if="!props.disabledMenu"
41
+ :state="menuType"
42
+ disabled-padding
43
+ @review-edit="emit('edit', props.item)"
44
+ @review-delete="emit('delete', props.item)"
45
+ />
46
+ </div>
47
+ </div>
48
+ <div class="text-gray font-body-small">
49
+ {{ convertNumber(props.item.user?.review_count ?? 0) }} รีวิว •
50
+ {{ convertNumber(props.item.user?.like_count ?? 0) }} ชื่นชอบรีวิว
51
+ </div>
52
+ </div>
53
+ <div class="flex gap-[8px] items-center">
54
+ <InputRating
55
+ :size="11"
56
+ readonly
57
+ :model-value="props.item.review?.rating"
58
+ />
59
+ <div class="text-gray font-body-small">
60
+ {{ convertDateTime(props.item.review?.created_at ?? "") }}
61
+ </div>
62
+ </div>
63
+ </div>
64
+
65
+ <div v-if="props.item.review?.description" class="font-body-large">
66
+ {{ props.item.review?.description }}
67
+ </div>
68
+
69
+ <div v-if="props.item.review?.images?.length" class="flex gap-[8px]">
70
+ <DisplayImageReview
71
+ :items="props.item.review?.images"
72
+ @select="selectImage"
73
+ />
74
+ </div>
75
+ </div>
76
+
77
+ <div v-if="props.item.replies">
78
+ <div class="p-[16px] rounded-sm bg-bright">
79
+ <div class="text-gray font-body-large">
80
+ การตอบกลับจาก {{ props.item.replies?.author }}
81
+ </div>
82
+ <div class="font-body-large">
83
+ {{ props.item.replies?.description }}
84
+ </div>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ </div>
89
+
90
+ <ModalMediaView
91
+ v-model="isOpen"
92
+ :items="props.item.review?.images"
93
+ :start-index="startIndex"
94
+ title="รูปภาพรีวิว"
95
+ />
96
96
  </template>
97
97
 
98
98
  <script setup>
@@ -100,7 +100,7 @@ import { computed, ref, watch } from "vue";
100
100
  import { useRouter } from "vue-router";
101
101
  import { useConvert } from "../../composables/useConvert";
102
102
  const router = useRouter();
103
- const { convertNumber, convertDate } = useConvert();
103
+ const { convertNumber, convertDateTime } = useConvert();
104
104
  const props = defineProps({
105
105
  item: { type: Object, required: true, default: () => ({
106
106
  review_id: "0",
@@ -1,74 +1,74 @@
1
1
  <template>
2
- <Drawer
3
- class="w-[748px]"
4
- :title="drawerTitle"
5
- @close="onClose"
6
- disabled-auto-close
7
- @submit="onSubmit"
8
- v-model="isOpen"
9
- >
10
- <div class="flex flex-col gap-[16px]">
11
- <div class="flex gap-[16px]">
12
- <div class="w-[178px] h-[100px] rounded-[8px] overflow-hidden flex-shrink-0">
13
- <Image
14
- v-if="form.coverImage"
15
- :src="form.coverImage"
16
- width="auto"
17
- height="auto"
18
- fit="cover"
19
- />
20
- </div>
21
- <div class="flex flex-col gap-[4px]">
22
- <div class="font-body-large-prominent">{{ form.placeName }}</div>
23
- <div class="font-body-small text-gray">{{ form.address }}</div>
24
- </div>
25
- </div>
26
-
27
- <div class="flex items-center justify-center py-[16px]">
28
- <InputRating v-model="form.rating" :size="28" />
29
- </div>
30
-
31
- <InputTextarea
32
- name="description"
33
- label="คำอธิบาย"
34
- placeholder="เขียนรีวิวของคุณ"
35
- showCounter
36
- v-model="form.description"
37
- class="min-h-[120px]"
38
- />
39
-
40
- <div class="flex flex-col gap-[8px]">
41
- <div class="flex flex-col gap-[4px]">
42
- <div class="text-gray font-body-large">เพิ่มภาพถ่าย</div>
43
- <div class="text-gray font-body-small">อัปโหลด 9 รายการ</div>
44
- </div>
45
- <InputFile
46
- :limit="9"
47
- name="photos"
48
- accept="image/jpeg,image/png,image/webp,image/bmp,image/gif"
49
- v-model="form.photos"
50
- />
51
- <div class="flex flex-col text-gray font-body-small">
52
- <div>รองรับไฟล์ *.jpg *.jpeg *.png *.webp *.bmp *.gif</div>
53
- <div>ขนาดไฟล์สูงสุด 30 mb</div>
54
- </div>
55
- </div>
56
- </div>
57
-
58
- <template #footer>
59
- <div class="flex justify-end gap-[16px] items-center">
60
- <Button variant="outline" @click="onClose" :disabled="isSubmitting">ยกเลิก</Button>
61
- <Button
62
- type="submit"
63
- color="primary"
64
- :disabled="form.rating === 0 || isSubmitting"
65
- :loading="isSubmitting"
66
- >
67
- ยืนยัน
68
- </Button>
69
- </div>
70
- </template>
71
- </Drawer>
2
+ <Drawer
3
+ class="w-[748px]"
4
+ :title="drawerTitle"
5
+ @close="onClose"
6
+ disabled-auto-close
7
+ @submit="onSubmit"
8
+ v-model="isOpen"
9
+ >
10
+ <div class="flex flex-col gap-[16px]">
11
+ <div class="flex gap-[16px]">
12
+ <div class="w-[178px] h-[100px] rounded-[8px] overflow-hidden flex-shrink-0">
13
+ <Image
14
+ v-if="form.coverImage"
15
+ :src="form.coverImage"
16
+ width="auto"
17
+ height="auto"
18
+ fit="cover"
19
+ />
20
+ </div>
21
+ <div class="flex flex-col gap-[4px]">
22
+ <div class="font-body-large-prominent">{{ form.placeName }}</div>
23
+ <div class="font-body-small text-gray">{{ form.address }}</div>
24
+ </div>
25
+ </div>
26
+
27
+ <div class="flex items-center justify-center py-[16px]">
28
+ <InputRating v-model="form.rating" :size="28" />
29
+ </div>
30
+
31
+ <InputTextarea
32
+ name="description"
33
+ label="คำอธิบาย"
34
+ placeholder="เขียนรีวิวของคุณ"
35
+ showCounter
36
+ v-model="form.description"
37
+ class="min-h-[120px]"
38
+ />
39
+
40
+ <div class="flex flex-col gap-[8px]">
41
+ <div class="flex flex-col gap-[4px]">
42
+ <div class="text-gray font-body-large">เพิ่มภาพถ่าย</div>
43
+ <div class="text-gray font-body-small">อัปโหลด 9 รายการ</div>
44
+ </div>
45
+ <InputFile
46
+ :limit="9"
47
+ name="photos"
48
+ accept="image/jpeg,image/png,image/webp,image/bmp,image/gif"
49
+ v-model="form.photos"
50
+ />
51
+ <div class="flex flex-col text-gray font-body-small">
52
+ <div>รองรับไฟล์ *.jpg *.jpeg *.png *.webp *.bmp *.gif</div>
53
+ <div>ขนาดไฟล์สูงสุด 30 mb</div>
54
+ </div>
55
+ </div>
56
+ </div>
57
+
58
+ <template #footer>
59
+ <div class="flex justify-end gap-[16px] items-center">
60
+ <Button variant="outline" @click="onClose" :disabled="isSubmitting">ยกเลิก</Button>
61
+ <Button
62
+ type="submit"
63
+ color="primary"
64
+ :disabled="form.rating === 0 || isSubmitting"
65
+ :loading="isSubmitting"
66
+ >
67
+ ยืนยัน
68
+ </Button>
69
+ </div>
70
+ </template>
71
+ </Drawer>
72
72
  </template>
73
73
 
74
74
  <script setup>
@@ -4,7 +4,7 @@ export interface ProfileSummary {
4
4
  avatar: string;
5
5
  follower_count: number;
6
6
  following_count: number;
7
- favorite_count?: number;
7
+ like_place_count?: number;
8
8
  }
9
9
  export interface DrawerProfileNetworkProps {
10
10
  profileId: string;
@@ -17,13 +17,19 @@
17
17
  <ShadTabs v-model="activeTab" class="flex-1 flex flex-col h-full">
18
18
  <ShadTabsList>
19
19
  <ShadTabsTrigger value="followers" class="px-[16px] py-[8px]">
20
- ผู้ติดตาม ({{ $convert.convertNumber(profileData?.follower_count || 0) }})
20
+ ผู้ติดตาม ({{
21
+ $convert.convertNumber(profileData?.follower_count || 0)
22
+ }})
21
23
  </ShadTabsTrigger>
22
24
  <ShadTabsTrigger value="following" class="px-[16px] py-[8px]">
23
- กำลังติดตาม ({{ $convert.convertNumber(profileData?.following_count || 0) }})
25
+ กำลังติดตาม ({{
26
+ $convert.convertNumber(profileData?.following_count || 0)
27
+ }})
24
28
  </ShadTabsTrigger>
25
29
  <ShadTabsTrigger value="favorites" class="px-[16px] py-[8px]">
26
- สถานที่โปรด ({{ $convert.convertNumber(profileData?.favorite_count || 0) }})
30
+ สถานที่โปรด ({{
31
+ $convert.convertNumber(profileData?.like_place_count || 0)
32
+ }})
27
33
  </ShadTabsTrigger>
28
34
  </ShadTabsList>
29
35
 
@@ -32,33 +38,62 @@
32
38
  <ShadTabsContent value="followers" class="flex flex-col mt-0">
33
39
  <template v-if="tabState.followers.loading">
34
40
  <div class="flex justify-center py-[40px]">
35
- <Icon name="lucide:loader-2" size="24" class="animate-spin text-gray" />
41
+ <Icon
42
+ name="lucide:loader-2"
43
+ size="24"
44
+ class="animate-spin text-gray"
45
+ />
36
46
  </div>
37
47
  </template>
38
48
  <template v-else-if="followers.length > 0">
39
49
  <div class="flex flex-col gap-[16px]">
40
- <template v-for="(user, index) in followers" :key="user.id || index">
41
- <CardUserItem v-model="followers[index]" @require-auth="emit('require-auth')" />
50
+ <template
51
+ v-for="(user, index) in followers"
52
+ :key="user.id || index"
53
+ >
54
+ <CardUserItem
55
+ v-model="followers[index]"
56
+ @require-auth="emit('require-auth')"
57
+ />
42
58
  <Divider v-if="index !== followers.length - 1" />
43
59
  </template>
44
60
  </div>
45
- <div v-if="tabState.followers.loadingMore" class="flex justify-center py-[16px]">
46
- <Icon name="lucide:loader-2" size="20" class="animate-spin text-gray" />
61
+ <div
62
+ v-if="tabState.followers.loadingMore"
63
+ class="flex justify-center py-[16px]"
64
+ >
65
+ <Icon
66
+ name="lucide:loader-2"
67
+ size="20"
68
+ class="animate-spin text-gray"
69
+ />
47
70
  </div>
48
71
  </template>
49
72
  <template v-else>
50
- <div v-if="isMyProfile" class="h-[200px] flex flex-col gap-[8px] items-center justify-center">
51
- <div class="font-title-medium-prominent text-black">กำลังมองหาผู้ติดตามอยู่ใช่ไหม</div>
73
+ <div
74
+ v-if="isMyProfile"
75
+ class="h-[200px] flex flex-col gap-[8px] items-center justify-center"
76
+ >
77
+ <div class="font-title-medium-prominent text-black">
78
+ กำลังมองหาผู้ติดตามอยู่ใช่ไหม
79
+ </div>
52
80
  <div class="flex flex-col font-body-large text-gray items-center">
53
81
  <div>เมื่อมีคนติดตามบัญชีนี้ ข้อมูลจะแสดงที่นี่</div>
54
82
  <div>การโพสต์และการโต้ตอบกับผู้อื่นจะช่วยเพิ่มผู้ติดตาม</div>
55
83
  </div>
56
84
  </div>
57
- <div v-else class="h-[200px] flex flex-col gap-[8px] items-center justify-center">
58
- <div class="font-title-medium-prominent text-black flex gap-[4px]">
85
+ <div
86
+ v-else
87
+ class="h-[200px] flex flex-col gap-[8px] items-center justify-center"
88
+ >
89
+ <div
90
+ class="font-title-medium-prominent text-black flex gap-[4px]"
91
+ >
59
92
  {{ profileData?.name }} ยังไม่มีผู้ติดตาม
60
93
  </div>
61
- <div class="font-body-large text-gray">เมื่อมีคนติดตามบัญชีนี้ ข้อมูลจะแสดงที่นี่</div>
94
+ <div class="font-body-large text-gray">
95
+ เมื่อมีคนติดตามบัญชีนี้ ข้อมูลจะแสดงที่นี่
96
+ </div>
62
97
  </div>
63
98
  </template>
64
99
  </ShadTabsContent>
@@ -67,36 +102,71 @@
67
102
  <ShadTabsContent value="following" class="flex flex-col mt-0">
68
103
  <template v-if="tabState.following.loading">
69
104
  <div class="flex justify-center py-[40px]">
70
- <Icon name="lucide:loader-2" size="24" class="animate-spin text-gray" />
105
+ <Icon
106
+ name="lucide:loader-2"
107
+ size="24"
108
+ class="animate-spin text-gray"
109
+ />
71
110
  </div>
72
111
  </template>
73
112
  <template v-else-if="following.length > 0">
74
113
  <div class="flex flex-col gap-[16px]">
75
- <template v-for="(user, index) in following" :key="user.id || index">
76
- <CardUserItem v-model="following[index]" @require-auth="emit('require-auth')" />
114
+ <template
115
+ v-for="(user, index) in following"
116
+ :key="user.id || index"
117
+ >
118
+ <CardUserItem
119
+ v-model="following[index]"
120
+ @require-auth="emit('require-auth')"
121
+ />
77
122
  <Divider v-if="index !== following.length - 1" />
78
123
  </template>
79
124
  </div>
80
- <div v-if="tabState.following.loadingMore" class="flex justify-center py-[16px]">
81
- <Icon name="lucide:loader-2" size="20" class="animate-spin text-gray" />
125
+ <div
126
+ v-if="tabState.following.loadingMore"
127
+ class="flex justify-center py-[16px]"
128
+ >
129
+ <Icon
130
+ name="lucide:loader-2"
131
+ size="20"
132
+ class="animate-spin text-gray"
133
+ />
82
134
  </div>
83
135
  </template>
84
136
  <template v-else>
85
- <div v-if="isMyProfile" class="flex flex-col gap-[24px] items-center justify-center h-[200px]">
137
+ <div
138
+ v-if="isMyProfile"
139
+ class="flex flex-col gap-[24px] items-center justify-center h-[200px]"
140
+ >
86
141
  <div class="flex flex-col gap-[8px] items-center">
87
- <div class="font-title-medium-prominent text-black">ทันเหตุการณ์</div>
88
- <div class="flex flex-col font-body-large text-gray items-center">
89
- <div>การติดตามบัญชีเป็นวิธีที่ง่ายในการสร้างลำดับเหตุการณ์</div>
90
- <div>ของคุณและรู้ว่ามีอะไรเกิดขึ้นบ้างกับหัวข้อและผู้ที่คุณสนใจ</div>
142
+ <div class="font-title-medium-prominent text-black">
143
+ ทันเหตุการณ์
144
+ </div>
145
+ <div
146
+ class="flex flex-col font-body-large text-gray items-center"
147
+ >
148
+ <div>
149
+ การติดตามบัญชีเป็นวิธีที่ง่ายในการสร้างลำดับเหตุการณ์
150
+ </div>
151
+ <div>
152
+ ของคุณและรู้ว่ามีอะไรเกิดขึ้นบ้างกับหัวข้อและผู้ที่คุณสนใจ
153
+ </div>
91
154
  </div>
92
155
  </div>
93
- <Button color="primary" @click="emit('explore-profiles')">ติดตามใครดี</Button>
156
+ <Button color="primary" @click="emit('explore-profiles')"
157
+ >ติดตามใครดี</Button
158
+ >
94
159
  </div>
95
- <div v-else class="h-[200px] flex flex-col gap-[8px] items-center justify-center">
160
+ <div
161
+ v-else
162
+ class="h-[200px] flex flex-col gap-[8px] items-center justify-center"
163
+ >
96
164
  <div class="font-title-medium-prominent text-black">
97
165
  {{ profileData?.name }} ไม่ได้กำลังติดตามใคร
98
166
  </div>
99
- <div class="font-body-large text-gray">เมื่อพวกเขาติดตามบัญชี ข้อมูลจะแสดงที่นี่</div>
167
+ <div class="font-body-large text-gray">
168
+ เมื่อพวกเขาติดตามบัญชี ข้อมูลจะแสดงที่นี่
169
+ </div>
100
170
  </div>
101
171
  </template>
102
172
  </ShadTabsContent>
@@ -105,30 +175,65 @@
105
175
  <ShadTabsContent value="favorites" class="flex flex-col mt-0">
106
176
  <template v-if="tabState.favorites.loading">
107
177
  <div class="flex justify-center py-[40px]">
108
- <Icon name="lucide:loader-2" size="24" class="animate-spin text-gray" />
178
+ <Icon
179
+ name="lucide:loader-2"
180
+ size="24"
181
+ class="animate-spin text-gray"
182
+ />
109
183
  </div>
110
184
  </template>
111
185
  <template v-else-if="favorites.length > 0">
112
- <div class="flex flex-col items-center justify-center p-[48px] text-gray font-body-large">
113
- สถานที่โปรด (อยู่ระหว่างการพัฒนา)?
186
+ <div class="flex flex-col gap-[16px]">
187
+ <template
188
+ v-for="(place, index) in favorites"
189
+ :key="place.id || index"
190
+ >
191
+ <CardPlaceItem
192
+ v-model="favorites[index]"
193
+ @require-auth="emit('require-auth')"
194
+ />
195
+ <Divider v-if="index !== favorites.length - 1" />
196
+ </template>
114
197
  </div>
115
- <div v-if="tabState.favorites.loadingMore" class="flex justify-center py-[16px]">
116
- <Icon name="lucide:loader-2" size="20" class="animate-spin text-gray" />
198
+
199
+ <div
200
+ v-if="tabState.favorites.loadingMore"
201
+ class="flex justify-center py-[16px]"
202
+ >
203
+ <Icon
204
+ name="lucide:loader-2"
205
+ size="20"
206
+ class="animate-spin text-gray"
207
+ />
117
208
  </div>
118
209
  </template>
119
210
  <template v-else>
120
- <div v-if="isMyProfile" class="flex flex-col gap-[24px] items-center justify-center h-[200px]">
211
+ <div
212
+ v-if="isMyProfile"
213
+ class="flex flex-col gap-[24px] items-center justify-center h-[200px]"
214
+ >
121
215
  <div class="flex flex-col gap-[8px] items-center">
122
- <div class="font-title-medium-prominent text-black">ยังไม่มีสถานที่โปรด</div>
123
- <div class="font-body-large text-gray">กดหัวใจที่สถานที่ใดก็ได้เพื่อบันทึกไว้ที่นี่</div>
216
+ <div class="font-title-medium-prominent text-black">
217
+ ยังไม่มีสถานที่โปรด
218
+ </div>
219
+ <div class="font-body-large text-gray">
220
+ กดหัวใจที่สถานที่ใดก็ได้เพื่อบันทึกไว้ที่นี่
221
+ </div>
124
222
  </div>
125
- <Button color="primary" @click="emit('explore-places')">ที่ไหนน่าสนใจ</Button>
223
+ <Button color="primary" @click="emit('explore-places')"
224
+ >ที่ไหนน่าสนใจ</Button
225
+ >
126
226
  </div>
127
- <div v-else class="h-[200px] flex flex-col gap-[8px] items-center justify-center">
227
+ <div
228
+ v-else
229
+ class="h-[200px] flex flex-col gap-[8px] items-center justify-center"
230
+ >
128
231
  <div class="font-title-medium-prominent text-black">
129
232
  @{{ profileData?.name }} ยังไม่มีสถานที่โปรด
130
233
  </div>
131
- <div class="font-body-large text-gray">เมื่อเขาบันทึกสถานที่ไว้จะแสดงที่นี่</div>
234
+ <div class="font-body-large text-gray">
235
+ เมื่อเขาบันทึกสถานที่ไว้จะแสดงที่นี่
236
+ </div>
132
237
  </div>
133
238
  </template>
134
239
  </ShadTabsContent>
@@ -179,9 +284,12 @@ const fetchFollowers = async (isLoadMore = false) => {
179
284
  s.page = 1;
180
285
  }
181
286
  try {
182
- const res = await api(`/profiles/${props.profileId}/followers`, {
183
- query: { page: s.page, page_size: PAGE_SIZE }
184
- });
287
+ const res = await api(
288
+ `/profiles/${props.profileId}/followers`,
289
+ {
290
+ query: { page: s.page, page_size: PAGE_SIZE }
291
+ }
292
+ );
185
293
  if (res.meta) s.hasMore = s.page < res.meta.total_pages;
186
294
  if (res.data?.profile) syncProfile(res.data.profile);
187
295
  const items = res.data?.items || [];
@@ -204,9 +312,12 @@ const fetchFollowing = async (isLoadMore = false) => {
204
312
  s.page = 1;
205
313
  }
206
314
  try {
207
- const res = await api(`/profiles/${props.profileId}/following`, {
208
- query: { page: s.page, page_size: PAGE_SIZE }
209
- });
315
+ const res = await api(
316
+ `/profiles/${props.profileId}/following`,
317
+ {
318
+ query: { page: s.page, page_size: PAGE_SIZE }
319
+ }
320
+ );
210
321
  if (res.meta) s.hasMore = s.page < res.meta.total_pages;
211
322
  if (res.data?.profile) syncProfile(res.data.profile);
212
323
  const items = res.data?.items || [];
@@ -229,10 +340,9 @@ const fetchFavorites = async (isLoadMore = false) => {
229
340
  s.page = 1;
230
341
  }
231
342
  try {
232
- const res = await api(
233
- `/profiles/${props.profileId}/favorites`,
234
- { query: { page: s.page, page_size: PAGE_SIZE } }
235
- );
343
+ const res = await api(`/profiles/${props.profileId}/liked-places`, {
344
+ query: { page: s.page, page_size: PAGE_SIZE }
345
+ });
236
346
  if (res.meta) s.hasMore = s.page < res.meta.total_pages;
237
347
  if (res.data?.profile) syncProfile(res.data.profile);
238
348
  const items = res.data?.items || [];
@@ -4,7 +4,7 @@ export interface ProfileSummary {
4
4
  avatar: string;
5
5
  follower_count: number;
6
6
  following_count: number;
7
- favorite_count?: number;
7
+ like_place_count?: number;
8
8
  }
9
9
  export interface DrawerProfileNetworkProps {
10
10
  profileId: string;
@@ -1,130 +1,130 @@
1
1
  <template>
2
- <div class="flex gap-[16px] w-full">
3
- <!-- กรอกข้อมูล -->
4
- <div class="flex flex-col gap-[16px] w-[490px]">
5
- <InputAddress name="address" v-model="modelValue.address" :fixed-province-id="props.fixedProvinceId" />
6
- <template v-if="isAddressCompleted">
7
- <div class="font-body-large-prominent">รายละเอียด</div>
8
- <div class="flex flex-col gap-[4px]">
9
- <InputAutocomplete
10
- name="businessName"
11
- v-model="modelValue.businessName"
12
- label="ชื่อสถานที่ธุรกิจ"
13
- placeholder="กรอกชื่อสถานที่"
14
- required
15
- show-counter
16
- :limit="180"
17
- :free-text="true"
18
- :fetch-fn="fetchApprovedPlaces"
19
- value-key="business_name"
20
- label-key="business_name"
21
- />
22
- <InputTextField
23
- v-if="extraNameCount >= 1"
24
- name="nameTh"
25
- v-model="modelValue.nameTh"
26
- label="ชื่อภาษาไทย"
27
- placeholder="ใส่ในกรณีที่ต่างจากชื่อหลัก"
28
- show-counter
29
- :limit="180"
30
- />
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
- </div>
45
- <InputCombobox
46
- name="categories"
47
- v-model="modelValue.categories"
48
- label="หมวดหมู่"
49
- placeholder="เพิ่มหมวดหมู่ที่เกี่ยวข้องกับสถานที่"
50
- :limit="3"
51
- show-counter
52
- :options="categoryOptions"
53
- required
54
- multiple
55
- />
56
- <InputTextarea
57
- name="description"
58
- v-model="modelValue.description"
59
- label="คำอธิบาย"
60
- placeholder="คำอธิบายเกี่ยวกับสถานที่ (สูงสุด 220 ตัวอักษร)"
61
- :limit="220"
62
- show-counter
63
- />
64
- <InputDateOpening name="openingHours" v-model="modelValue.openingHours" />
65
- <div class="flex flex-col gap-[8px]">
66
- <InputTextField name="phone" v-model="modelValue.phone" label="เบอร์โทรศัพท์" placeholder="กรอกเบอร์โทรศัพท์" />
67
- <InputTextField
68
- v-for="(_, index) in modelValue.extraPhones"
69
- :key="index"
70
- :name="`extraPhone-${index}`"
71
- label="เบอร์โทรศัพท์"
72
- placeholder="กรอกเบอร์โทรศัพท์"
73
- v-model="modelValue.extraPhones[index]"
74
- />
75
- <Button variant="text" color="primary" class="w-[145px]" @click="modelValue.extraPhones.push('')">
76
- <Icon name="lucide:plus" />
77
- เพิ่มเบอร์โทรศัพท์
78
- </Button>
79
- <InputLink name="contactChannels" format="contact_channel" default-first v-model="modelValue.contactChannels" />
80
- </div>
81
- <template v-if="props.state === 'personal'">
82
- <div class="flex flex-col gap-[16px]">
83
- <InputCheckbox name="isReview" v-model="modelValue.isReview" label="คุณต้องการรีวิวสถานที่นี้" />
84
- <template v-if="modelValue.isReview">
85
- <InputRating name="rating" v-model="modelValue.rating" class="flex py-4 justify-center" />
86
- <InputTextarea
87
- name="reviewDescription"
88
- v-model="modelValue.reviewDescription"
89
- label="คำอธิบาย"
90
- placeholder="คำอธิบายเกี่ยวกับสถานที่"
91
- show-counter
92
- />
93
- <div class="flex flex-col gap-[8px]">
94
- <div class="flex flex-col gap-[4px]">
95
- <div class="font-body-large-prominent text-gray">เพิ่มภาพถ่าย</div>
96
- <div class="font-body-small text-gray">สูงสุด 9 รายการ</div>
97
- </div>
98
- <InputFile name="reviewPhotos" v-model="modelValue.reviewPhotos" accept="image/*" :limit="9" />
99
- <div class="font-body-small text-gray w-[250px]">
100
- รองรับไฟล์ *.jpg *.jpeg *.png *.webp *.bmp *.gif ขนาดไฟล์ไม่เกิน 30 mb
101
- </div>
102
- </div>
103
- </template>
104
- </div>
105
- </template>
106
- </template>
107
- </div>
108
-
109
- <!-- แสดง polygon -->
110
- <div class="flex flex-col gap-[16px] w-[334px] sticky top-0 self-start">
111
- <SuggestPlaceMap
112
- :province-id="modelValue.address?.province_id"
113
- :amphur-id="modelValue.address?.amphur_id"
114
- :tambon-id="modelValue.address?.tambon_id"
115
- @update:lat-lng="modelValue.latLng = $event"
116
- />
117
- <InputTextField
118
- name="latitude-longitude"
119
- icon-prepend="lucide:locate-fixed"
120
- readonly
121
- placeholder="ละติจูด, ลองจิจูด"
2
+ <div class="flex gap-[16px] w-full">
3
+ <!-- กรอกข้อมูล -->
4
+ <div class="flex flex-col gap-[16px] w-[490px]">
5
+ <InputAddress name="address" v-model="modelValue.address" :fixed-province-id="props.fixedProvinceId" />
6
+ <template v-if="isAddressCompleted">
7
+ <div class="font-body-large-prominent">รายละเอียด</div>
8
+ <div class="flex flex-col gap-[4px]">
9
+ <InputAutocomplete
10
+ name="businessName"
11
+ v-model="modelValue.businessName"
12
+ label="ชื่อสถานที่ธุรกิจ"
13
+ placeholder="กรอกชื่อสถานที่"
14
+ required
15
+ show-counter
16
+ :limit="180"
17
+ :free-text="true"
18
+ :fetch-fn="fetchApprovedPlaces"
19
+ value-key="business_name"
20
+ label-key="business_name"
21
+ />
22
+ <InputTextField
23
+ v-if="extraNameCount >= 1"
24
+ name="nameTh"
25
+ v-model="modelValue.nameTh"
26
+ label="ชื่อภาษาไทย"
27
+ placeholder="ใส่ในกรณีที่ต่างจากชื่อหลัก"
28
+ show-counter
29
+ :limit="180"
30
+ />
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
+ </div>
45
+ <InputCombobox
46
+ name="categories"
47
+ v-model="modelValue.categories"
48
+ label="หมวดหมู่"
49
+ placeholder="เพิ่มหมวดหมู่ที่เกี่ยวข้องกับสถานที่"
50
+ :limit="3"
51
+ show-counter
52
+ :options="categoryOptions"
53
+ required
54
+ multiple
55
+ />
56
+ <InputTextarea
57
+ name="description"
58
+ v-model="modelValue.description"
59
+ label="คำอธิบาย"
60
+ placeholder="คำอธิบายเกี่ยวกับสถานที่ (สูงสุด 220 ตัวอักษร)"
61
+ :limit="220"
62
+ show-counter
63
+ />
64
+ <InputDateOpening name="openingHours" v-model="modelValue.openingHours" />
65
+ <div class="flex flex-col gap-[8px]">
66
+ <InputTextField name="phone" v-model="modelValue.phone" label="เบอร์โทรศัพท์" placeholder="กรอกเบอร์โทรศัพท์" />
67
+ <InputTextField
68
+ v-for="(_, index) in modelValue.extraPhones"
69
+ :key="index"
70
+ :name="`extraPhone-${index}`"
71
+ label="เบอร์โทรศัพท์"
72
+ placeholder="กรอกเบอร์โทรศัพท์"
73
+ v-model="modelValue.extraPhones[index]"
74
+ />
75
+ <Button variant="text" color="primary" class="w-[145px]" @click="modelValue.extraPhones.push('')">
76
+ <Icon name="lucide:plus" />
77
+ เพิ่มเบอร์โทรศัพท์
78
+ </Button>
79
+ <InputLink name="contactChannels" format="contact_channel" default-first v-model="modelValue.contactChannels" />
80
+ </div>
81
+ <template v-if="props.state === 'personal'">
82
+ <div class="flex flex-col gap-[16px]">
83
+ <InputCheckbox name="isReview" v-model="modelValue.isReview" label="คุณต้องการรีวิวสถานที่นี้" />
84
+ <template v-if="modelValue.isReview">
85
+ <InputRating name="rating" v-model="modelValue.rating" class="flex py-4 justify-center" />
86
+ <InputTextarea
87
+ name="reviewDescription"
88
+ v-model="modelValue.reviewDescription"
89
+ label="คำอธิบาย"
90
+ placeholder="คำอธิบายเกี่ยวกับสถานที่"
91
+ show-counter
92
+ />
93
+ <div class="flex flex-col gap-[8px]">
94
+ <div class="flex flex-col gap-[4px]">
95
+ <div class="font-body-large-prominent text-gray">เพิ่มภาพถ่าย</div>
96
+ <div class="font-body-small text-gray">สูงสุด 9 รายการ</div>
97
+ </div>
98
+ <InputFile name="reviewPhotos" v-model="modelValue.reviewPhotos" accept="image/*" :limit="9" />
99
+ <div class="font-body-small text-gray w-[250px]">
100
+ รองรับไฟล์ *.jpg *.jpeg *.png *.webp *.bmp *.gif ขนาดไฟล์ไม่เกิน 30 mb
101
+ </div>
102
+ </div>
103
+ </template>
104
+ </div>
105
+ </template>
106
+ </template>
107
+ </div>
108
+
109
+ <!-- แสดง polygon -->
110
+ <div class="flex flex-col gap-[16px] w-[334px] sticky top-0 self-start">
111
+ <SuggestPlaceMap
112
+ :province-id="modelValue.address?.province_id"
113
+ :amphur-id="modelValue.address?.amphur_id"
114
+ :tambon-id="modelValue.address?.tambon_id"
115
+ @update:lat-lng="modelValue.latLng = $event"
116
+ />
117
+ <InputTextField
118
+ name="latitude-longitude"
119
+ icon-prepend="lucide:locate-fixed"
120
+ readonly
121
+ placeholder="ละติจูด, ลองจิจูด"
122
122
  :model-value="
123
123
  modelValue.latLng ? `${modelValue.latLng.lat.toFixed(6)}, ${modelValue.latLng.lng.toFixed(6)}` : ''
124
- "
125
- />
126
- </div>
127
- </div>
124
+ "
125
+ />
126
+ </div>
127
+ </div>
128
128
  </template>
129
129
 
130
130
  <script setup>
@@ -48,8 +48,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
48
48
  pageSize: number;
49
49
  id: string;
50
50
  name: string;
51
- description: string;
52
51
  options: AutocompleteOption[] | string[] | number[];
52
+ description: string;
53
53
  limit: number;
54
54
  placeholder: string;
55
55
  disabledErrorMessage: boolean;
@@ -48,8 +48,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
48
48
  pageSize: number;
49
49
  id: string;
50
50
  name: string;
51
- description: string;
52
51
  options: AutocompleteOption[] | string[] | number[];
52
+ description: string;
53
53
  limit: number;
54
54
  placeholder: string;
55
55
  disabledErrorMessage: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pukaad-ui-lib",
3
- "version": "1.298.0",
3
+ "version": "1.300.0",
4
4
  "description": "pukaad-ui for MeMSG",
5
5
  "repository": {
6
6
  "type": "git",