Skip to content

Commit 8a390ad

Browse files
committed
refactor: pagination
BREAKING CHANGE: orderBy expects tOrderBy[]
1 parent 53f6a15 commit 8a390ad

File tree

6 files changed

+102
-56
lines changed

6 files changed

+102
-56
lines changed

packages/common-types/src/fetch.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
export type tOrderBy = [string, ("desc" | "asc")?];
1+
export type tOrder = "desc" | "asc";
2+
3+
/**
4+
* Order using column or property
5+
*
6+
* @example [["id", "asc" ]], order ascendente de id
7+
* @example [["id", "desc" ]] or [["id"]], order descendente de id
8+
*/
9+
export type tOrderBy = [string, tOrder?];
210

311
export interface iPageEdge<T, C extends string | number = string> {
412
cursor: C;
@@ -19,7 +27,7 @@ export interface iPage<T, C extends string | number = string> {
1927
totalCount: number;
2028
}
2129

22-
export interface iPagination<O = tOrderBy> {
30+
export interface iPagination {
2331
/**
2432
* Identificador del elemento donde empieza la paginacion
2533
*
@@ -30,12 +38,7 @@ export interface iPagination<O = tOrderBy> {
3038
* Cantidad de elementos (limite)
3139
*/
3240
first?: number;
33-
/**
34-
* Ordenar segun columna o propiedad
35-
* ej: ["id", "asc" ], order ascendente de id
36-
* ej: ["id", "desc" ], order descendente de id
37-
*/
38-
orderBy?: O;
41+
orderBy?: tOrderBy[];
3942
}
4043

4144
/**

packages/components-vue/src/components/loader/ContentFetch.vue

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,19 @@
33
<LoaderContent
44
v-bind="{
55
...$attrs,
6-
content: !!content,
6+
content: !!content && patchedIsContent(content),
77
errors: !!errors,
88
loading: loading,
99
refresh,
1010
unwrap,
1111
theme,
12+
noContentMessage,
1213
label,
1314
noLoader,
1415
}"
1516
>
1617
<slot
17-
v-if="content && (!loading || firstLoad)"
18+
v-if="!!content && patchedIsContent(content) && (!loading || firstLoad)"
1819
v-bind="{ content, refresh, loading, errors }"
1920
></slot>
2021
</LoaderContent>
@@ -40,6 +41,7 @@
4041
import type { iUseThemeProps } from "../../types/props";
4142
4243
interface iLoaderContentFetchProps<Ti, Pi extends any[]> extends iUseThemeProps {
44+
noContentMessage?: string;
4345
/**
4446
* Loader label
4547
*/
@@ -59,6 +61,10 @@
5961
*/
6062
el?: VueComponent | FunctionalComponent | DefineComponent | string;
6163
preventAutoload?: boolean;
64+
/**
65+
* Additional content validation before rendering fetched data
66+
*/
67+
isContent?: (c?: any) => boolean;
6268
}
6369
6470
/**
@@ -96,6 +102,10 @@
96102
};
97103
const content = computed(() => fetchedContent.value ?? props.fallback);
98104
105+
function patchedIsContent(c?: T): boolean {
106+
return props.isContent?.(c) ?? !!c;
107+
}
108+
99109
async function refresh() {
100110
try {
101111
if (props.promise || props.hydratablePromise || props.url) {

packages/components-vue/src/components/pagination/Content.vue

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
v-slot="{ content, refresh }"
44
:promise="patchedPromise"
55
:payload="[{ ...pagination, ...defaults }]"
6-
v-bind="{ ...$attrs, preventAutoload, theme, label }"
6+
v-bind="{ ...$attrs, preventAutoload, theme, noContentMessage, label, isContent }"
77
>
88
<slot
99
v-bind="{
@@ -29,13 +29,13 @@
2929
iPage,
3030
iPagination,
3131
iPluginOptions,
32-
tOrderBy,
3332
} from "@open-xamu-co/ui-common-types";
3433
3534
import LoaderContentFetch from "../loader/ContentFetch.vue";
3635
import PaginationSimple from "./Simple.vue";
3736
3837
import type { iUseThemeProps } from "../../types/props";
38+
import { useOrderBy } from "../../composables/utils";
3939
4040
export interface iPCBaseProps extends iPagination, iUseThemeProps {
4141
/**
@@ -44,6 +44,9 @@
4444
page: any;
4545
/**
4646
* paginate using route
47+
*
48+
* @example "?orderBy=id:asc" single order property
49+
* @example "?orderBy=id:asc&orderBy=createdAt" multiple order properties
4750
*/
4851
withRoute?: boolean;
4952
/**
@@ -55,6 +58,7 @@
5558
* Additional parameters to send every request
5659
*/
5760
defaults?: Record<string, any>;
61+
noContentMessage?: string;
5862
/**
5963
* Loader label
6064
*/
@@ -108,27 +112,24 @@
108112
const routePagination = computed<iPagination>(() => {
109113
if (!router) return {};
110114
115+
const { first, at } = propsPagination.value;
116+
111117
const route = router.currentRoute.value;
112-
const newPagination = { ...propsPagination.value };
113-
/**
114-
* TODO: support multiple order params
115-
* @example { price: "asc", createdAt: "desc", }
116-
*/
117-
const [cursorName, order] = Array.isArray(route.query.orderBy) ? route.query.orderBy : [];
118-
const ascOrDesc = order === "asc" || order === "desc" ? order : "desc";
119-
const orderBy: tOrderBy = [cursorName ?? "createdAt", ascOrDesc];
120-
const first = route.query.first;
121-
const at = route.query.at;
118+
const newPagination: iPagination = { first, at };
119+
const routeFirst = route.query.first;
120+
121+
if (routeFirst && !Array.isArray(routeFirst)) newPagination.first = Number(first);
122122
123-
newPagination.orderBy = orderBy;
123+
const routeAt = route.query.at;
124124
125-
if (first && !Array.isArray(first)) newPagination.first = Number(first);
126-
if (at && !Array.isArray(at)) {
125+
if (routeAt && !Array.isArray(at)) {
127126
const newAt = Number(at);
128127
129-
newPagination.at = isNaN(newAt) ? at : newAt;
128+
newPagination.at = isNaN(newAt) ? routeAt : newAt;
130129
}
131130
131+
newPagination.orderBy = useOrderBy(route.query.orderBy);
132+
132133
return newPagination;
133134
});
134135
const pagination = computed<iPagination>({
@@ -145,4 +146,8 @@
145146
};
146147
},
147148
});
149+
150+
function isContent(c?: iPage<T, C>): boolean {
151+
return !!c?.edges.length;
152+
}
148153
</script>

packages/components-vue/src/components/pagination/Simple.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
<ul class="flx --flxRow-wrap --flx-center --gap-5 --gap:sm">
1515
<li>
1616
<SelectSimple
17-
id="order"
17+
id="first"
1818
v-model="firstModel"
1919
:theme="theme"
2020
class="--maxWidthVw-60"
21-
name="order"
21+
name="first"
2222
:options="[5, 10, 25, 50, 100]"
2323
/>
2424
</li>

packages/components-vue/src/components/table/Simple.vue

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
<!-- TODO: define filters, filter table contents -->
4545
<th
4646
class="--sticky"
47-
:class="{ ['is--selected']: canSort && isOrdering('id') }"
47+
:class="{ ['is--selected']: canSort && !!ordering['id'] }"
4848
data-column-name="id"
4949
data-column="id"
5050
>
@@ -70,7 +70,7 @@
7070
@click="setOrdering('id')"
7171
>
7272
<span>#</span>
73-
<template v-if="isOrdering('id')">
73+
<template v-if="!!ordering['id']">
7474
<IconFa v-if="ordering.asc" name="arrow-down" />
7575
<IconFa v-if="!ordering.asc" name="arrow-up" />
7676
</template>
@@ -83,7 +83,7 @@
8383
class="--maxWidth-440"
8484
:class="[
8585
`--txtSize-${size}`,
86-
{ ['is--selected']: canSort && isOrdering(propertyName.value) },
86+
{ ['is--selected']: canSort && !!ordering[propertyName.value] },
8787
]"
8888
:data-column-name="propertyName.value"
8989
:data-column="propertyName.alias"
@@ -93,7 +93,7 @@
9393
: 'auto'
9494
"
9595
>
96-
<span v-if="!canSort" :title="String(propertyName.value)">
96+
<span v-if="!canSort" :title="propertyName.value">
9797
{{ propertyName.alias }}
9898
</span>
9999
<ActionLink
@@ -107,7 +107,7 @@
107107
@click="setOrdering(propertyName.value)"
108108
>
109109
<span>{{ propertyName.alias }}</span>
110-
<template v-if="isOrdering(propertyName.value)">
110+
<template v-if="!!ordering[propertyName.value]">
111111
<IconFa v-if="ordering.asc" name="arrow-down" />
112112
<IconFa v-else name="arrow-up" />
113113
</template>
@@ -136,7 +136,7 @@
136136
>
137137
<th
138138
class="--sticky"
139-
:class="{ ['is--selected']: isOrdering('id') }"
139+
:class="{ ['is--selected']: !!ordering['id'] }"
140140
data-column-name="id"
141141
data-column="id"
142142
>
@@ -343,6 +343,7 @@
343343
iNodeFn,
344344
iProperty,
345345
iSelectOption,
346+
tOrder,
346347
tProps,
347348
tSizeModifier,
348349
} from "@open-xamu-co/ui-common-types";
@@ -361,7 +362,7 @@
361362
362363
import type { iModalProps, iUseThemeProps } from "../../types/props";
363364
import useTheme from "../../composables/theme";
364-
import { useHelpers } from "../../composables/utils";
365+
import { useHelpers, useOrderBy } from "../../composables/utils";
365366
import useUUID from "../../composables/uuid";
366367
367368
export interface iTableProps<Ti extends Record<string, any>> extends iUseThemeProps {
@@ -463,22 +464,23 @@
463464
/**
464465
* ordering property
465466
*
466-
* TODO: order using props or model instead (external pagination)
467+
* TODO: require & use order getter fn instead
467468
*/
468469
const ordering = computed(() => {
469-
const orderBy = { name: "id", asc: true };
470+
let orderBy: Record<string, tOrder> = { id: "desc" };
470471
471472
if (router) {
472473
const route = router.currentRoute.value;
473474
474-
if (!route.query.orderBy) return orderBy;
475+
const routeOrderBy = useOrderBy(route.query.orderBy);
475476
476-
const properties = String(route.query.orderBy).split(",");
477-
const property = properties[0]?.split(":");
477+
if (!routeOrderBy.length) return orderBy;
478478
479-
orderBy.name = property[0];
479+
orderBy = routeOrderBy.reduce<Record<string, tOrder>>((acc, [key, value]) => {
480+
acc[key] = value || "desc";
480481
481-
if (String(property[1]).toUpperCase() === "DESC") orderBy.asc = false;
482+
return acc;
483+
}, {});
482484
}
483485
484486
return orderBy;
@@ -490,10 +492,15 @@
490492
(!props.updateNode && !props.cloneNode && !props.deleteNode)
491493
);
492494
});
495+
496+
interface iPropertyMeta extends iSelectOption {
497+
value: string;
498+
}
499+
493500
/**
494501
* This one assumes all objects within nodes are all the same
495502
*/
496-
const propertiesMeta = computed<iSelectOption[]>(() => {
503+
const propertiesMeta = computed<iPropertyMeta[]>(() => {
497504
return Object.entries(props.nodes[0])
498505
.sort(([a], [b]) => {
499506
// updatedAt, updatedBy, createdAt and createdBy to last position
@@ -506,13 +513,14 @@
506513
507514
return 0;
508515
})
509-
.map(([key]) => {
516+
.map(([key]): iPropertyMeta => {
510517
const options = (props.properties || []).map(toOption);
511518
const property = toOption(options.find((p) => p.value === key) || key);
512519
const aliasKey = _.snakeCase(key);
513520
514521
return {
515522
...property,
523+
value: String(property.value),
516524
alias: _.capitalize(_.startCase(property.alias || tet(aliasKey))),
517525
};
518526
})
@@ -542,24 +550,19 @@
542550
selectedNodes.value[index] = [selected, !children];
543551
}
544552
545-
/**
546-
* property is ordering the table
547-
*/
548-
function isOrdering(property: string | number): boolean {
549-
return ordering.value.name === property;
550-
}
551-
552553
/**
553554
* set pagination order
554555
*
556+
* TODO: require & use order target fn instead
557+
*
555558
* @replace
556559
*/
557-
function setOrdering(property: string | number) {
558-
var order = "ASC";
560+
function setOrdering(property: string) {
561+
let order: tOrder = "desc";
559562
560-
if (ordering.value.name === property && ordering.value.asc) {
563+
if (ordering.value[property]) {
561564
// switch order
562-
order = "DESC";
565+
order = ordering.value[property] === "desc" ? "asc" : "desc";
563566
}
564567
565568
const orderBy = `${property}:${order}`;

packages/components-vue/src/composables/utils.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { inject } from "vue";
22

3-
import type { iPluginOptions } from "@open-xamu-co/ui-common-types";
3+
import type { iPluginOptions, tOrder, tOrderBy } from "@open-xamu-co/ui-common-types";
44

55
export function useSortObject(data: Record<string, any>) {
66
return Object.entries(data)
@@ -23,3 +23,28 @@ export function useHelpers<T>(helper: (o?: iPluginOptions) => T): ReturnType<typ
2323

2424
return helper(xamuOptions);
2525
}
26+
27+
export function useOrderBy(orderByParam: any): tOrderBy[] {
28+
function getOrderBy(bys: string[]) {
29+
return bys
30+
.map((by): tOrderBy | false => {
31+
if (typeof by !== "string") return false;
32+
33+
const splitBy = by.split(":", 2);
34+
const order: tOrder = splitBy[1]?.toLowerCase?.() === "asc" ? "asc" : "desc";
35+
36+
return [splitBy[0], order];
37+
})
38+
.filter((by): by is tOrderBy => !!by);
39+
}
40+
41+
const routeOrderBy: string | string[] = orderByParam;
42+
43+
if (Array.isArray(routeOrderBy)) {
44+
return getOrderBy(routeOrderBy);
45+
} else if (typeof routeOrderBy === "string") {
46+
return getOrderBy([routeOrderBy]);
47+
}
48+
49+
return [];
50+
}

0 commit comments

Comments
 (0)