@awes-io/ui 2.142.3 → 2.144.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 (264) hide show
  1. package/assets/css/components/_index.css +7 -1
  2. package/assets/css/components/action-card.css +1 -0
  3. package/assets/css/components/action-icon.css +2 -2
  4. package/assets/css/components/alert.css +28 -22
  5. package/assets/css/components/animation.css +52 -32
  6. package/assets/css/components/badge.css +1 -0
  7. package/assets/css/components/banner-text.css +15 -4
  8. package/assets/css/components/card.css +0 -1
  9. package/assets/css/components/content-placeholder.css +104 -0
  10. package/assets/css/components/dropdown.css +20 -7
  11. package/assets/css/components/empty-container.css +69 -1
  12. package/assets/css/components/filter-chosen.css +6 -0
  13. package/assets/css/components/filter-date-range.css +17 -1
  14. package/assets/css/components/filter-month.css +23 -17
  15. package/assets/css/components/filter-select.css +11 -0
  16. package/assets/css/components/icon-menu-item.css +12 -7
  17. package/assets/css/components/layout.css +1 -32
  18. package/assets/css/components/mobile-menu-nav.css +8 -4
  19. package/assets/css/components/modal.css +1 -1
  20. package/assets/css/components/number.css +12 -0
  21. package/assets/css/components/page-aside.css +54 -0
  22. package/assets/css/components/text-field.css +4 -0
  23. package/assets/js/css.js +1 -1
  24. package/assets/js/icons/mono.js +59 -91
  25. package/assets/js/icons/multicolor.js +1 -31
  26. package/components/1_atoms/AwActionIcon.vue +11 -2
  27. package/components/1_atoms/AwContentPlaceholder.vue +60 -0
  28. package/components/1_atoms/AwFlow.vue +37 -49
  29. package/components/1_atoms/AwGrid.vue +11 -3
  30. package/components/1_atoms/AwIcon/AwIcon.vue +5 -3
  31. package/components/1_atoms/AwIcon/AwIconSystemMono.vue +3 -2
  32. package/components/1_atoms/AwInput.vue +2 -2
  33. package/components/1_atoms/AwLabel.vue +1 -1
  34. package/components/1_atoms/AwList.vue +3 -1
  35. package/components/1_atoms/AwRadio.vue +1 -1
  36. package/components/1_atoms/AwSlider.vue +15 -1
  37. package/components/1_atoms/AwTag.vue +6 -1
  38. package/components/2_molecules/AwAlert.vue +63 -42
  39. package/components/2_molecules/AwBadge.vue +1 -1
  40. package/components/2_molecules/AwBannerText.vue +8 -2
  41. package/components/2_molecules/AwButton.vue +1 -1
  42. package/components/2_molecules/AwDescriptionInput.vue +19 -1
  43. package/components/2_molecules/AwEmptyContainer.vue +74 -72
  44. package/components/2_molecules/AwNumber.vue +180 -0
  45. package/components/2_molecules/AwSelect.vue +11 -4
  46. package/components/3_organisms/AwBottomBar.vue +22 -4
  47. package/components/3_organisms/AwFilterChosen.vue +73 -0
  48. package/components/3_organisms/AwFilterDateRange.vue +177 -0
  49. package/components/3_organisms/AwFilterMonth.vue +37 -40
  50. package/components/3_organisms/AwFilterSelect.vue +368 -0
  51. package/components/3_organisms/AwMultiBlockBuilder.vue +1 -1
  52. package/components/3_organisms/AwSubnav.vue +11 -1
  53. package/components/3_organisms/AwTable/AwTableBuilder.vue +20 -60
  54. package/components/3_organisms/AwTable/_AwTableCellDropdown.vue +6 -1
  55. package/components/3_organisms/AwTable/_AwTableRow.vue +2 -1
  56. package/components/4_pages/AwPage.vue +1 -0
  57. package/components/4_pages/AwPageAside.vue +108 -0
  58. package/components/5_layouts/AwLayoutCenter.vue +3 -8
  59. package/components/5_layouts/_AwMenuItemIcon.vue +9 -2
  60. package/components/5_layouts/_AwMobileMenuItem.vue +5 -3
  61. package/components/5_layouts/_AwUserMenu.vue +1 -1
  62. package/components/_config.js +26 -1
  63. package/docs/_template.md +80 -0
  64. package/docs/components/atoms/aw-accordion-fold.md +129 -0
  65. package/docs/components/atoms/aw-action-card-body.md +99 -0
  66. package/docs/components/atoms/aw-action-card.md +130 -0
  67. package/docs/components/atoms/aw-action-icon.md +126 -0
  68. package/docs/components/atoms/aw-avatar.md +106 -0
  69. package/docs/components/atoms/aw-card.md +137 -0
  70. package/docs/components/atoms/aw-checkbox.md +288 -0
  71. package/docs/components/atoms/aw-content-placeholder.md +147 -0
  72. package/docs/components/atoms/aw-description.md +83 -0
  73. package/docs/components/atoms/aw-dock.md +90 -0
  74. package/docs/components/atoms/aw-dropdown-button.md +94 -0
  75. package/docs/components/atoms/aw-dropdown.md +178 -0
  76. package/docs/components/atoms/aw-file.md +73 -0
  77. package/docs/components/atoms/aw-flow.md +140 -0
  78. package/docs/components/atoms/aw-grid.md +109 -0
  79. package/docs/components/atoms/aw-headline.md +71 -0
  80. package/docs/components/atoms/aw-icon-system-color.md +122 -0
  81. package/docs/components/atoms/aw-icon-system-mono.md +206 -0
  82. package/docs/components/atoms/aw-icon.md +235 -0
  83. package/docs/components/atoms/aw-info.md +123 -0
  84. package/docs/components/atoms/aw-input.md +212 -0
  85. package/docs/components/atoms/aw-label.md +136 -0
  86. package/docs/components/atoms/aw-link.md +151 -0
  87. package/docs/components/atoms/aw-list.md +152 -0
  88. package/docs/components/atoms/aw-progress.md +119 -0
  89. package/docs/components/atoms/aw-radio.md +182 -0
  90. package/docs/components/atoms/aw-refresh-wrapper.md +81 -0
  91. package/docs/components/atoms/aw-select-native.md +234 -0
  92. package/docs/components/atoms/aw-slider.md +189 -0
  93. package/docs/components/atoms/aw-sub-headline.md +73 -0
  94. package/docs/components/atoms/aw-switcher.md +192 -0
  95. package/docs/components/atoms/aw-tag.md +144 -0
  96. package/docs/components/atoms/aw-title.md +70 -0
  97. package/docs/components/atoms/aw-toggler.md +90 -0
  98. package/docs/components/layouts/aw-layout-center.md +168 -0
  99. package/docs/components/layouts/aw-layout-error.md +153 -0
  100. package/docs/components/layouts/aw-layout-provider.md +238 -0
  101. package/docs/components/layouts/aw-layout.md +88 -0
  102. package/docs/components/molecules/aw-action-button.md +138 -0
  103. package/docs/components/molecules/aw-alert.md +191 -0
  104. package/docs/components/molecules/aw-badge.md +129 -0
  105. package/docs/components/molecules/aw-banner-text.md +156 -0
  106. package/docs/components/molecules/aw-button-nav.md +111 -0
  107. package/docs/components/molecules/aw-button.md +193 -0
  108. package/docs/components/molecules/aw-description-input.md +124 -0
  109. package/docs/components/molecules/aw-empty-container.md +235 -0
  110. package/docs/components/molecules/aw-island.md +506 -0
  111. package/docs/components/molecules/aw-number.md +138 -0
  112. package/docs/components/molecules/aw-select-object.md +401 -0
  113. package/docs/components/molecules/aw-select.md +215 -0
  114. package/docs/components/molecules/aw-tab-nav.md +108 -0
  115. package/docs/components/molecules/aw-tel.md +129 -0
  116. package/docs/components/molecules/aw-textarea.md +83 -0
  117. package/docs/components/molecules/aw-userpic.md +115 -0
  118. package/docs/components/organisms/aw-address-block.md +64 -0
  119. package/docs/components/organisms/aw-address.md +132 -0
  120. package/docs/components/organisms/aw-birthday-picker.md +73 -0
  121. package/docs/components/organisms/aw-bottom-bar.md +66 -0
  122. package/docs/components/organisms/aw-calendar-days.md +115 -0
  123. package/docs/components/organisms/aw-calendar-nav.md +98 -0
  124. package/docs/components/organisms/aw-calendar-view.md +98 -0
  125. package/docs/components/organisms/aw-calendar.md +166 -0
  126. package/docs/components/organisms/aw-chart.md +154 -0
  127. package/docs/components/organisms/aw-chip-select.md +164 -0
  128. package/docs/components/organisms/aw-chip.md +126 -0
  129. package/docs/components/organisms/aw-code-snippet.md +94 -0
  130. package/docs/components/organisms/aw-code.md +132 -0
  131. package/docs/components/organisms/aw-context-menu.md +117 -0
  132. package/docs/components/organisms/aw-cropper.md +151 -0
  133. package/docs/components/organisms/aw-date.md +161 -0
  134. package/docs/components/organisms/aw-display-date.md +33 -0
  135. package/docs/components/organisms/aw-download-link.md +46 -0
  136. package/docs/components/organisms/aw-fetch-data.md +161 -0
  137. package/docs/components/organisms/aw-filter-chosen.md +226 -0
  138. package/docs/components/organisms/aw-filter-date-range.md +205 -0
  139. package/docs/components/organisms/aw-filter-month.md +43 -0
  140. package/docs/components/organisms/aw-filter-select.md +239 -0
  141. package/docs/components/organisms/aw-form.md +174 -0
  142. package/docs/components/organisms/aw-gmap-marker.md +86 -0
  143. package/docs/components/organisms/aw-gmap.md +90 -0
  144. package/docs/components/organisms/aw-image-upload.md +56 -0
  145. package/docs/components/organisms/aw-island-avatar.md +87 -0
  146. package/docs/components/organisms/aw-markdown-editor.md +104 -0
  147. package/docs/components/organisms/aw-modal-buttons.md +57 -0
  148. package/docs/components/organisms/aw-modal.md +246 -0
  149. package/docs/components/organisms/aw-model-edit.md +74 -0
  150. package/docs/components/organisms/aw-money.md +53 -0
  151. package/docs/components/organisms/aw-multi-block-builder.md +165 -0
  152. package/docs/components/organisms/aw-pagination.md +121 -0
  153. package/docs/components/organisms/aw-password.md +103 -0
  154. package/docs/components/organisms/aw-preview-card.md +45 -0
  155. package/docs/components/organisms/aw-search.md +116 -0
  156. package/docs/components/organisms/aw-subnav.md +122 -0
  157. package/docs/components/organisms/aw-table-builder.md +165 -0
  158. package/docs/components/organisms/aw-table-col.md +123 -0
  159. package/docs/components/organisms/aw-table-head.md +92 -0
  160. package/docs/components/organisms/aw-table-row.md +91 -0
  161. package/docs/components/organisms/aw-table.md +172 -0
  162. package/docs/components/organisms/aw-tags.md +54 -0
  163. package/docs/components/organisms/aw-toggle-show-aside.md +43 -0
  164. package/docs/components/organisms/aw-uploader-files.md +125 -0
  165. package/docs/components/organisms/aw-uploader.md +163 -0
  166. package/docs/components/organisms/aw-user-menu.md +87 -0
  167. package/docs/components/pages/aw-page-aside.md +296 -0
  168. package/docs/components/pages/aw-page-menu-buttons.md +172 -0
  169. package/docs/components/pages/aw-page-modal.md +198 -0
  170. package/docs/components/pages/aw-page-single.md +300 -0
  171. package/docs/components/pages/aw-page.md +194 -0
  172. package/docs/configuration.md +493 -0
  173. package/docs/cookbook/advanced-patterns.md +1388 -0
  174. package/docs/cookbook/common-patterns.md +965 -0
  175. package/docs/cookbook/index.md +786 -0
  176. package/docs/getting-started.md +596 -0
  177. package/docs/guides/best-practices.md +1106 -0
  178. package/docs/guides/data-fetching.md +852 -0
  179. package/docs/guides/error-handling.md +1172 -0
  180. package/docs/guides/forms-guide.md +1329 -0
  181. package/docs/guides/mobile-subnavigation.md +359 -0
  182. package/docs/guides/page-patterns/aside-pages.md +1418 -0
  183. package/docs/guides/page-patterns/dashboard-pages.md +990 -0
  184. package/docs/guides/page-patterns/detail-pages.md +1556 -0
  185. package/docs/guides/page-patterns/list-pages.md +1242 -0
  186. package/docs/index.md +263 -1
  187. package/docs/integrations.md +870 -0
  188. package/docs/reference/colors.md +232 -0
  189. package/docs/reference/icons.md +163 -0
  190. package/docs/reference/menu.md +462 -0
  191. package/docs/reference/plugins.md +970 -0
  192. package/docs/reference/troubleshooting.md +964 -0
  193. package/nuxt/awes.config.js +9 -8
  194. package/nuxt/index.js +2 -2
  195. package/nuxt/pages/more.vue +1 -1
  196. package/package.json +5 -3
  197. package/readme.md +171 -1
  198. package/store/awesIo.js +11 -0
  199. package/CHANGELOG.md +0 -4520
  200. package/docs/aw-accordion-fold.md +0 -46
  201. package/docs/aw-address.md +0 -44
  202. package/docs/aw-avatar.md +0 -51
  203. package/docs/aw-badge.md +0 -32
  204. package/docs/aw-button-nav.md +0 -44
  205. package/docs/aw-button.md +0 -50
  206. package/docs/aw-calendar-days.md +0 -46
  207. package/docs/aw-calendar-nav.md +0 -25
  208. package/docs/aw-calendar-view.md +0 -12
  209. package/docs/aw-calendar.md +0 -59
  210. package/docs/aw-card.md +0 -48
  211. package/docs/aw-chart.md +0 -51
  212. package/docs/aw-checkbox.md +0 -56
  213. package/docs/aw-chip-select.md +0 -46
  214. package/docs/aw-chip.md +0 -53
  215. package/docs/aw-code-snippet.md +0 -18
  216. package/docs/aw-code.md +0 -56
  217. package/docs/aw-content-wrapper.md +0 -40
  218. package/docs/aw-context-menu.md +0 -31
  219. package/docs/aw-cropper.md +0 -60
  220. package/docs/aw-dashboard-card.md +0 -37
  221. package/docs/aw-dashboard-donut.md +0 -30
  222. package/docs/aw-dashboard-line.md +0 -20
  223. package/docs/aw-dashboard-progress.md +0 -33
  224. package/docs/aw-dashboard-section.md +0 -32
  225. package/docs/aw-dashboard-speed.md +0 -30
  226. package/docs/aw-date.md +0 -52
  227. package/docs/aw-dropdown-button.md +0 -31
  228. package/docs/aw-dropdown.md +0 -69
  229. package/docs/aw-fetch-data.md +0 -45
  230. package/docs/aw-form.md +0 -52
  231. package/docs/aw-grid.md +0 -48
  232. package/docs/aw-icon.md +0 -50
  233. package/docs/aw-info.md +0 -53
  234. package/docs/aw-input.md +0 -55
  235. package/docs/aw-layout-default.md +0 -30
  236. package/docs/aw-layout-frame-center.md +0 -29
  237. package/docs/aw-layout-simple.md +0 -49
  238. package/docs/aw-link.md +0 -54
  239. package/docs/aw-markdown-editor.md +0 -51
  240. package/docs/aw-modal.md +0 -63
  241. package/docs/aw-multi-block-builder.md +0 -66
  242. package/docs/aw-page.md +0 -36
  243. package/docs/aw-pagination.md +0 -54
  244. package/docs/aw-password.md +0 -48
  245. package/docs/aw-radio.md +0 -54
  246. package/docs/aw-search.md +0 -49
  247. package/docs/aw-select.md +0 -93
  248. package/docs/aw-slider.md +0 -40
  249. package/docs/aw-svg-image.md +0 -19
  250. package/docs/aw-switcher.md +0 -51
  251. package/docs/aw-tab-nav.md +0 -55
  252. package/docs/aw-table-builder.md +0 -58
  253. package/docs/aw-table-col.md +0 -33
  254. package/docs/aw-table-head.md +0 -28
  255. package/docs/aw-table-row.md +0 -33
  256. package/docs/aw-table.md +0 -59
  257. package/docs/aw-tel.md +0 -47
  258. package/docs/aw-textarea.md +0 -47
  259. package/docs/aw-toggler.md +0 -41
  260. package/docs/aw-uploader-files.md +0 -20
  261. package/docs/aw-uploader.md +0 -60
  262. package/docs/aw-user-menu.md +0 -34
  263. package/docs/aw-userpic.md +0 -34
  264. /package/components/{3_organisms → 2_molecules}/AwTel.vue +0 -0
@@ -0,0 +1,1242 @@
1
+ # List Page Patterns
2
+
3
+ Complete guide to building list pages with tables, search, and actions using AwPage and AwTableBuilder.
4
+
5
+ ## When to Use AwPage
6
+
7
+ Use `AwPage` for:
8
+ - **List pages** - Display collections of items in tables
9
+ - **Dashboards** - Overview pages with metrics and data
10
+ - **Index pages** - Entry points to sections with navigation
11
+
12
+ **Key characteristics:**
13
+ - Shows aside menu by default
14
+ - Simple title prop
15
+ - Automatic integration with menu system
16
+ - Best for browsing and navigation
17
+
18
+ ## Basic List Page
19
+
20
+ ### Minimal Example
21
+
22
+ ```markup
23
+ <template>
24
+ <AwPage title="Customers">
25
+ <AwTableBuilder
26
+ :collection="customers"
27
+ @click:row="viewCustomer"
28
+ >
29
+ <AwTableCol field="name" title="Name" />
30
+ <AwTableCol field="email" title="Email" />
31
+ <AwTableCol field="phone" title="Phone" />
32
+ </AwTableBuilder>
33
+ </AwPage>
34
+ </template>
35
+
36
+ <script>
37
+ import Customers from '~/collections/Customers'
38
+
39
+ export default {
40
+ middleware: 'auth',
41
+
42
+ data() {
43
+ return {
44
+ customers: new Customers([], {
45
+ shop_uuid: this.$route.params.shop_uuid
46
+ })
47
+ }
48
+ },
49
+
50
+ methods: {
51
+ viewCustomer(customer) {
52
+ this.$router.push(`/${this.$route.params.shop_uuid}/customers/${customer.id}`)
53
+ }
54
+ }
55
+ }
56
+ </script>
57
+ ```
58
+
59
+ **What happens:**
60
+ 1. ✅ AwTableBuilder automatically calls `customers.fetch()` on mount
61
+ 2. ✅ No manual `fetch()` or `mounted()` needed
62
+ 3. ✅ Collection handles pagination automatically
63
+ 4. ✅ Loading states managed internally
64
+ 5. ✅ Row click navigates to detail page
65
+
66
+ **⚠️ Important: Don't use v-if for loading**
67
+
68
+ ```markup
69
+ <!-- ❌ BAD - Prevents AwTableBuilder from mounting and fetching -->
70
+ <template>
71
+ <AwPage title="Customers">
72
+ <div v-if="loading">Loading...</div>
73
+ <AwTableBuilder v-else :collection="customers">
74
+ <!-- Never mounts if loading=true initially -->
75
+ </AwTableBuilder>
76
+ </AwPage>
77
+ </template>
78
+
79
+ <!-- ✅ GOOD - Let AwTableBuilder handle its own loading -->
80
+ <template>
81
+ <AwPage title="Customers">
82
+ <AwTableBuilder :collection="customers">
83
+ <!-- Mounts immediately, shows internal loading state -->
84
+ </AwTableBuilder>
85
+ </AwPage>
86
+ </template>
87
+ ```
88
+
89
+ ## Header Action Buttons
90
+
91
+ **⚠️ Important: Create buttons should not be in menu items**
92
+
93
+ Create actions should only appear on list pages using `AwPageMenuButtons` and in empty states. Do not add create pages to menu navigation items.
94
+
95
+ ### Single CTA Button
96
+
97
+ For navigation to create pages, use `href` instead of `listeners`:
98
+
99
+ ```markup
100
+ <template>
101
+ <AwPage title="Templates">
102
+ <template #buttons>
103
+ <AwPageMenuButtons
104
+ :items="[{
105
+ text: 'Create Template',
106
+ icon: 'awesio/plus',
107
+ cta: true,
108
+ href: '/templates/create'
109
+ }]"
110
+ />
111
+ </template>
112
+
113
+ <AwTableBuilder :collection="templates">
114
+ <!-- Table columns -->
115
+ </AwTableBuilder>
116
+ </AwPage>
117
+ </template>
118
+ ```
119
+
120
+ ### Multiple Action Buttons
121
+
122
+ ```markup
123
+ <template #buttons>
124
+ <AwPageMenuButtons
125
+ :items="[
126
+ {
127
+ text: 'Export',
128
+ icon: 'awesio/download',
129
+ color: 'mono',
130
+ listeners: { click: exportData }
131
+ },
132
+ {
133
+ text: 'Import',
134
+ icon: 'awesio/upload',
135
+ color: 'mono',
136
+ listeners: { click: importData }
137
+ },
138
+ {
139
+ text: 'Create',
140
+ icon: 'awesio/plus',
141
+ cta: true,
142
+ listeners: { click: createItem }
143
+ }
144
+ ]"
145
+ />
146
+ </template>
147
+ ```
148
+
149
+ **Button Configuration:**
150
+ - `text` - Button label (required)
151
+ - `icon` - Icon name (optional)
152
+ - `color` - `'mono'`, `'primary'`, `'accent'` (default: 'mono')
153
+ - `cta` - Makes button prominent with accent color (default: false)
154
+ - `href` - Navigation URL (use for navigation, preferred over listeners)
155
+ - `listeners` - Event handlers object `{ click: handler }` (use only for non-navigation actions)
156
+
157
+ **Button Ordering:**
158
+ - Secondary actions (Export, Import) first
159
+ - Primary CTA (Create, Add) last
160
+ - CTA buttons automatically styled with accent color
161
+
162
+ ## Search Integration
163
+
164
+ ### Basic Search
165
+
166
+ ```markup
167
+ <template>
168
+ <AwPage title="Customers">
169
+ <!-- Search bar -->
170
+ <div class="flex justify-end mb-6">
171
+ <AwSearch class="w-full lg:w-auto" />
172
+ </div>
173
+
174
+ <!-- Table watches for search param changes -->
175
+ <AwTableBuilder
176
+ :collection="customers"
177
+ :watch-params="['search']"
178
+ >
179
+ <AwTableCol field="name" title="Name" />
180
+ <AwTableCol field="email" title="Email" />
181
+ </AwTableBuilder>
182
+ </AwPage>
183
+ </template>
184
+ ```
185
+
186
+ **How it works:**
187
+ 1. ✅ AwSearch updates `$route.query.search`
188
+ 2. ✅ AwTableBuilder watches `search` param via `:watch-params`
189
+ 3. ✅ Table automatically refetches when search changes
190
+ 4. ✅ Collection receives search param in API request
191
+
192
+ ### Search with Filters
193
+
194
+ When using `AwFilterSelect` components that sync with URL parameters, use `AwFilterChosen` to display active filters:
195
+
196
+ ```markup
197
+ <template>
198
+ <AwPage title="Users">
199
+ <div class="flex flex-wrap gap-4 mb-6">
200
+ <AwFilterSelect
201
+ param="is_active"
202
+ label="Status"
203
+ :options="statusOptions"
204
+ single
205
+ />
206
+ <AwFilterSelect
207
+ param="permissions"
208
+ label="Permissions"
209
+ :options="permissionOptions"
210
+ />
211
+ <AwSearch class="ml-auto" />
212
+ </div>
213
+
214
+ <AwFilterChosen :watch-params="['is_active', 'permissions']">
215
+ <template #is_active="{ value }">
216
+ {{ getStatusLabel(value) }}
217
+ </template>
218
+ <template #permissions="{ value }">
219
+ {{ getPermissionLabel(value) }}
220
+ </template>
221
+ </AwFilterChosen>
222
+
223
+ <AwTableBuilder
224
+ :collection="users"
225
+ :watch-params="['search', 'is_active', 'permissions']"
226
+ >
227
+ <AwTableCol field="first_name" title="First Name" />
228
+ <AwTableCol field="last_name" title="Last Name" />
229
+ </AwTableBuilder>
230
+ </AwPage>
231
+ </template>
232
+
233
+ <script>
234
+ export default {
235
+ computed: {
236
+ statusOptions() {
237
+ return [
238
+ { id: 'true', title: 'Active' },
239
+ { id: 'false', title: 'Inactive' }
240
+ ]
241
+ },
242
+
243
+ permissionOptions() {
244
+ return [
245
+ { id: 'manage-profile', title: 'Manage profile' },
246
+ { id: 'manage-users', title: 'Manage users' }
247
+ ]
248
+ }
249
+ },
250
+
251
+ methods: {
252
+ getStatusLabel(value) {
253
+ if (value === true || value === 'true') return 'Active'
254
+ if (value === false || value === 'false') return 'Inactive'
255
+ return value
256
+ },
257
+
258
+ getPermissionLabel(value) {
259
+ if (!value) return value
260
+
261
+ const permissions = Array.isArray(value)
262
+ ? value
263
+ : typeof value === 'string'
264
+ ? value.split(',').map((p) => p.trim())
265
+ : [value]
266
+
267
+ return permissions
268
+ .map((perm) => {
269
+ const option = this.permissionOptions.find(
270
+ (opt) => opt.id === perm || String(opt.id) === String(perm)
271
+ )
272
+ return option ? option.title : perm
273
+ })
274
+ .join(', ')
275
+ }
276
+ }
277
+ }
278
+ </script>
279
+ ```
280
+
281
+ **Best Practices:**
282
+ - ✅ **Always use custom slots** in `AwFilterChosen` to display user-friendly text instead of raw parameter values
283
+ - ✅ **Omit `'search'` from watch-params** - Search doesn't need to be displayed as a chip
284
+ - ✅ **Place search on the right** using `ml-auto` class
285
+ - ✅ **Handle different value types** - Values can be strings, booleans, arrays, or comma-separated strings
286
+
287
+ ## Button Nav Filtering
288
+
289
+ For simple toggle filters (like Active/Inactive, All/Pending/Completed), use `AwButtonNav` with query parameters:
290
+
291
+ ### Basic Button Nav Filter
292
+
293
+ ```markup
294
+ <template>
295
+ <AwPage title="Services">
296
+ <AwButtonNav :items="filterItems" class="mb-6" />
297
+
298
+ <AwTableBuilder
299
+ :collection="services"
300
+ :watch-params="['is_active']"
301
+ >
302
+ <AwTableCol field="name" title="Service" />
303
+ <AwTableCol field="price" title="Price" />
304
+ </AwTableBuilder>
305
+ </AwPage>
306
+ </template>
307
+
308
+ <script>
309
+ import { BaseCollection } from '@awes-io/vue-mc'
310
+
311
+ class Services extends BaseCollection {
312
+ routes() {
313
+ return {
314
+ fetch: '/api/services'
315
+ }
316
+ }
317
+ }
318
+
319
+ export default {
320
+ data() {
321
+ return {
322
+ services: new Services()
323
+ }
324
+ },
325
+
326
+ computed: {
327
+ filterItems() {
328
+ return [
329
+ {
330
+ text: 'Active',
331
+ href: { query: { is_active: null } }
332
+ },
333
+ {
334
+ text: 'Inactive',
335
+ href: { query: { is_active: '0' } }
336
+ }
337
+ ]
338
+ }
339
+ }
340
+ }
341
+ </script>
342
+ ```
343
+
344
+ **How it works:**
345
+ 1. ✅ `AwButtonNav` items use `href: { query: { param: value } }` format
346
+ 2. ✅ Setting `param: null` removes the parameter from URL (shows all active items)
347
+ 3. ✅ Setting `param: '0'` adds `is_active=0` to URL (shows inactive items)
348
+ 4. ✅ `AwTableBuilder` watches `is_active` param via `:watch-params`
349
+ 5. ✅ Table automatically refetches when query parameter changes
350
+
351
+ ### Multiple Filter Options
352
+
353
+ For filters with more than two options:
354
+
355
+ ```markup
356
+ <template>
357
+ <AwPage title="Orders">
358
+ <AwButtonNav :items="statusFilters" class="mb-6" />
359
+
360
+ <AwTableBuilder
361
+ :collection="orders"
362
+ :watch-params="['status']"
363
+ >
364
+ <AwTableCol field="number" title="Order #" />
365
+ <AwTableCol field="total" title="Total" />
366
+ </AwTableBuilder>
367
+ </AwPage>
368
+ </template>
369
+
370
+ <script>
371
+ export default {
372
+ computed: {
373
+ statusFilters() {
374
+ return [
375
+ {
376
+ text: 'All',
377
+ href: { query: { status: null } }
378
+ },
379
+ {
380
+ text: 'Pending',
381
+ href: { query: { status: 'pending' } }
382
+ },
383
+ {
384
+ text: 'Completed',
385
+ href: { query: { status: 'completed' } }
386
+ },
387
+ {
388
+ text: 'Cancelled',
389
+ href: { query: { status: 'cancelled' } }
390
+ }
391
+ ]
392
+ }
393
+ }
394
+ }
395
+ </script>
396
+ ```
397
+
398
+ ### Button Nav with Icons
399
+
400
+ You can add icons to filter buttons:
401
+
402
+ ```markup
403
+ <template>
404
+ <AwButtonNav :items="filterItems" class="mb-6" />
405
+ </template>
406
+
407
+ <script>
408
+ export default {
409
+ computed: {
410
+ filterItems() {
411
+ return [
412
+ {
413
+ text: 'Active',
414
+ icon: 'awesio/check-circle',
415
+ href: { query: { is_active: null } }
416
+ },
417
+ {
418
+ text: 'Inactive',
419
+ icon: 'awesio/x-circle',
420
+ href: { query: { is_active: '0' } }
421
+ }
422
+ ]
423
+ }
424
+ }
425
+ }
426
+ </script>
427
+ ```
428
+
429
+ **Best Practices:**
430
+ - ✅ **Use for simple toggle filters** - Best for 2-4 options (Active/Inactive, All/Pending/Completed)
431
+ - ✅ **Use `null` to remove parameters** - Setting `param: null` removes it from URL, showing unfiltered results
432
+ - ✅ **Use `AwFilterSelect` for complex filters** - When you have many options or need multi-select, use `AwFilterSelect` instead
433
+ - ✅ **Combine with search** - Button nav filters work well alongside `AwSearch` for combined filtering
434
+
435
+ ## Custom Column Rendering
436
+
437
+ ### Understanding the `field` Prop
438
+
439
+ **⚠️ Critical Distinction:**
440
+
441
+ ```markup
442
+ <!-- WITH field: cell = field value only -->
443
+ <AwTableCol title="Name" field="name">
444
+ <template #default="{ cell }">
445
+ {{ cell }} <!-- cell is just the string value of 'name' -->
446
+ </template>
447
+ </AwTableCol>
448
+
449
+ <!-- WITHOUT field: cell = entire row object -->
450
+ <AwTableCol title="Name">
451
+ <template #default="{ cell }">
452
+ {{ cell.name }} <!-- cell is the full row object -->
453
+ {{ cell.email }} <!-- can access other fields -->
454
+ </template>
455
+ </AwTableCol>
456
+ ```
457
+
458
+ ### Simple Field Display
459
+
460
+ ```markup
461
+ <!-- No custom template needed for simple fields -->
462
+ <AwTableCol field="name" title="Name" />
463
+ <AwTableCol field="email" title="Email" />
464
+ <AwTableCol field="phone" title="Phone" />
465
+ ```
466
+
467
+ ### Status Badge
468
+
469
+ ```markup
470
+ <AwTableCol title="Status">
471
+ <template #default="{ cell }">
472
+ <AwLabel
473
+ :label="cell.is_active ? 'Active' : 'Inactive'"
474
+ :color="cell.is_active ? 'success' : 'mono'"
475
+ />
476
+ </template>
477
+ </AwTableCol>
478
+ ```
479
+
480
+ ### Date Formatting
481
+
482
+ ```markup
483
+ <AwTableCol title="Created">
484
+ <template #default="{ cell }">
485
+ {{ $dayjs(cell.created_at).format('ll') }}
486
+ </template>
487
+ </AwTableCol>
488
+ ```
489
+
490
+ **Common date formats:**
491
+ - `ll` - Jan 15, 2024
492
+ - `lll` - Jan 15, 2024 10:30 AM
493
+ - `LL` - January 15, 2024
494
+ - `MMMM YYYY` - January 2024
495
+
496
+ ### Currency Formatting
497
+
498
+ ```markup
499
+ <AwTableCol title="Total">
500
+ <template #default="{ cell }">
501
+ ${{ (cell.total / 100).toFixed(2) }}
502
+ </template>
503
+ </AwTableCol>
504
+ ```
505
+
506
+ ### Combined Data
507
+
508
+ ```markup
509
+ <AwTableCol title="Customer">
510
+ <template #default="{ cell }">
511
+ <div class="flex items-center gap-2">
512
+ <AwAvatar :src="cell.avatar" size="sm" />
513
+ <div>
514
+ <div class="font-medium">{{ cell.name }}</div>
515
+ <div class="text-sm text-secondary">{{ cell.email }}</div>
516
+ </div>
517
+ </div>
518
+ </template>
519
+ </AwTableCol>
520
+ ```
521
+
522
+ ## Mobile vs Desktop Table Layouts
523
+
524
+ ### Reordering Columns for Mobile
525
+
526
+ A common pattern is to display important information differently on mobile vs desktop. For example, an employee userpic might be in the 3rd or 4th column on desktop, but should appear in the mobile header for better visual hierarchy.
527
+
528
+ **Pattern: Userpic in Mobile Header**
529
+
530
+ ```markup
531
+ <template>
532
+ <AwPage title="Jobs">
533
+ <AwTableBuilder :collection="jobs">
534
+ <!-- Job title - shown on both desktop and mobile -->
535
+ <AwTableCol field="title" title="Job Title" />
536
+
537
+ <!-- Client info -->
538
+ <AwTableCol field="client_name" title="Client" />
539
+
540
+ <!-- Assigned employee - 3rd column on desktop, hidden on mobile -->
541
+ <AwTableCol title="Assigned To" hide-mobile>
542
+ <template #default="{ cell }">
543
+ <AwUserpic
544
+ :src="cell.employee?.avatar"
545
+ :name="cell.employee?.name"
546
+ :description="cell.employee?.role"
547
+ />
548
+ </template>
549
+ </AwTableCol>
550
+
551
+ <!-- Due date -->
552
+ <AwTableCol title="Due Date">
553
+ <template #default="{ cell }">
554
+ {{ $dayjs(cell.due_date).format('ll') }}
555
+ </template>
556
+ </AwTableCol>
557
+
558
+ <!-- Mobile header: Show userpic prominently -->
559
+ <template #mobile-header-content="{ cell }">
560
+ <AwUserpic
561
+ :src="cell.employee?.avatar"
562
+ :name="cell.employee?.name"
563
+ :description="cell.employee?.role"
564
+ />
565
+ </template>
566
+ </AwTableBuilder>
567
+ </AwPage>
568
+ </template>
569
+ ```
570
+
571
+ **What happens:**
572
+ - **Desktop:** Userpic appears in "Assigned To" column (3rd position)
573
+ - **Mobile:** Row collapses into card layout with userpic in header
574
+ - `hide-mobile` prop hides the desktop column on mobile to avoid duplication
575
+ - `mobile-header-content` slot positions userpic prominently in collapsed row header
576
+
577
+ **Pattern: Main Info in Mobile Header**
578
+
579
+ Sometimes the most important information should be highlighted in the mobile header:
580
+
581
+ ```markup
582
+ <template>
583
+ <AwPage title="Services">
584
+ <AwTableBuilder :collection="services">
585
+ <!-- Service name - desktop only -->
586
+ <AwTableCol field="name" title="Service" hide-mobile />
587
+
588
+ <!-- Price -->
589
+ <AwTableCol title="Price">
590
+ <template #default="{ cell }">
591
+ ${{ (cell.price / 100).toFixed(2) }}
592
+ </template>
593
+ </AwTableCol>
594
+
595
+ <!-- Duration -->
596
+ <AwTableCol field="duration" title="Duration" />
597
+
598
+ <!-- Category -->
599
+ <AwTableCol field="category" title="Category" />
600
+
601
+ <!-- Mobile header: Show service name as headline -->
602
+ <template #mobile-header-content="{ cell }">
603
+ <AwHeadline>{{ cell.name }}</AwHeadline>
604
+ </template>
605
+ </AwTableBuilder>
606
+ </AwPage>
607
+ </template>
608
+ ```
609
+
610
+ **What happens:**
611
+ - **Desktop:** Service name in first column
612
+ - **Mobile:** Service name becomes prominent headline in collapsed row header
613
+ - Other details (price, duration, category) shown below in mobile card layout
614
+ - `hide-mobile` on desktop column prevents duplicate service name
615
+
616
+ **Pattern: Combined User + Main Info**
617
+
618
+ For complex tables, combine userpic with main information in mobile header:
619
+
620
+ ```markup
621
+ <template>
622
+ <AwPage title="Bookings">
623
+ <AwTableBuilder :collection="bookings">
624
+ <!-- Service name - hidden on mobile -->
625
+ <AwTableCol field="service_name" title="Service" hide-mobile />
626
+
627
+ <!-- Date & Time -->
628
+ <AwTableCol title="Date & Time">
629
+ <template #default="{ cell }">
630
+ {{ $dayjs(cell.scheduled_at).format('lll') }}
631
+ </template>
632
+ </AwTableCol>
633
+
634
+ <!-- Client - shown on desktop, hidden on mobile -->
635
+ <AwTableCol title="Client" hide-mobile>
636
+ <template #default="{ cell }">
637
+ <AwUserpic
638
+ :src="cell.client?.avatar"
639
+ :name="cell.client?.name"
640
+ :description="cell.client?.email"
641
+ />
642
+ </template>
643
+ </AwTableCol>
644
+
645
+ <!-- Status -->
646
+ <AwTableCol title="Status">
647
+ <template #default="{ cell }">
648
+ <AwLabel
649
+ :label="cell.status"
650
+ :color="getStatusColor(cell.status)"
651
+ />
652
+ </template>
653
+ </AwTableCol>
654
+
655
+ <!-- Mobile header: Service + Client -->
656
+ <template #mobile-header-content="{ cell }">
657
+ <div class="space-y-2">
658
+ <AwHeadline>{{ cell.service_name }}</AwHeadline>
659
+ <AwUserpic
660
+ :src="cell.client?.avatar"
661
+ :name="cell.client?.name"
662
+ :description="cell.client?.email"
663
+ />
664
+ </div>
665
+ </template>
666
+ </AwTableBuilder>
667
+ </AwPage>
668
+ </template>
669
+
670
+ <script>
671
+ export default {
672
+ methods: {
673
+ getStatusColor(status) {
674
+ const colors = {
675
+ confirmed: 'success',
676
+ pending: 'warning',
677
+ cancelled: 'error',
678
+ completed: 'info'
679
+ }
680
+ return colors[status] || 'mono'
681
+ }
682
+ }
683
+ }
684
+ </script>
685
+ ```
686
+
687
+ ### Best Practices for Mobile Layouts
688
+
689
+ **1. Use `hide-mobile` to Prevent Duplication**
690
+
691
+ ```markup
692
+ <!-- ✅ GOOD - Hide desktop column to avoid showing info twice -->
693
+ <AwTableCol title="Employee" hide-mobile>
694
+ <template #default="{ cell }">
695
+ <AwUserpic :name="cell.employee.name" />
696
+ </template>
697
+ </AwTableCol>
698
+
699
+ <template #mobile-header-content="{ cell }">
700
+ <AwUserpic :name="cell.employee.name" />
701
+ </template>
702
+
703
+ <!-- ❌ BAD - Shows employee info twice on mobile -->
704
+ <AwTableCol title="Employee">
705
+ <template #default="{ cell }">
706
+ <AwUserpic :name="cell.employee.name" />
707
+ </template>
708
+ </AwTableCol>
709
+
710
+ <template #mobile-header-content="{ cell }">
711
+ <AwUserpic :name="cell.employee.name" />
712
+ </template>
713
+ ```
714
+
715
+ **2. Prioritize Visual Hierarchy on Mobile**
716
+
717
+ ```markup
718
+ <!-- ✅ GOOD - Most important info in mobile header -->
719
+ <template #mobile-header-content="{ cell }">
720
+ <AwHeadline>{{ cell.title }}</AwHeadline>
721
+ <AwUserpic :name="cell.owner.name" />
722
+ </template>
723
+
724
+ <!-- ❌ BAD - Generic info that doesn't help user scan -->
725
+ <template #mobile-header-content="{ cell }">
726
+ <span>{{ cell.id }}</span>
727
+ </template>
728
+ ```
729
+
730
+ **3. Use Appropriate Components**
731
+
732
+ - **AwHeadline** - For main titles/names
733
+ - **AwUserpic** - For person/employee information with avatar
734
+ - **AwLabel** - For status badges
735
+ - Keep mobile headers concise and scannable
736
+
737
+ **4. Consider Touch Targets**
738
+
739
+ ```markup
740
+ <!-- Mobile header content should support touch/tap -->
741
+ <AwTableBuilder
742
+ :collection="items"
743
+ @click:row="viewItem"
744
+ >
745
+ <template #mobile-header-content="{ cell }">
746
+ <AwUserpic :name="cell.name" />
747
+ </template>
748
+ </AwTableBuilder>
749
+ ```
750
+
751
+ ## Row Actions
752
+
753
+ ### Click Row (Navigation)
754
+
755
+ ```markup
756
+ <template>
757
+ <AwPage title="Customers">
758
+ <AwTableBuilder
759
+ :collection="customers"
760
+ @click:row="viewCustomer"
761
+ >
762
+ <AwTableCol field="name" title="Name" />
763
+ <AwTableCol field="email" title="Email" />
764
+ </AwTableBuilder>
765
+ </AwPage>
766
+ </template>
767
+
768
+ <script>
769
+ export default {
770
+ methods: {
771
+ viewCustomer(customer) {
772
+ this.$router.push(
773
+ `/${this.$route.params.shop_uuid}/customers/${customer.id}`
774
+ )
775
+ }
776
+ }
777
+ }
778
+ </script>
779
+ ```
780
+
781
+ ### Dropdown Actions
782
+
783
+ ```markup
784
+ <template>
785
+ <AwPage title="Templates">
786
+ <AwTableBuilder :collection="templates">
787
+ <AwTableCol field="name" title="Name" />
788
+ <AwTableCol field="status" title="Status" />
789
+
790
+ <!-- Dropdown slot for row actions -->
791
+ <template #dropdown="{ cell }">
792
+ <AwDropdownButton
793
+ text="View"
794
+ @click="viewItem(cell)"
795
+ />
796
+ <AwDropdownButton
797
+ text="Edit"
798
+ @click="editItem(cell)"
799
+ />
800
+ <AwDropdownButton
801
+ text="Duplicate"
802
+ @click="duplicateItem(cell)"
803
+ />
804
+ <AwDropdownButton
805
+ text="Delete"
806
+ color="error"
807
+ @click="deleteItem(cell)"
808
+ />
809
+ </template>
810
+ </AwTableBuilder>
811
+ </AwPage>
812
+ </template>
813
+
814
+ <script>
815
+ export default {
816
+ methods: {
817
+ viewItem(item) {
818
+ this.$router.push(`/templates/${item.id}`)
819
+ },
820
+
821
+ editItem(item) {
822
+ this.$router.push(`/templates/${item.id}/edit`)
823
+ },
824
+
825
+ duplicateItem(item) {
826
+ // Duplicate logic
827
+ },
828
+
829
+ async deleteItem(item) {
830
+ const confirmed = await this.$confirm({
831
+ title: 'Delete Template',
832
+ message: `Are you sure you want to delete "${item.name}"?`
833
+ })
834
+
835
+ if (!confirmed) return
836
+
837
+ try {
838
+ await this.$axios.delete(`/api/templates/${item.id}`)
839
+ this.$notify({
840
+ title: 'Template deleted successfully',
841
+ type: 'success'
842
+ })
843
+ this.templates.fetch() // Refetch list
844
+ } catch (error) {
845
+ this.$notify({
846
+ title: 'Failed to delete template',
847
+ type: 'error'
848
+ })
849
+ }
850
+ }
851
+ }
852
+ }
853
+ </script>
854
+ ```
855
+
856
+ **⚠️ Important: Can't use both @click:row and #dropdown**
857
+
858
+ ```markup
859
+ <!-- ❌ BAD - Conflict! Row click and dropdown both trigger -->
860
+ <AwTableBuilder
861
+ :collection="items"
862
+ @click:row="viewItem"
863
+ >
864
+ <template #dropdown="{ cell }">
865
+ <AwDropdownButton text="Edit" />
866
+ </template>
867
+ </AwTableBuilder>
868
+
869
+ <!-- ✅ GOOD - Choose one approach -->
870
+ <!-- Option 1: Row click only -->
871
+ <AwTableBuilder
872
+ :collection="items"
873
+ @click:row="viewItem"
874
+ >
875
+ <!-- No dropdown -->
876
+ </AwTableBuilder>
877
+
878
+ <!-- Option 2: Dropdown only -->
879
+ <AwTableBuilder :collection="items">
880
+ <template #dropdown="{ cell }">
881
+ <AwDropdownButton text="View" @click="viewItem(cell)" />
882
+ <AwDropdownButton text="Edit" @click="editItem(cell)" />
883
+ </template>
884
+ </AwTableBuilder>
885
+ ```
886
+
887
+ **Dropdown Action Ordering:**
888
+ 1. View (if separate from row click)
889
+ 2. Edit
890
+ 3. Other actions (Duplicate, Archive, etc.)
891
+ 4. Delete (last, in error color)
892
+
893
+ ## Mobile Breadcrumbs
894
+
895
+ For pages in subfolders, add breadcrumbs for mobile navigation:
896
+
897
+ ```markup
898
+ <template>
899
+ <AwPage title="Email Templates">
900
+ <!-- Mobile breadcrumb -->
901
+ <template #mobile-breadcrumbs>
902
+ <AwButton
903
+ :href="`/${$route.params.shop_uuid}/notifications`"
904
+ theme="text"
905
+ icon="arrow-left"
906
+ text="Notifications"
907
+ />
908
+ </template>
909
+
910
+ <!-- Page content -->
911
+ </AwPage>
912
+ </template>
913
+ ```
914
+
915
+ ## Empty States
916
+
917
+ Use the `cta-button` prop on `AwEmptyContainer` to add a create button in the empty state. The button should use the same `href` as the header button:
918
+
919
+ ```markup
920
+ <template>
921
+ <AwPage title="Customers">
922
+ <template #buttons>
923
+ <AwPageMenuButtons
924
+ :items="[{
925
+ text: 'Create Customer',
926
+ icon: 'awesio/plus',
927
+ cta: true,
928
+ href: '/customers/create'
929
+ }]"
930
+ />
931
+ </template>
932
+
933
+ <AwTableBuilder :collection="customers">
934
+ <AwTableCol field="name" title="Name" />
935
+
936
+ <!-- Empty state with create button -->
937
+ <template #empty-container>
938
+ <AwEmptyContainer
939
+ icon="users"
940
+ title="No customers yet"
941
+ description="Create your first customer to get started"
942
+ :cta-button="{
943
+ text: 'Create Customer',
944
+ icon: 'awesio/plus',
945
+ href: '/customers/create'
946
+ }"
947
+ />
948
+ </template>
949
+ </AwTableBuilder>
950
+ </AwPage>
951
+ </template>
952
+ ```
953
+
954
+ **Best Practice:** Always use the same create button in both the header (`AwPageMenuButtons`) and empty state (`AwEmptyContainer` cta-button) with the same `href` for consistency.
955
+
956
+ ## Complete Example
957
+
958
+ Full-featured list page with all patterns:
959
+
960
+ ```markup
961
+ <template>
962
+ <AwPage title="Email Templates">
963
+ <!-- Mobile breadcrumbs -->
964
+ <template #mobile-breadcrumbs>
965
+ <AwButton
966
+ :href="`/${$route.params.shop_uuid}/notifications`"
967
+ theme="text"
968
+ icon="arrow-left"
969
+ text="Back to Notifications"
970
+ />
971
+ </template>
972
+
973
+ <!-- Header action buttons -->
974
+ <template #buttons>
975
+ <AwPageMenuButtons
976
+ :items="[
977
+ {
978
+ text: 'Import',
979
+ icon: 'awesio/upload',
980
+ color: 'mono',
981
+ listeners: { click: importTemplates }
982
+ },
983
+ {
984
+ text: 'Create Template',
985
+ icon: 'awesio/plus',
986
+ cta: true,
987
+ href: `/${shopUuid}/templates/create`
988
+ }
989
+ ]"
990
+ />
991
+ </template>
992
+
993
+ <!-- Search and filters -->
994
+ <div class="flex flex-col lg:flex-row gap-4 mb-6">
995
+ <AwSearch class="flex-1" />
996
+ <AwSelect
997
+ v-model="channelFilter"
998
+ :options="['all', 'email', 'sms', 'push']"
999
+ label="Channel"
1000
+ class="w-full lg:w-48"
1001
+ />
1002
+ </div>
1003
+
1004
+ <!-- Table with data -->
1005
+ <AwTableBuilder
1006
+ :collection="templates"
1007
+ :watch-params="['search']"
1008
+ :options="{ shop_uuid: shopUuid, channel: channelFilter }"
1009
+ >
1010
+ <!-- Simple field columns -->
1011
+ <AwTableCol field="name" title="Template Name" />
1012
+
1013
+ <!-- Custom status column -->
1014
+ <AwTableCol title="Status">
1015
+ <template #default="{ cell }">
1016
+ <AwLabel
1017
+ :label="cell.is_active ? 'Active' : 'Inactive'"
1018
+ :color="cell.is_active ? 'success' : 'mono'"
1019
+ />
1020
+ </template>
1021
+ </AwTableCol>
1022
+
1023
+ <!-- Channel badge -->
1024
+ <AwTableCol title="Channel">
1025
+ <template #default="{ cell }">
1026
+ <AwBadge :text="cell.channel" />
1027
+ </template>
1028
+ </AwTableCol>
1029
+
1030
+ <!-- Date column -->
1031
+ <AwTableCol title="Last Modified">
1032
+ <template #default="{ cell }">
1033
+ {{ $dayjs(cell.updated_at).format('ll') }}
1034
+ </template>
1035
+ </AwTableCol>
1036
+
1037
+ <!-- Row actions dropdown -->
1038
+ <template #dropdown="{ cell }">
1039
+ <AwDropdownButton
1040
+ text="Edit"
1041
+ @click="editTemplate(cell)"
1042
+ />
1043
+ <AwDropdownButton
1044
+ text="Duplicate"
1045
+ @click="duplicateTemplate(cell)"
1046
+ />
1047
+ <AwDropdownButton
1048
+ :text="cell.is_active ? 'Deactivate' : 'Activate'"
1049
+ @click="toggleActive(cell)"
1050
+ />
1051
+ <AwDropdownButton
1052
+ text="Delete"
1053
+ color="error"
1054
+ @click="deleteTemplate(cell)"
1055
+ />
1056
+ </template>
1057
+
1058
+ <!-- Empty state -->
1059
+ <template #empty-container>
1060
+ <AwEmptyContainer
1061
+ icon="awesio/mail"
1062
+ title="No templates yet"
1063
+ description="Create your first email template"
1064
+ :cta-button="{
1065
+ text: 'Create Template',
1066
+ icon: 'awesio/plus',
1067
+ href: `/${shopUuid}/templates/create`
1068
+ }"
1069
+ />
1070
+ </template>
1071
+ </AwTableBuilder>
1072
+ </AwPage>
1073
+ </template>
1074
+
1075
+ <script>
1076
+ import EmailTemplates from '~/collections/EmailTemplates'
1077
+
1078
+ export default {
1079
+ middleware: 'auth',
1080
+
1081
+ data() {
1082
+ return {
1083
+ channelFilter: 'all',
1084
+ templates: new EmailTemplates([], {
1085
+ shop_uuid: this.$route.params.shop_uuid
1086
+ })
1087
+ }
1088
+ },
1089
+
1090
+ computed: {
1091
+ shopUuid() {
1092
+ return this.$route.params.shop_uuid
1093
+ }
1094
+ },
1095
+
1096
+ methods: {
1097
+ editTemplate(template) {
1098
+ this.$router.push({
1099
+ name: 'templates-edit',
1100
+ params: {
1101
+ shop_uuid: this.shopUuid,
1102
+ id: template.id
1103
+ }
1104
+ })
1105
+ },
1106
+
1107
+ async duplicateTemplate(template) {
1108
+ try {
1109
+ await this.$axios.post(`/api/templates/${template.id}/duplicate`)
1110
+ this.$notify({
1111
+ title: 'Template duplicated successfully',
1112
+ type: 'success'
1113
+ })
1114
+ this.templates.fetch()
1115
+ } catch (error) {
1116
+ this.$notify({
1117
+ title: 'Failed to duplicate template',
1118
+ type: 'error'
1119
+ })
1120
+ }
1121
+ },
1122
+
1123
+ async toggleActive(template) {
1124
+ try {
1125
+ await this.$axios.patch(`/api/templates/${template.id}`, {
1126
+ is_active: !template.is_active
1127
+ })
1128
+ this.$notify({
1129
+ title: `Template ${template.is_active ? 'deactivated' : 'activated'}`,
1130
+ type: 'success'
1131
+ })
1132
+ this.templates.fetch()
1133
+ } catch (error) {
1134
+ this.$notify({
1135
+ title: 'Failed to update template',
1136
+ type: 'error'
1137
+ })
1138
+ }
1139
+ },
1140
+
1141
+ async deleteTemplate(template) {
1142
+ const confirmed = await this.$confirm({
1143
+ title: 'Delete Template',
1144
+ message: `Are you sure you want to delete "${template.name}"?`
1145
+ })
1146
+
1147
+ if (!confirmed) return
1148
+
1149
+ try {
1150
+ await this.$axios.delete(`/api/templates/${template.id}`)
1151
+ this.$notify({
1152
+ title: 'Template deleted successfully',
1153
+ type: 'success'
1154
+ })
1155
+ this.templates.fetch()
1156
+ } catch (error) {
1157
+ this.$notify({
1158
+ title: 'Failed to delete template',
1159
+ type: 'error'
1160
+ })
1161
+ }
1162
+ },
1163
+
1164
+ importTemplates() {
1165
+ // Import logic
1166
+ }
1167
+ }
1168
+ }
1169
+ </script>
1170
+ ```
1171
+
1172
+ ## Best Practices
1173
+
1174
+ ### 1. Collection Initialization
1175
+
1176
+ ✅ **Always include shop_uuid in options:**
1177
+ ```javascript
1178
+ customers: new Customers([], {
1179
+ shop_uuid: this.$route.params.shop_uuid
1180
+ })
1181
+ ```
1182
+
1183
+ ### 2. No Manual Fetching
1184
+
1185
+ ✅ **Let AwTableBuilder handle fetching:**
1186
+ ```markup
1187
+ <!-- ✅ GOOD -->
1188
+ <AwTableBuilder :collection="customers" />
1189
+
1190
+ <!-- ❌ BAD - Don't fetch manually -->
1191
+ <script>
1192
+ async mounted() {
1193
+ await this.customers.fetch() // Unnecessary!
1194
+ }
1195
+ </script>
1196
+ ```
1197
+
1198
+ ### 3. Loading States
1199
+
1200
+ ✅ **Don't wrap in v-if:**
1201
+ ```markup
1202
+ <!-- ✅ GOOD - Component handles loading -->
1203
+ <AwTableBuilder :collection="customers" />
1204
+
1205
+ <!-- ❌ BAD - Prevents mounting -->
1206
+ <AwTableBuilder v-if="!loading" :collection="customers" />
1207
+ ```
1208
+
1209
+ ### 4. Date Formatting
1210
+
1211
+ ✅ **Always use $dayjs:**
1212
+ ```markup
1213
+ <!-- ✅ GOOD -->
1214
+ {{ $dayjs(cell.created_at).format('ll') }}
1215
+
1216
+ <!-- ❌ BAD - Inconsistent formatting -->
1217
+ {{ new Date(cell.created_at).toLocaleDateString() }}
1218
+ ```
1219
+
1220
+ ### 5. Confirmations for Destructive Actions
1221
+
1222
+ ✅ **Always confirm deletes:**
1223
+ ```javascript
1224
+ async deleteItem(item) {
1225
+ const confirmed = await this.$confirm({
1226
+ title: 'Delete Item',
1227
+ message: `Are you sure you want to delete "${item.name}"?`
1228
+ })
1229
+
1230
+ if (!confirmed) return
1231
+
1232
+ // Proceed with deletion
1233
+ }
1234
+ ```
1235
+
1236
+ ## See Also
1237
+
1238
+ - [Detail Pages](./detail-pages.md) - Create and edit pages
1239
+ - [Dashboard Pages](./dashboard-pages.md) - Metrics and overview pages
1240
+ - [AwTableBuilder](../../components/organisms/aw-table-builder.md) - Table component reference
1241
+ - [AwPage](../../components/pages/aw-page.md) - Page component reference
1242
+ - [Data Fetching Guide](../data-fetching.md) - Working with collections