mirror of
https://github.com/crater-invoice/crater.git
synced 2025-10-28 04:01:10 -04:00
v5.0.0 update
This commit is contained in:
351
resources/scripts/components/base/base-table/BaseTable.vue
Normal file
351
resources/scripts/components/base/base-table/BaseTable.vue
Normal file
@ -0,0 +1,351 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<div class="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8 pb-4 lg:pb-0">
|
||||
<div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
||||
<div
|
||||
class="
|
||||
relative
|
||||
overflow-hidden
|
||||
bg-white
|
||||
border-b border-gray-200
|
||||
shadow
|
||||
sm:rounded-lg
|
||||
"
|
||||
>
|
||||
<slot name="header" />
|
||||
<table :class="tableClass">
|
||||
<thead :class="theadClass">
|
||||
<tr>
|
||||
<th
|
||||
v-for="column in tableColumns"
|
||||
:key="column.key"
|
||||
:class="[
|
||||
getThClass(column),
|
||||
{
|
||||
'text-bold text-black': sort.fieldName === column.key,
|
||||
},
|
||||
]"
|
||||
@click="changeSorting(column)"
|
||||
>
|
||||
{{ column.label }}
|
||||
<span
|
||||
v-if="sort.fieldName === column.key && sort.order === 'asc'"
|
||||
class="asc-direction"
|
||||
>
|
||||
↑
|
||||
</span>
|
||||
<span
|
||||
v-if="
|
||||
sort.fieldName === column.key && sort.order === 'desc'
|
||||
"
|
||||
class="desc-direction"
|
||||
>
|
||||
↓
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody
|
||||
v-if="loadingType === 'placeholder' && (loading || isLoading)"
|
||||
>
|
||||
<tr
|
||||
v-for="placeRow in placeholderCount"
|
||||
:key="placeRow"
|
||||
:class="placeRow % 2 === 0 ? 'bg-white' : 'bg-gray-50'"
|
||||
>
|
||||
<td
|
||||
v-for="column in columns"
|
||||
:key="column.key"
|
||||
class=""
|
||||
:class="getTdClass(column)"
|
||||
>
|
||||
<base-content-placeholders
|
||||
:class="getPlaceholderClass(column)"
|
||||
:rounded="true"
|
||||
>
|
||||
<base-content-placeholders-text
|
||||
class="w-full h-6"
|
||||
:lines="1"
|
||||
/>
|
||||
</base-content-placeholders>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody v-else>
|
||||
<tr
|
||||
v-for="(row, index) in sortedRows"
|
||||
:key="index"
|
||||
:class="index % 2 === 0 ? 'bg-white' : 'bg-gray-50'"
|
||||
>
|
||||
<td
|
||||
v-for="column in columns"
|
||||
:key="column.key"
|
||||
class=""
|
||||
:class="getTdClass(column)"
|
||||
>
|
||||
<slot :name="'cell-' + column.key" :row="row">
|
||||
{{ lodashGet(row.data, column.key) }}
|
||||
</slot>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div
|
||||
v-if="loadingType === 'spinner' && (loading || isLoading)"
|
||||
class="
|
||||
absolute
|
||||
top-0
|
||||
left-0
|
||||
z-10
|
||||
flex
|
||||
items-center
|
||||
justify-center
|
||||
w-full
|
||||
h-full
|
||||
bg-white bg-opacity-60
|
||||
"
|
||||
>
|
||||
<SpinnerIcon class="w-10 h-10 text-primary-500" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="
|
||||
!loading && !isLoading && sortedRows && sortedRows.length === 0
|
||||
"
|
||||
class="
|
||||
text-center text-gray-500
|
||||
pb-2
|
||||
flex
|
||||
h-[160px]
|
||||
justify-center
|
||||
items-center
|
||||
flex-col
|
||||
"
|
||||
>
|
||||
<BaseIcon
|
||||
name="ExclamationCircleIcon"
|
||||
class="w-6 h-6 text-gray-400"
|
||||
/>
|
||||
|
||||
<span class="block mt-1">{{ noResultsMessage }}</span>
|
||||
</div>
|
||||
|
||||
<BaseTablePagination
|
||||
v-if="pagination"
|
||||
:pagination="pagination"
|
||||
@pageChange="pageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, watch, ref, reactive } from 'vue'
|
||||
import { get } from 'lodash'
|
||||
import Row from './Row'
|
||||
import Column from './Column'
|
||||
import BaseTablePagination from './BaseTablePagination.vue'
|
||||
import SpinnerIcon from '@/scripts/components/icons/SpinnerIcon.vue'
|
||||
|
||||
const props = defineProps({
|
||||
columns: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
data: {
|
||||
type: [Array, Function],
|
||||
required: true,
|
||||
},
|
||||
sortBy: { type: String, default: '' },
|
||||
sortOrder: { type: String, default: '' },
|
||||
tableClass: {
|
||||
type: String,
|
||||
default: 'min-w-full divide-y divide-gray-200',
|
||||
},
|
||||
theadClass: { type: String, default: 'bg-gray-50' },
|
||||
tbodyClass: { type: String, default: '' },
|
||||
noResultsMessage: {
|
||||
type: String,
|
||||
default: 'No Results Found',
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
loadingType: {
|
||||
type: String,
|
||||
default: 'placeholder',
|
||||
validator: function (value) {
|
||||
return ['placeholder', 'spinner'].indexOf(value) !== -1
|
||||
},
|
||||
},
|
||||
placeholderCount: {
|
||||
type: Number,
|
||||
default: 3,
|
||||
},
|
||||
})
|
||||
|
||||
let rows = reactive([])
|
||||
let isLoading = ref(false)
|
||||
|
||||
let tableColumns = reactive(props.columns.map((column) => new Column(column)))
|
||||
|
||||
let sort = reactive({
|
||||
fieldName: '',
|
||||
order: '',
|
||||
})
|
||||
|
||||
let pagination = ref('')
|
||||
|
||||
const usesLocalData = computed(() => {
|
||||
return Array.isArray(props.data)
|
||||
})
|
||||
|
||||
const sortedRows = computed(() => {
|
||||
if (!usesLocalData.value) {
|
||||
return rows.value
|
||||
}
|
||||
|
||||
if (sort.fieldName === '') {
|
||||
return rows.value
|
||||
}
|
||||
|
||||
if (tableColumns.length === 0) {
|
||||
return rows.value
|
||||
}
|
||||
|
||||
const sortColumn = getColumn(sort.fieldName)
|
||||
|
||||
if (!sortColumn) {
|
||||
return rows.value
|
||||
}
|
||||
|
||||
let sorted = [...rows.value].sort(
|
||||
sortColumn.getSortPredicate(sort.order, tableColumns)
|
||||
)
|
||||
|
||||
return sorted
|
||||
})
|
||||
|
||||
function getColumn(columnName) {
|
||||
return tableColumns.find((column) => column.key === columnName)
|
||||
}
|
||||
|
||||
function getThClass(column) {
|
||||
let classes =
|
||||
'whitespace-nowrap px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider'
|
||||
|
||||
if (column.defaultThClass) {
|
||||
classes = column.defaultThClass
|
||||
}
|
||||
|
||||
if (column.sortable) {
|
||||
classes = `${classes} cursor-pointer`
|
||||
} else {
|
||||
classes = `${classes} pointer-events-none`
|
||||
}
|
||||
|
||||
if (column.thClass) {
|
||||
classes = `${classes} ${column.thClass}`
|
||||
}
|
||||
|
||||
return classes
|
||||
}
|
||||
|
||||
function getTdClass(column) {
|
||||
let classes = 'px-6 py-4 text-sm text-gray-500 whitespace-nowrap'
|
||||
|
||||
if (column.defaultTdClass) {
|
||||
classes = column.defaultTdClass
|
||||
}
|
||||
|
||||
if (column.tdClass) {
|
||||
classes = `${classes} ${column.tdClass}`
|
||||
}
|
||||
|
||||
return classes
|
||||
}
|
||||
|
||||
function getPlaceholderClass(column) {
|
||||
let classes = 'w-full'
|
||||
|
||||
if (column.placeholderClass) {
|
||||
classes = `${classes} ${column.placeholderClass}`
|
||||
}
|
||||
|
||||
return classes
|
||||
}
|
||||
|
||||
function prepareLocalData() {
|
||||
pagination.value = null
|
||||
return props.data
|
||||
}
|
||||
|
||||
async function fetchServerData() {
|
||||
const page = (pagination.value && pagination.value.currentPage) || 1
|
||||
|
||||
isLoading.value = true
|
||||
|
||||
const response = await props.data({
|
||||
sort,
|
||||
page,
|
||||
})
|
||||
|
||||
isLoading.value = false
|
||||
|
||||
pagination.value = response.pagination
|
||||
return response.data
|
||||
}
|
||||
|
||||
function changeSorting(column) {
|
||||
if (sort.fieldName !== column.key) {
|
||||
sort.fieldName = column.key
|
||||
sort.order = 'asc'
|
||||
} else {
|
||||
sort.order = sort.order === 'asc' ? 'desc' : 'asc'
|
||||
}
|
||||
|
||||
if (!usesLocalData.value) {
|
||||
mapDataToRows()
|
||||
}
|
||||
}
|
||||
|
||||
async function mapDataToRows() {
|
||||
const data = usesLocalData.value
|
||||
? prepareLocalData()
|
||||
: await fetchServerData()
|
||||
|
||||
rows.value = data.map((rowData) => new Row(rowData, tableColumns))
|
||||
}
|
||||
|
||||
async function pageChange(page) {
|
||||
pagination.value.currentPage = page
|
||||
await mapDataToRows()
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
await mapDataToRows()
|
||||
}
|
||||
|
||||
function lodashGet(array, key) {
|
||||
return get(array, key)
|
||||
}
|
||||
|
||||
if (usesLocalData.value) {
|
||||
watch(
|
||||
() => props.data,
|
||||
() => {
|
||||
mapDataToRows()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await mapDataToRows()
|
||||
})
|
||||
|
||||
defineExpose({ refresh })
|
||||
</script>
|
||||
@ -0,0 +1,361 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="shouldShowPagination"
|
||||
class="
|
||||
flex
|
||||
items-center
|
||||
justify-between
|
||||
px-4
|
||||
py-3
|
||||
bg-white
|
||||
border-t border-gray-200
|
||||
sm:px-6
|
||||
"
|
||||
>
|
||||
<div class="flex justify-between flex-1 sm:hidden">
|
||||
<a
|
||||
href="#"
|
||||
:class="{
|
||||
'disabled cursor-normal pointer-events-none !bg-gray-100 !text-gray-400':
|
||||
pagination.currentPage === 1,
|
||||
}"
|
||||
class="
|
||||
relative
|
||||
inline-flex
|
||||
items-center
|
||||
px-4
|
||||
py-2
|
||||
text-sm
|
||||
font-medium
|
||||
text-gray-700
|
||||
bg-white
|
||||
border border-gray-300
|
||||
rounded-md
|
||||
hover:bg-gray-50
|
||||
"
|
||||
@click="pageClicked(pagination.currentPage - 1)"
|
||||
>
|
||||
Previous
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
:class="{
|
||||
'disabled cursor-default pointer-events-none !bg-gray-100 !text-gray-400':
|
||||
pagination.currentPage === pagination.totalPages,
|
||||
}"
|
||||
class="
|
||||
relative
|
||||
inline-flex
|
||||
items-center
|
||||
px-4
|
||||
py-2
|
||||
ml-3
|
||||
text-sm
|
||||
font-medium
|
||||
text-gray-700
|
||||
bg-white
|
||||
border border-gray-300
|
||||
rounded-md
|
||||
hover:bg-gray-50
|
||||
"
|
||||
@click="pageClicked(pagination.currentPage + 1)"
|
||||
>
|
||||
Next
|
||||
</a>
|
||||
</div>
|
||||
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-700">
|
||||
Showing
|
||||
{{ ' ' }}
|
||||
<span
|
||||
v-if="pagination.limit && pagination.currentPage"
|
||||
class="font-medium"
|
||||
>
|
||||
{{
|
||||
pagination.currentPage * pagination.limit - (pagination.limit - 1)
|
||||
}}
|
||||
</span>
|
||||
{{ ' ' }}
|
||||
to
|
||||
{{ ' ' }}
|
||||
<span
|
||||
v-if="pagination.limit && pagination.currentPage"
|
||||
class="font-medium"
|
||||
>
|
||||
<span
|
||||
v-if="
|
||||
pagination.currentPage * pagination.limit <=
|
||||
pagination.totalCount
|
||||
"
|
||||
>
|
||||
{{ pagination.currentPage * pagination.limit }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ pagination.totalCount }}
|
||||
</span>
|
||||
</span>
|
||||
{{ ' ' }}
|
||||
of
|
||||
{{ ' ' }}
|
||||
<span v-if="pagination.totalCount" class="font-medium">
|
||||
{{ pagination.totalCount }}
|
||||
</span>
|
||||
{{ ' ' }}
|
||||
results
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<nav
|
||||
class="relative z-0 inline-flex -space-x-px rounded-md shadow-sm"
|
||||
aria-label="Pagination"
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
:class="{
|
||||
'disabled cursor-normal pointer-events-none !bg-gray-100 !text-gray-400':
|
||||
pagination.currentPage === 1,
|
||||
}"
|
||||
class="
|
||||
relative
|
||||
inline-flex
|
||||
items-center
|
||||
px-2
|
||||
py-2
|
||||
text-sm
|
||||
font-medium
|
||||
text-gray-500
|
||||
bg-white
|
||||
border border-gray-300
|
||||
rounded-l-md
|
||||
hover:bg-gray-50
|
||||
"
|
||||
@click="pageClicked(pagination.currentPage - 1)"
|
||||
>
|
||||
<span class="sr-only">Previous</span>
|
||||
<BaseIcon name="ChevronLeftIcon" />
|
||||
</a>
|
||||
<a
|
||||
v-if="hasFirst"
|
||||
href="#"
|
||||
aria-current="page"
|
||||
:class="{
|
||||
'z-10 bg-primary-50 border-primary-500 text-primary-600':
|
||||
isActive(1),
|
||||
'bg-white border-gray-300 text-gray-500 hover:bg-gray-50':
|
||||
!isActive(1),
|
||||
}"
|
||||
class="
|
||||
relative
|
||||
inline-flex
|
||||
items-center
|
||||
px-4
|
||||
py-2
|
||||
text-sm
|
||||
font-medium
|
||||
border
|
||||
"
|
||||
@click="pageClicked(1)"
|
||||
>
|
||||
1
|
||||
</a>
|
||||
|
||||
<span
|
||||
v-if="hasFirstEllipsis"
|
||||
class="
|
||||
relative
|
||||
inline-flex
|
||||
items-center
|
||||
px-4
|
||||
py-2
|
||||
text-sm
|
||||
font-medium
|
||||
text-gray-700
|
||||
bg-white
|
||||
border border-gray-300
|
||||
"
|
||||
>
|
||||
...
|
||||
</span>
|
||||
<a
|
||||
v-for="page in pages"
|
||||
:key="page"
|
||||
href="#"
|
||||
:class="{
|
||||
'z-10 bg-primary-50 border-primary-500 text-primary-600':
|
||||
isActive(page),
|
||||
'bg-white border-gray-300 text-gray-500 hover:bg-gray-50':
|
||||
!isActive(page),
|
||||
disabled: page === '...',
|
||||
}"
|
||||
class="
|
||||
relative
|
||||
items-center
|
||||
hidden
|
||||
px-4
|
||||
py-2
|
||||
text-sm
|
||||
font-medium
|
||||
text-gray-500
|
||||
bg-white
|
||||
border border-gray-300
|
||||
hover:bg-gray-50
|
||||
md:inline-flex
|
||||
"
|
||||
@click="pageClicked(page)"
|
||||
>
|
||||
{{ page }}
|
||||
</a>
|
||||
|
||||
<span
|
||||
v-if="hasLastEllipsis"
|
||||
class="
|
||||
relative
|
||||
inline-flex
|
||||
items-center
|
||||
px-4
|
||||
py-2
|
||||
text-sm
|
||||
font-medium
|
||||
text-gray-700
|
||||
bg-white
|
||||
border border-gray-300
|
||||
"
|
||||
>
|
||||
...
|
||||
</span>
|
||||
<a
|
||||
v-if="hasLast"
|
||||
href="#"
|
||||
aria-current="page"
|
||||
:class="{
|
||||
'z-10 bg-primary-50 border-primary-500 text-primary-600':
|
||||
isActive(pagination.totalPages),
|
||||
'bg-white border-gray-300 text-gray-500 hover:bg-gray-50':
|
||||
!isActive(pagination.totalPages),
|
||||
}"
|
||||
class="
|
||||
relative
|
||||
inline-flex
|
||||
items-center
|
||||
px-4
|
||||
py-2
|
||||
text-sm
|
||||
font-medium
|
||||
border
|
||||
"
|
||||
@click="pageClicked(pagination.totalPages)"
|
||||
>
|
||||
{{ pagination.totalPages }}
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
class="
|
||||
relative
|
||||
inline-flex
|
||||
items-center
|
||||
px-2
|
||||
py-2
|
||||
text-sm
|
||||
font-medium
|
||||
text-gray-500
|
||||
bg-white
|
||||
border border-gray-300
|
||||
rounded-r-md
|
||||
hover:bg-gray-50
|
||||
"
|
||||
:class="{
|
||||
'disabled cursor-default pointer-events-none !bg-gray-100 !text-gray-400':
|
||||
pagination.currentPage === pagination.totalPages,
|
||||
}"
|
||||
@click="pageClicked(pagination.currentPage + 1)"
|
||||
>
|
||||
<span class="sr-only">Next</span>
|
||||
<BaseIcon name="ChevronRightIcon" />
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// Todo: Need to convert this to Composition API
|
||||
|
||||
export default {
|
||||
props: {
|
||||
pagination: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
pages() {
|
||||
return this.pagination.totalPages === undefined ? [] : this.pageLinks()
|
||||
},
|
||||
hasFirst() {
|
||||
return this.pagination.currentPage >= 4 || this.pagination.totalPages < 10
|
||||
},
|
||||
hasLast() {
|
||||
return (
|
||||
this.pagination.currentPage <= this.pagination.totalPages - 3 ||
|
||||
this.pagination.totalPages < 10
|
||||
)
|
||||
},
|
||||
hasFirstEllipsis() {
|
||||
return (
|
||||
this.pagination.currentPage >= 4 && this.pagination.totalPages >= 10
|
||||
)
|
||||
},
|
||||
hasLastEllipsis() {
|
||||
return (
|
||||
this.pagination.currentPage <= this.pagination.totalPages - 3 &&
|
||||
this.pagination.totalPages >= 10
|
||||
)
|
||||
},
|
||||
shouldShowPagination() {
|
||||
if (this.pagination.totalPages === undefined) {
|
||||
return false
|
||||
}
|
||||
if (this.pagination.count === 0) {
|
||||
return false
|
||||
}
|
||||
return this.pagination.totalPages > 1
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isActive(page) {
|
||||
const currentPage = this.pagination.currentPage || 1
|
||||
return currentPage === page
|
||||
},
|
||||
pageClicked(page) {
|
||||
if (
|
||||
page === '...' ||
|
||||
page === this.pagination.currentPage ||
|
||||
page > this.pagination.totalPages ||
|
||||
page < 1
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$emit('pageChange', page)
|
||||
},
|
||||
pageLinks() {
|
||||
const pages = []
|
||||
let left = 2
|
||||
let right = this.pagination.totalPages - 1
|
||||
if (this.pagination.totalPages >= 10) {
|
||||
left = Math.max(1, this.pagination.currentPage - 2)
|
||||
right = Math.min(
|
||||
this.pagination.currentPage + 2,
|
||||
this.pagination.totalPages
|
||||
)
|
||||
}
|
||||
for (let i = left; i <= right; i++) {
|
||||
pages.push(i)
|
||||
}
|
||||
return pages
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
66
resources/scripts/components/base/base-table/Column.js
Normal file
66
resources/scripts/components/base/base-table/Column.js
Normal file
@ -0,0 +1,66 @@
|
||||
import { pick } from './helpers';
|
||||
|
||||
export default class Column {
|
||||
constructor(columnObject) {
|
||||
const properties = pick(columnObject, [
|
||||
'key', 'label', 'thClass', 'tdClass', 'sortBy', 'sortable', 'hidden', 'dataType'
|
||||
]);
|
||||
|
||||
for (const property in properties) {
|
||||
this[property] = columnObject[property];
|
||||
}
|
||||
|
||||
if (!properties['dataType']) {
|
||||
this['dataType'] = 'string'
|
||||
}
|
||||
|
||||
if (properties['sortable'] === undefined) {
|
||||
this['sortable'] = true
|
||||
}
|
||||
}
|
||||
|
||||
getFilterFieldName() {
|
||||
return this.filterOn || this.key;
|
||||
}
|
||||
|
||||
isSortable() {
|
||||
return this.sortable;
|
||||
}
|
||||
|
||||
getSortPredicate(sortOrder, allColumns) {
|
||||
const sortFieldName = this.getSortFieldName();
|
||||
|
||||
const sortColumn = allColumns.find(column => column.key === sortFieldName);
|
||||
|
||||
const dataType = sortColumn.dataType;
|
||||
|
||||
if (dataType.startsWith('date') || dataType === 'numeric') {
|
||||
|
||||
return (row1, row2) => {
|
||||
const value1 = row1.getSortableValue(sortFieldName);
|
||||
const value2 = row2.getSortableValue(sortFieldName);
|
||||
|
||||
if (sortOrder === 'desc') {
|
||||
return value2 < value1 ? -1 : 1;
|
||||
}
|
||||
|
||||
return value1 < value2 ? -1 : 1;
|
||||
};
|
||||
}
|
||||
|
||||
return (row1, row2) => {
|
||||
const value1 = row1.getSortableValue(sortFieldName);
|
||||
const value2 = row2.getSortableValue(sortFieldName);
|
||||
|
||||
if (sortOrder === 'desc') {
|
||||
return value2.localeCompare(value1);
|
||||
}
|
||||
|
||||
return value1.localeCompare(value2);
|
||||
};
|
||||
}
|
||||
|
||||
getSortFieldName() {
|
||||
return this.sortBy || this.key;
|
||||
}
|
||||
}
|
||||
43
resources/scripts/components/base/base-table/Row.js
Normal file
43
resources/scripts/components/base/base-table/Row.js
Normal file
@ -0,0 +1,43 @@
|
||||
import moment from 'moment';
|
||||
import { get } from './helpers';
|
||||
|
||||
export default class Row {
|
||||
constructor(data, columns) {
|
||||
this.data = data;
|
||||
this.columns = columns;
|
||||
}
|
||||
|
||||
getValue(columnName) {
|
||||
return get(this.data, columnName);
|
||||
}
|
||||
|
||||
getColumn(columnName) {
|
||||
return this.columns.find(column => column.key === columnName);
|
||||
}
|
||||
|
||||
getSortableValue(columnName) {
|
||||
const dataType = this.getColumn(columnName).dataType;
|
||||
|
||||
let value = this.getValue(columnName);
|
||||
|
||||
if (value === undefined || value === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (value instanceof String) {
|
||||
value = value.toLowerCase();
|
||||
}
|
||||
|
||||
if (dataType.startsWith('date')) {
|
||||
const format = dataType.replace('date:', '');
|
||||
|
||||
return moment(value, format).format('YYYYMMDDHHmmss');
|
||||
}
|
||||
|
||||
if (dataType === 'numeric') {
|
||||
return value;
|
||||
}
|
||||
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
30
resources/scripts/components/base/base-table/helpers.js
Normal file
30
resources/scripts/components/base/base-table/helpers.js
Normal file
@ -0,0 +1,30 @@
|
||||
export function classList(...classes) {
|
||||
return classes
|
||||
.map(c => Array.isArray(c) ? c : [c])
|
||||
.reduce((classes, c) => classes.concat(c), []);
|
||||
}
|
||||
|
||||
export function get(object, path) {
|
||||
if (!path) {
|
||||
return object;
|
||||
}
|
||||
|
||||
if (object === null || typeof object !== 'object') {
|
||||
return object;
|
||||
}
|
||||
|
||||
const [pathHead, pathTail] = path.split(/\.(.+)/);
|
||||
|
||||
return get(object[pathHead], pathTail);
|
||||
}
|
||||
|
||||
export function pick(object, properties) {
|
||||
return properties.reduce((pickedObject, property) => {
|
||||
pickedObject[property] = object[property];
|
||||
return pickedObject;
|
||||
}, {});
|
||||
}
|
||||
|
||||
export function range(from, to) {
|
||||
return [...Array(to - from)].map((_, i) => i + from);
|
||||
}
|
||||
Reference in New Issue
Block a user