diff --git a/apps/admin-panel/src/components/x-table/XTable.vue b/apps/admin-panel/src/components/x-table/XTable.vue index 1554ff1..4462285 100644 --- a/apps/admin-panel/src/components/x-table/XTable.vue +++ b/apps/admin-panel/src/components/x-table/XTable.vue @@ -303,9 +303,13 @@ const filterableOptions = computed(() => ); /** legacy `order` computed: '+field' / '-field' */ -const order = computed(() => - sort.value ? `${sortDirection.value === 'asc' ? '+' : '-'}${sort.value}` : undefined, -); +const order = computed(() => { + if (!sort.value) return undefined; + // A column may be keyed by the reference's name for it; its filter key, when it + // has one, is the real field it stands for, so that is what is sorted on. + const col = cols.value.find((c) => c.key === sort.value); + return `${sortDirection.value === 'asc' ? '+' : '-'}${col?.__fkey ?? sort.value}`; +}); const antColumns = computed(() => visibleCols.value.map((c) => ({ diff --git a/apps/admin-panel/src/utils/select.ts b/apps/admin-panel/src/utils/select.ts new file mode 100644 index 0000000..de82adf --- /dev/null +++ b/apps/admin-panel/src/utils/select.ts @@ -0,0 +1,14 @@ +/** + * The text of a slot-style ``, for a custom `filter-option`. + * + * ant-design-vue 4 hands `filterOption` the option's default slot as a + * function, not as an array of vnodes (the shape the legacy filters indexed + * into), so `option.children[0].children` is always undefined and a typed + * search matched nothing. This calls the slot first. + */ +export function optionText(option: any): string { + const children = typeof option?.children === 'function' ? option.children() : option?.children; + const first = Array.isArray(children) ? children[0] : children; + if (typeof first === 'string') return first; + return String(first?.children ?? ''); +} diff --git a/apps/admin-panel/src/views/apps/category/Add.vue b/apps/admin-panel/src/views/apps/category/Add.vue index c0ad617..9acf85a 100644 --- a/apps/admin-panel/src/views/apps/category/Add.vue +++ b/apps/admin-panel/src/views/apps/category/Add.vue @@ -136,6 +136,7 @@ @@ -392,6 +520,80 @@ onMounted(load) } /* Secondary now that "enter the account" is the primary action here. */ +.cch__actions { + display: flex; + gap: 10px; + margin-top: 10px; +} + +.cch__act { + flex: 1; + padding: 12px; + border: 1px solid @wine; + border-radius: 14px; + background: transparent; + color: @wine; + font-family: inherit; + font-size: 13.5px; + font-weight: 700; + cursor: pointer; + + &.is-danger { + border-color: @red; + color: @red; + } + + &:disabled { + opacity: 0.5; + cursor: default; + } +} + +.cch__confirm { + display: flex; + align-items: center; + gap: 10px; + margin: 12px 0 0; + font-size: 13px; + color: @inkSoft; +} + +.cch__link { + border: none; + background: transparent; + color: @inkFaint; + font-family: inherit; + font-size: 12.5px; + cursor: pointer; +} + +.cch__field { + display: flex; + flex-direction: column; + gap: 5px; + padding: 8px 0; + font-size: 12.5px; + color: @inkSoft; +} + +.cch__field input, +.cch__field textarea { + width: 100%; + padding: 10px 12px; + border: 1px solid @hairline; + border-radius: 12px; + background: transparent; + color: @ink; + font-family: inherit; + font-size: 14px; +} + +.cch__error { + margin: 8px 0 0; + font-size: 12.5px; + color: @red; +} + .cch__close { margin-top: 10px; width: 100%; diff --git a/apps/user-app/src/views/user/DashboardView.vue b/apps/user-app/src/views/user/DashboardView.vue index 5a85dcc..0c34139 100644 --- a/apps/user-app/src/views/user/DashboardView.vue +++ b/apps/user-app/src/views/user/DashboardView.vue @@ -198,6 +198,14 @@
بیخیال
+ + + +
+ + +
+
@@ -205,6 +213,7 @@ import { brand } from '../../brand' import { computed, onActivated, onMounted, reactive, ref } from 'vue' import { useRouter } from 'vue-router' +import { Drawer } from 'ant-design-vue' import AppPullRefresh from '../../components/ui/AppPullRefresh.vue' import Icon from '../../components/icon/Icon.vue' import SectionHead from '../../components/home/SectionHead.vue' @@ -257,6 +266,8 @@ const BASE_TILES = [ // gated on hasMCode() (Dashboard.vue:179, :449). The target has no mcode, so // every signed-in customer sees it and the server scopes it to their token. { id: 6, icon: 'vuesax-linear:chart-2', title: 'گزارش', tone: 'slate', disabled: false }, + // «سفارش ساخت» (reference makeOrder): ask for a piece to be made. + { id: 8, icon: 'vuesax-linear:3d-cube-scan', title: 'سفارش ساخت', tone: 'rose', disabled: false }, ] /** @@ -297,6 +308,15 @@ function goToDetails(id: number) { if (id === 4) router.push({ name: 'ChildOrder' }) if (id === 5) router.push({ name: 'customerChoice' }) if (id === 6) router.push({ name: 'report' }) + if (id === 8) makeOrderChoice.value = true +} + +// «سفارش ساخت»: the reference opened a two-button dialog — place a request, or +// see the ones already placed. +const makeOrderChoice = ref(false) +function chooseMakeOrder(name: 'makeOrderList' | 'makeOrderStatus') { + makeOrderChoice.value = false + router.push({ name }) } async function logOut() { @@ -756,4 +776,28 @@ onMounted(async () => { color: @inkFaint; opacity: 0.7; } + +.dash__choice { + display: flex; + flex-direction: column; + gap: 10px; + padding: 20px 16px 24px; +} + +.dash__choiceBtn { + padding: 15px; + border: 1px solid @wine; + border-radius: 14px; + background: @wine; + color: #fff; + font-family: inherit; + font-size: 15px; + font-weight: 700; + cursor: pointer; +} + +.dash__choiceBtn.is-quiet { + background: transparent; + color: @wine; +} diff --git a/apps/user-app/src/views/user/MakeOrderStatusView.vue b/apps/user-app/src/views/user/MakeOrderStatusView.vue new file mode 100644 index 0000000..dcd63da --- /dev/null +++ b/apps/user-app/src/views/user/MakeOrderStatusView.vue @@ -0,0 +1,237 @@ + + + + + + diff --git a/apps/user-app/src/views/user/MakeOrderView.vue b/apps/user-app/src/views/user/MakeOrderView.vue new file mode 100644 index 0000000..1c546f3 --- /dev/null +++ b/apps/user-app/src/views/user/MakeOrderView.vue @@ -0,0 +1,197 @@ + +