diff --git a/src/api/customer.ts b/src/api/customer.ts index 5fa9ef4..950c740 100644 --- a/src/api/customer.ts +++ b/src/api/customer.ts @@ -14,6 +14,16 @@ export const updateCustomer = async (id: number, obj: Customer) => export const getCustomerTable = async (params: TableInput) => await useAxios.post>('/admin/customer/table', params) +export const getOnlineCustomers = async () => + await useAxios.get<{ + count: number + online_within_seconds: number + rows: Customer[] + }>('/admin/customer/online') + +export const getCustomer = async (id: number) => + await useAxios.get(`/admin/customer/${id}`) + export const getCustomerAssetsSummary = async (customerId: number) => await useAxios.get(`/admin/customer/${customerId}/assets`) diff --git a/src/assets/style.less b/src/assets/style.less index c9459a6..d356d63 100644 --- a/src/assets/style.less +++ b/src/assets/style.less @@ -10,9 +10,25 @@ opacity: 0; } -.ant-tabs-rtl .ant-tabs-nav .ant-tabs-tab{ - margin: 10px !important; +/* + Do NOT set equal margin on .ant-tabs-tab in RTL — it desyncs the ink-bar + from the active tab (Ant measures tab boxes; asymmetric/all-side margins skew it). + Use gap on the nav-list instead for consistent spacing everywhere. +*/ +.ant-tabs > .ant-tabs-nav .ant-tabs-nav-list, +.ant-tabs > div > .ant-tabs-nav .ant-tabs-nav-list { + gap: 0.75rem; } + +.ant-tabs-rtl .ant-tabs-nav .ant-tabs-tab, +.ant-tabs .ant-tabs-nav .ant-tabs-tab { + margin: 0 !important; +} + +.ant-tabs .ant-tabs-nav .ant-tabs-tab { + padding: 0.5rem 0.75rem; +} + /*::-webkit-scrollbar-track { background-color: #f2f6ff !important; } diff --git a/src/core/components/BaseTable/src/BaseTable.vue b/src/core/components/BaseTable/src/BaseTable.vue index 5a0a64f..573274e 100644 --- a/src/core/components/BaseTable/src/BaseTable.vue +++ b/src/core/components/BaseTable/src/BaseTable.vue @@ -86,6 +86,7 @@ :columns="_columns" :data-source="data" v-bind="_props" + :scroll="tableScroll" :pagination="false" @resizeColumn="handleResizeColumn" > @@ -289,24 +290,9 @@ import { availableFilterType, MapFilterOpToFaString } from '@core/components/BaseTable/hooks/filterOperator' -//import { mapColumnTypeFilterToFilterType } from '@core/type/baseTable' -//import { useElementSize } from '@vueuse/core' -//import { ContentLoader } from 'vue-content-loader' -//skeleton -//const skeletonWidth = ref(null) -/*const skeletonWidthSize = reactive( - useElementSize(skeletonWidth, { width: 0, height: 0 }, { box: 'border-box' }) -)*/ -//const skeletonHeight = 357 -/*const skeletonHeight = ref(null) -const skeletonHeightSize = reactive( - useElementSize(skeletonHeight, { width: 0, height: 0 }, { box: 'border-box' }) -)*/ -/* -watch(skeletonHeightSize, (v) => { - console.log('ttt', v) -}) -*/ + +/** Wide enough for dense action icon rows (e.g. customers); too-narrow fixed cells bleed into neighbors. */ +const DEFAULT_ACTION_WIDTH = 280 interface Props { bordered?: boolean @@ -328,7 +314,7 @@ const props = withDefaults(defineProps(), { driver: 'laravel-sql', mode: 'pro', columns: () => [], - scroll: () => ({ x: true }), //y: '100%' //y: 600 + scroll: () => ({ x: true }), pageSize: () => [5, 10, 20, 50, 100], defaultPageLimit: 10, showPaginate: true, @@ -345,13 +331,30 @@ const filters = ref([]) const order = ref<'asc' | 'desc'>('asc') const orderColumnKey = ref(null) const containerRef = ref() -//const route = useRoute() const loading = ref(false) const tableReady = ref(false) let callApiPromise: Promise | null = null const _props = computed(() => { - return omit(props, ['columns', 'pagination', 'dataSource']) + return omit(props, ['columns', 'pagination', 'dataSource', 'scroll']) }) + +const tableScroll = computed(() => ({ + x: props.scroll?.x ?? true, + ...(props.scroll?.y != null ? { y: props.scroll.y } : {}) +})) + +function isActionColumn(column: BaseTableColumn) { + const rawIndex = column.dataIndex + const dataIndex = Array.isArray(rawIndex) ? rawIndex.join('.') : String(rawIndex ?? '') + const key = column.key != null ? String(column.key) : '' + return ( + column.slot === 'action' || + key === 'action' || + key === 'row_action' || + dataIndex === 'action' || + dataIndex === 'row_action' + ) +} const emit = defineEmits<{ (e: 'register', data: BaseTableEmit): void }>() @@ -469,15 +472,17 @@ const _columns = computed(() => { if (!Object.hasOwn(column, 'align')) { column.align = 'center' } + if (isActionColumn(column)) { + if (!Object.hasOwn(column, 'fixed') || column.fixed == null) { + column.fixed = 'right' + } + const w = Number(column.width) + if (!Object.hasOwn(column, 'width') || column.width == null || !Number.isFinite(w) || w < 200) { + column.width = DEFAULT_ACTION_WIDTH + } + } //@ts-ignore - column.dataIndex = column.dataIndex.split('.') - //if(isArray(column.dataIndex)) - /*if(!isArray(column.dataIndex)){ - }*/ - //column.dataIndex = column.dataIndex.split('.') - /*if(){ - column.dataIndex= - }*/ + column.dataIndex = String(column.dataIndex ?? '').split('.') }) return cols }) @@ -699,6 +704,19 @@ defineExpose({ overflow: auto; } + /* Fixed ops column must fully cover neighbors (RTL left / fixed-right) */ + .ant-table-cell-fix-right, + .ant-table-cell-fix-right.ant-table-cell-fix-last-right { + background: var(--admin-bg-elevated, var(--admin-bg-surface, #141414)) !important; + } + + html.is-dark & { + .ant-table-cell-fix-right, + .ant-table-cell-fix-right.ant-table-cell-fix-last-right { + background: var(--admin-bg-elevated, #1f1f1f) !important; + } + } + .table-footer { flex-shrink: 0; } diff --git a/src/core/components/BaseTableOld/src/BaseTable.vue b/src/core/components/BaseTableOld/src/BaseTable.vue index df725ad..aa41e9a 100644 --- a/src/core/components/BaseTableOld/src/BaseTable.vue +++ b/src/core/components/BaseTableOld/src/BaseTable.vue @@ -7,6 +7,7 @@
+
@@ -84,10 +85,13 @@
+
+
@@ -100,9 +104,10 @@ +
-
+