Initial commit
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
<script setup>
|
||||
import data from '@/views/demos/forms/tables/data-table/datatable'
|
||||
|
||||
const headers = [
|
||||
{
|
||||
title: 'ID',
|
||||
sortable: false,
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: 'NAME',
|
||||
key: 'fullName',
|
||||
},
|
||||
{
|
||||
title: 'EMAIL',
|
||||
key: 'email',
|
||||
},
|
||||
{
|
||||
title: 'DATE',
|
||||
key: 'startDate',
|
||||
},
|
||||
{
|
||||
title: 'EXPERIENCE',
|
||||
key: 'experience',
|
||||
},
|
||||
{
|
||||
title: 'AGE',
|
||||
key: 'age',
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDataTable
|
||||
:headers="headers"
|
||||
:items="data"
|
||||
:items-per-page="5"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,211 @@
|
||||
<script setup>
|
||||
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
||||
import { AgGridVue } from 'ag-grid-vue3'
|
||||
import data from '@/views/demos/forms/tables/data-table/datatable'
|
||||
import { ModuleRegistry, AllCommunityModule } from 'ag-grid-community'
|
||||
import { ExcelExportModule } from 'ag-grid-enterprise'
|
||||
import DataTableHeader from './components/DataTableHeader.vue'
|
||||
import UserDetailsDialog from './components/UserDetailsDialog.vue'
|
||||
import ConfirmDeleteDialog from './components/ConfirmDeleteDialog.vue'
|
||||
import { useDataTableGrid } from './composables/useDataTableGrid'
|
||||
import { useDataTableActions } from './composables/useDataTableActions'
|
||||
import { useResponsive } from './composables/useResponsive'
|
||||
import jsPDF from 'jspdf'
|
||||
import autoTable from 'jspdf-autotable'
|
||||
|
||||
ModuleRegistry.registerModules([AllCommunityModule, ExcelExportModule])
|
||||
|
||||
const { isMobile, isTablet, windowWidth } = useResponsive()
|
||||
|
||||
const {
|
||||
gridApi,
|
||||
columnDefs,
|
||||
rowData,
|
||||
defaultColDef,
|
||||
gridOptions,
|
||||
onGridReady: gridReady,
|
||||
onCellValueChanged,
|
||||
updateColumnVisibility,
|
||||
updatePagination
|
||||
} = useDataTableGrid(data, isMobile, isTablet)
|
||||
|
||||
const {
|
||||
setupGlobalHandlers,
|
||||
cleanupGlobalHandlers,
|
||||
exportToCSV,
|
||||
exportToExcel,
|
||||
saveUser,
|
||||
confirmDelete,
|
||||
cancelDelete,
|
||||
showDetailsDialog,
|
||||
selectedRowData,
|
||||
deleteRowData,
|
||||
showDeleteDialog,
|
||||
deleteRow
|
||||
} = useDataTableActions(gridApi, rowData)
|
||||
|
||||
const quickFilter = ref('')
|
||||
|
||||
watch(quickFilter, (newValue) => {
|
||||
if (gridApi.value) {
|
||||
gridApi.value.setGridOption('quickFilterText', newValue || '')
|
||||
}
|
||||
}, { immediate: false })
|
||||
|
||||
const onGridReady = params => {
|
||||
gridReady(params)
|
||||
setupGlobalHandlers()
|
||||
}
|
||||
|
||||
const handleSaveUser = updatedData => saveUser(updatedData)
|
||||
|
||||
const handleQuickFilterUpdate = (value) => {
|
||||
quickFilter.value = value || ''
|
||||
|
||||
if (gridApi.value) {
|
||||
gridApi.value.setGridOption('quickFilterText', value || '')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setupGlobalHandlers()
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
cleanupGlobalHandlers()
|
||||
window.removeEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
const handleResize = () => {
|
||||
windowWidth.value = window.innerWidth
|
||||
if (gridApi.value) {
|
||||
updateColumnVisibility()
|
||||
updatePagination()
|
||||
setTimeout(() => {
|
||||
if (gridApi.value && gridApi.value.sizeColumnsToFit) {
|
||||
gridApi.value.sizeColumnsToFit()
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
||||
const exportToPDF = () => {
|
||||
if (!gridApi.value) {
|
||||
console.error('Grid API not available')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const doc = new jsPDF()
|
||||
const rowDataForPDF = []
|
||||
const headers = []
|
||||
|
||||
columnDefs.value.forEach(col => {
|
||||
if (col.headerName && col.field && col.field !== 'action') {
|
||||
headers.push(col.headerName)
|
||||
}
|
||||
})
|
||||
|
||||
gridApi.value.forEachNodeAfterFilterAndSort(node => {
|
||||
const row = []
|
||||
columnDefs.value.forEach(col => {
|
||||
if (col.field && col.field !== 'action') {
|
||||
let value = node.data[col.field] || ''
|
||||
|
||||
if (col.field === 'salary' && value) {
|
||||
value = `$${Math.floor(Number(value)).toLocaleString()}`
|
||||
} else if (col.field === 'startDate' && value) {
|
||||
const date = new Date(value)
|
||||
if (!isNaN(date.getTime())) {
|
||||
const day = date.getDate().toString().padStart(2, '0')
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||||
const year = date.getFullYear()
|
||||
value = `${day}/${month}/${year}`
|
||||
}
|
||||
} else if (col.field === 'status') {
|
||||
const statusMap = {
|
||||
0: 'Applied',
|
||||
1: 'Current',
|
||||
2: 'Professional',
|
||||
3: 'Rejected',
|
||||
4: 'Resigned'
|
||||
}
|
||||
value = statusMap[value] || 'Applied'
|
||||
}
|
||||
|
||||
row.push(value)
|
||||
}
|
||||
})
|
||||
rowDataForPDF.push(row)
|
||||
})
|
||||
|
||||
autoTable(doc, {
|
||||
head: [headers],
|
||||
body: rowDataForPDF,
|
||||
styles: {
|
||||
fontSize: 8,
|
||||
cellPadding: 2
|
||||
},
|
||||
headStyles: {
|
||||
fillColor: [41, 128, 185],
|
||||
textColor: 255,
|
||||
fontStyle: 'bold'
|
||||
},
|
||||
alternateRowStyles: {
|
||||
fillColor: [245, 245, 245]
|
||||
},
|
||||
margin: { top: 20 }
|
||||
})
|
||||
|
||||
doc.save(`data-table-${new Date().toISOString().split('T')[0]}.pdf`)
|
||||
console.log('PDF exported successfully')
|
||||
} catch (error) {
|
||||
console.error('Error exporting PDF:', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card class="elevation-2">
|
||||
<DataTableHeader
|
||||
:is-mobile="isMobile"
|
||||
:is-tablet="isTablet"
|
||||
:quick-filter="quickFilter"
|
||||
@update:quick-filter="handleQuickFilterUpdate"
|
||||
@export-csv="exportToCSV"
|
||||
@export-excel="exportToExcel"
|
||||
@export-pdf="exportToPDF"
|
||||
/>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-text class="pa-0">
|
||||
<AgGridVue
|
||||
class="vuetify-grid ag-theme-alpine-dark"
|
||||
:style="`height:${isMobile ? '400px' : '600px'}; width:100%`"
|
||||
:columnDefs="columnDefs"
|
||||
:rowData="rowData"
|
||||
:defaultColDef="defaultColDef"
|
||||
:gridOptions="gridOptions"
|
||||
@grid-ready="onGridReady"
|
||||
@cell-value-changed="onCellValueChanged"
|
||||
/>
|
||||
</v-card-text>
|
||||
|
||||
<UserDetailsDialog
|
||||
v-model="showDetailsDialog"
|
||||
:selected-row-data="selectedRowData"
|
||||
:is-mobile="isMobile"
|
||||
@save-user="handleSaveUser"
|
||||
/>
|
||||
|
||||
<ConfirmDeleteDialog
|
||||
v-model="showDeleteDialog"
|
||||
:selected-row-data="deleteRowData"
|
||||
:is-mobile="isMobile"
|
||||
@confirm="confirmDelete"
|
||||
@cancel="cancelDelete"
|
||||
/>
|
||||
</v-card>
|
||||
</template>
|
||||
@@ -0,0 +1,40 @@
|
||||
<script setup>
|
||||
import data from '@/views/demos/forms/tables/data-table/datatable'
|
||||
|
||||
const headers = [
|
||||
{
|
||||
title: 'ID',
|
||||
sortable: false,
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: 'NAME',
|
||||
key: 'fullName',
|
||||
},
|
||||
{
|
||||
title: 'EMAIL',
|
||||
key: 'email',
|
||||
},
|
||||
{
|
||||
title: 'DATE',
|
||||
key: 'startDate',
|
||||
},
|
||||
{
|
||||
title: 'EXPERIENCE',
|
||||
key: 'experience',
|
||||
},
|
||||
{
|
||||
title: 'AGE',
|
||||
key: 'age',
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDataTable
|
||||
:headers="headers"
|
||||
:items="data"
|
||||
density="compact"
|
||||
:items-per-page="5"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,119 @@
|
||||
<script setup>
|
||||
import data from '@/views/demos/forms/tables/data-table/datatable'
|
||||
|
||||
// Headers
|
||||
const headers = [
|
||||
{
|
||||
title: '',
|
||||
key: 'data-table-expand',
|
||||
},
|
||||
{
|
||||
title: 'NAME',
|
||||
key: 'fullName',
|
||||
},
|
||||
{
|
||||
title: 'EMAIL',
|
||||
key: 'email',
|
||||
},
|
||||
{
|
||||
title: 'DATE',
|
||||
key: 'startDate',
|
||||
},
|
||||
{
|
||||
title: 'SALARY',
|
||||
key: 'salary',
|
||||
},
|
||||
{
|
||||
title: 'AGE',
|
||||
key: 'age',
|
||||
},
|
||||
{
|
||||
title: 'STATUS',
|
||||
key: 'status',
|
||||
},
|
||||
]
|
||||
|
||||
const resolveStatusVariant = status => {
|
||||
if (status === 1)
|
||||
return {
|
||||
color: 'primary',
|
||||
text: 'Current',
|
||||
}
|
||||
else if (status === 2)
|
||||
return {
|
||||
color: 'success',
|
||||
text: 'Professional',
|
||||
}
|
||||
else if (status === 3)
|
||||
return {
|
||||
color: 'error',
|
||||
text: 'Rejected',
|
||||
}
|
||||
else if (status === 4)
|
||||
return {
|
||||
color: 'warning',
|
||||
text: 'Resigned',
|
||||
}
|
||||
else
|
||||
return {
|
||||
color: 'info',
|
||||
text: 'Applied',
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDataTable
|
||||
:headers="headers"
|
||||
:items="data"
|
||||
:items-per-page="5"
|
||||
expand-on-click
|
||||
>
|
||||
<!-- Expanded Row Data -->
|
||||
<template #expanded-row="slotProps">
|
||||
<tr class="v-data-table__tr">
|
||||
<td :colspan="headers.length">
|
||||
<p class="my-1">
|
||||
City: {{ slotProps.item.city }}
|
||||
</p>
|
||||
<p class="my-1">
|
||||
Experience: {{ slotProps.item.experience }}
|
||||
</p>
|
||||
<p>Post: {{ slotProps.item.post }}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<!-- full name -->
|
||||
<template #item.fullName="{ item }">
|
||||
<div class="d-flex align-center">
|
||||
<VAvatar
|
||||
size="32"
|
||||
:color="item.avatar ? '' : 'primary'"
|
||||
:class="item.avatar ? '' : 'v-avatar-light-bg primary--text'"
|
||||
:variant="!item.avatar ? 'tonal' : undefined"
|
||||
>
|
||||
<VImg
|
||||
v-if="item.avatar"
|
||||
:src="item.avatar"
|
||||
/>
|
||||
<span v-else>{{ avatarText(item.fullName) }}</span>
|
||||
</VAvatar>
|
||||
<div class="d-flex flex-column ms-3">
|
||||
<span class="d-block font-weight-medium text-high-emphasis text-truncate">{{ item.fullName }}</span>
|
||||
<small>{{ item.post }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #item.status="{ item }">
|
||||
<VChip
|
||||
:color="resolveStatusVariant(item.status).color"
|
||||
class="font-weight-medium"
|
||||
size="small"
|
||||
>
|
||||
{{ resolveStatusVariant(item.status).text }}
|
||||
</VChip>
|
||||
</template>
|
||||
</VDataTable>
|
||||
</template>
|
||||
@@ -0,0 +1,135 @@
|
||||
<script setup>
|
||||
import data from '@/views/demos/forms/tables/data-table/datatable'
|
||||
|
||||
const userList = ref([])
|
||||
|
||||
const options = ref({
|
||||
page: 1,
|
||||
itemsPerPage: 5,
|
||||
sortBy: [''],
|
||||
sortDesc: [false],
|
||||
})
|
||||
|
||||
// headers
|
||||
const headers = [
|
||||
{
|
||||
title: 'NAME',
|
||||
key: 'fullName',
|
||||
},
|
||||
{
|
||||
title: 'EMAIL',
|
||||
key: 'email',
|
||||
},
|
||||
{
|
||||
title: 'DATE',
|
||||
key: 'startDate',
|
||||
},
|
||||
{
|
||||
title: 'SALARY',
|
||||
key: 'salary',
|
||||
},
|
||||
{
|
||||
title: 'AGE',
|
||||
key: 'age',
|
||||
},
|
||||
{
|
||||
title: 'STATUS',
|
||||
key: 'status',
|
||||
},
|
||||
]
|
||||
|
||||
const resolveStatusVariant = status => {
|
||||
if (status === 1)
|
||||
return {
|
||||
color: 'primary',
|
||||
text: 'Current',
|
||||
}
|
||||
else if (status === 2)
|
||||
return {
|
||||
color: 'success',
|
||||
text: 'Professional',
|
||||
}
|
||||
else if (status === 3)
|
||||
return {
|
||||
color: 'error',
|
||||
text: 'Rejected',
|
||||
}
|
||||
else if (status === 4)
|
||||
return {
|
||||
color: 'warning',
|
||||
text: 'Resigned',
|
||||
}
|
||||
else
|
||||
return {
|
||||
color: 'info',
|
||||
text: 'Applied',
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
userList.value = JSON.parse(JSON.stringify(data))
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDataTable
|
||||
:headers="headers"
|
||||
:items="userList"
|
||||
:items-per-page="options.itemsPerPage"
|
||||
:page="options.page"
|
||||
:options="options"
|
||||
>
|
||||
<!-- full name -->
|
||||
<template #item.fullName="{ item }">
|
||||
<div class="d-flex align-center">
|
||||
<VAvatar
|
||||
size="32"
|
||||
:color="item.avatar ? '' : 'primary'"
|
||||
:class="item.avatar ? '' : 'v-avatar-light-bg primary--text'"
|
||||
:variant="!item.avatar ? 'tonal' : undefined"
|
||||
>
|
||||
<VImg
|
||||
v-if="item.avatar"
|
||||
:src="item.avatar"
|
||||
/>
|
||||
<span v-else>{{ avatarText(item.fullName) }}</span>
|
||||
</VAvatar>
|
||||
<div class="d-flex flex-column ms-3">
|
||||
<span class="d-block font-weight-medium text-high-emphasis text-truncate">{{ item.fullName }}</span>
|
||||
<small>{{ item.post }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- status -->
|
||||
<template #item.status="{ item }">
|
||||
<VChip
|
||||
:color="resolveStatusVariant(item.status).color"
|
||||
class="font-weight-medium"
|
||||
size="small"
|
||||
>
|
||||
{{ resolveStatusVariant(item.status).text }}
|
||||
</VChip>
|
||||
</template>
|
||||
|
||||
<template #bottom>
|
||||
<VCardText class="pt-2">
|
||||
<div class="d-flex flex-wrap justify-center justify-sm-space-between gap-y-2 mt-2">
|
||||
<VSelect
|
||||
v-model="options.itemsPerPage"
|
||||
:items="[5, 10, 25, 50, 100]"
|
||||
label="Rows per page:"
|
||||
variant="underlined"
|
||||
style="max-inline-size: 8rem;min-inline-size: 5rem;"
|
||||
/>
|
||||
|
||||
<VPagination
|
||||
v-model="options.page"
|
||||
:total-visible="$vuetify.display.smAndDown ? 3 : 5"
|
||||
:length="Math.ceil(userList.length / options.itemsPerPage)"
|
||||
/>
|
||||
</div>
|
||||
</VCardText>
|
||||
</template>
|
||||
</VDataTable>
|
||||
</template>
|
||||
@@ -0,0 +1,101 @@
|
||||
<script setup>
|
||||
import data from '@/views/demos/forms/tables/data-table/datatable'
|
||||
|
||||
const headers = [
|
||||
{
|
||||
title: 'NAME',
|
||||
key: 'fullName',
|
||||
},
|
||||
{
|
||||
title: 'EMAIL',
|
||||
key: 'email',
|
||||
},
|
||||
{
|
||||
title: 'DATE',
|
||||
key: 'startDate',
|
||||
},
|
||||
{
|
||||
title: 'SALARY',
|
||||
key: 'salary',
|
||||
},
|
||||
{
|
||||
title: 'AGE',
|
||||
key: 'age',
|
||||
},
|
||||
{
|
||||
title: 'STATUS',
|
||||
key: 'status',
|
||||
},
|
||||
]
|
||||
|
||||
const resolveStatusVariant = status => {
|
||||
if (status === 1)
|
||||
return {
|
||||
color: 'primary',
|
||||
text: 'Current',
|
||||
}
|
||||
else if (status === 2)
|
||||
return {
|
||||
color: 'success',
|
||||
text: 'Professional',
|
||||
}
|
||||
else if (status === 3)
|
||||
return {
|
||||
color: 'error',
|
||||
text: 'Rejected',
|
||||
}
|
||||
else if (status === 4)
|
||||
return {
|
||||
color: 'warning',
|
||||
text: 'Resigned',
|
||||
}
|
||||
else
|
||||
return {
|
||||
color: 'info',
|
||||
text: 'Applied',
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDataTable
|
||||
:headers="headers"
|
||||
:items="data"
|
||||
:items-per-page="10"
|
||||
height="300"
|
||||
fixed-header
|
||||
>
|
||||
<!-- full name -->
|
||||
<template #item.fullName="{ item }">
|
||||
<div class="d-flex align-center">
|
||||
<VAvatar
|
||||
size="32"
|
||||
:color="item.avatar ? '' : 'primary'"
|
||||
:class="item.avatar ? '' : 'v-avatar-light-bg primary--text'"
|
||||
:variant="!item.avatar ? 'tonal' : undefined"
|
||||
>
|
||||
<VImg
|
||||
v-if="item.avatar"
|
||||
:src="item.avatar"
|
||||
/>
|
||||
<span v-else>{{ avatarText(item.fullName) }}</span>
|
||||
</VAvatar>
|
||||
<div class="d-flex flex-column ms-3">
|
||||
<span class="d-block font-weight-medium text-high-emphasis text-truncate">{{ item.fullName }}</span>
|
||||
<small>{{ item.post }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- status -->
|
||||
<template #item.status="{ item }">
|
||||
<VChip
|
||||
:color="resolveStatusVariant(item.status).color"
|
||||
class="font-weight-medium"
|
||||
size="small"
|
||||
>
|
||||
{{ resolveStatusVariant(item.status).text }}
|
||||
</VChip>
|
||||
</template>
|
||||
</VDataTable>
|
||||
</template>
|
||||
@@ -0,0 +1,398 @@
|
||||
<script setup>
|
||||
import avatar1 from '@images/avatars/avatar-1.png'
|
||||
import avatar2 from '@images/avatars/avatar-2.png'
|
||||
import avatar4 from '@images/avatars/avatar-4.png'
|
||||
import avatar7 from '@images/avatars/avatar-7.png'
|
||||
import avatar8 from '@images/avatars/avatar-8.png'
|
||||
|
||||
const userList = [
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 1,
|
||||
avatar: avatar8,
|
||||
fullName: 'Korrie O\'Crevy',
|
||||
post: 'Nuclear Power Engineer',
|
||||
email: 'kocrevy0@thetimes.co.uk',
|
||||
city: 'Krasnosilka',
|
||||
startDate: '09/23/2016',
|
||||
salary: 23896.35,
|
||||
age: '61',
|
||||
experience: '1 Year',
|
||||
status: 'Professional',
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 2,
|
||||
avatar: avatar1,
|
||||
fullName: 'Bailie Coulman',
|
||||
post: 'VP Quality Control',
|
||||
email: 'bcoulman1@yolasite.com',
|
||||
city: 'Hinigaran',
|
||||
startDate: '05/20/2018',
|
||||
salary: 13633.69,
|
||||
age: '63',
|
||||
experience: '3 Years',
|
||||
status: 'Professional',
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 3,
|
||||
avatar: avatar7,
|
||||
fullName: 'Stella Ganderton',
|
||||
post: 'Operator',
|
||||
email: 'sganderton2@tuttocitta.it',
|
||||
city: 'Golcowa',
|
||||
startDate: '03/24/2018',
|
||||
salary: 13076.28,
|
||||
age: '66',
|
||||
experience: '6 Years',
|
||||
status: 'Applied',
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 4,
|
||||
avatar: avatar8,
|
||||
fullName: 'Dorolice Crossman',
|
||||
post: 'Cost Accountant',
|
||||
email: 'dcrossman3@google.co.jp',
|
||||
city: 'Paquera',
|
||||
startDate: '12/03/2017',
|
||||
salary: 12336.17,
|
||||
age: '22',
|
||||
experience: '2 Years',
|
||||
status: 'Professional',
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 6,
|
||||
avatar: '',
|
||||
fullName: 'Genevra Honeywood',
|
||||
post: 'Geologist',
|
||||
email: 'ghoneywood5@narod.ru',
|
||||
city: 'Maofan',
|
||||
startDate: '06/01/2017',
|
||||
salary: 17803.8,
|
||||
age: '61',
|
||||
experience: '1 Year',
|
||||
status: 'Current',
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 7,
|
||||
avatar: '',
|
||||
fullName: 'Eileen Diehn',
|
||||
post: 'Environmental Specialist',
|
||||
email: 'ediehn6@163.com',
|
||||
city: 'Lampuyang',
|
||||
startDate: '10/15/2017',
|
||||
salary: 18991.67,
|
||||
age: '59',
|
||||
experience: '9 Years',
|
||||
status: 'Rejected',
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 8,
|
||||
avatar: avatar7,
|
||||
fullName: 'Richardo Aldren',
|
||||
post: 'Senior Sales Associate',
|
||||
email: 'raldren7@mtv.com',
|
||||
city: 'Skoghall',
|
||||
startDate: '11/05/2016',
|
||||
salary: 19230.13,
|
||||
age: '55',
|
||||
experience: '5 Years',
|
||||
status: 'Rejected',
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 9,
|
||||
avatar: avatar2,
|
||||
fullName: 'Allyson Moakler',
|
||||
post: 'Safety Technician',
|
||||
email: 'amoakler8@shareasale.com',
|
||||
city: 'Mogilany',
|
||||
startDate: '12/29/2018',
|
||||
salary: 11677.32,
|
||||
age: '39',
|
||||
experience: '9 Years',
|
||||
status: 'Applied',
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 11,
|
||||
avatar: '',
|
||||
fullName: 'De Falloon',
|
||||
post: 'Sales Representative',
|
||||
email: 'dfalloona@ifeng.com',
|
||||
city: 'Colima',
|
||||
startDate: '06/12/2018',
|
||||
salary: 19252.12,
|
||||
age: '30',
|
||||
experience: '0 Year',
|
||||
status: 'Resigned',
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 12,
|
||||
avatar: '',
|
||||
fullName: 'Cyrus Gornal',
|
||||
post: 'Senior Sales Associate',
|
||||
email: 'cgornalb@fda.gov',
|
||||
city: 'Boro Utara',
|
||||
startDate: '12/09/2017',
|
||||
salary: 16745.47,
|
||||
age: '22',
|
||||
experience: '2 Years',
|
||||
status: 'Resigned',
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 13,
|
||||
avatar: '',
|
||||
fullName: 'Tallou Balf',
|
||||
post: 'Staff Accountant',
|
||||
email: 'tbalfc@sina.com.cn',
|
||||
city: 'Siliana',
|
||||
startDate: '01/21/2016',
|
||||
salary: 15488.53,
|
||||
age: '36',
|
||||
experience: '6 Years',
|
||||
status: 'Resigned',
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 15,
|
||||
avatar: '',
|
||||
fullName: 'Wilmar Bourton',
|
||||
post: 'Administrative Assistant',
|
||||
email: 'wbourtone@sakura.ne.jp',
|
||||
city: 'Bích Động',
|
||||
startDate: '04/25/2018',
|
||||
salary: 13304.45,
|
||||
age: '19',
|
||||
experience: '9 Years',
|
||||
status: 'Applied',
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 16,
|
||||
avatar: avatar4,
|
||||
fullName: 'Robinson Brazenor',
|
||||
post: 'General Manager',
|
||||
email: 'rbrazenorf@symantec.com',
|
||||
city: 'Gendiwu',
|
||||
startDate: '12/23/2017',
|
||||
salary: 11953.08,
|
||||
age: '66',
|
||||
experience: '6 Years',
|
||||
status: 'Applied',
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 17,
|
||||
avatar: '',
|
||||
fullName: 'Nadia Bettenson',
|
||||
post: 'Environmental Tech',
|
||||
email: 'nbettensong@joomla.org',
|
||||
city: 'Chabařovice',
|
||||
startDate: '07/11/2018',
|
||||
salary: 20484.44,
|
||||
age: '64',
|
||||
experience: '4 Years',
|
||||
status: 'Current',
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 18,
|
||||
avatar: '',
|
||||
fullName: 'Titus Hayne',
|
||||
post: 'Web Designer',
|
||||
email: 'thayneh@kickstarter.com',
|
||||
city: 'Yangon',
|
||||
startDate: '05/25/2019',
|
||||
salary: 16871.48,
|
||||
age: '59',
|
||||
experience: '9 Years',
|
||||
status: 'Current',
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 19,
|
||||
avatar: avatar4,
|
||||
fullName: 'Roxie Huck',
|
||||
post: 'Administrative Assistant',
|
||||
email: 'rhucki@ed.gov',
|
||||
city: 'Polýkastro',
|
||||
startDate: '04/04/2019',
|
||||
salary: 19653.56,
|
||||
age: '41',
|
||||
experience: '1 Year',
|
||||
status: 'Resigned',
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 23,
|
||||
avatar: avatar7,
|
||||
fullName: 'Rosmunda Steed',
|
||||
post: 'Assistant Media Planner',
|
||||
email: 'rsteedm@xing.com',
|
||||
city: 'Manzanares',
|
||||
startDate: '12/23/2017',
|
||||
salary: 13778.34,
|
||||
age: '21',
|
||||
experience: '1 Year',
|
||||
status: 'Applied',
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 26,
|
||||
avatar: avatar2,
|
||||
fullName: 'Morgen Benes',
|
||||
post: 'Senior Sales Associate',
|
||||
email: 'mbenesp@ted.com',
|
||||
city: 'Cà Mau',
|
||||
startDate: '04/10/2016',
|
||||
salary: 16969.63,
|
||||
age: '42',
|
||||
experience: '2 Years',
|
||||
status: 'Resigned',
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 28,
|
||||
avatar: '',
|
||||
fullName: 'Kliment McGinney',
|
||||
post: 'Chief Design Engineer',
|
||||
email: 'kmcginneyr@paginegialle.it',
|
||||
city: 'Xiaocheng',
|
||||
startDate: '07/09/2018',
|
||||
salary: 24027.81,
|
||||
age: '28',
|
||||
experience: '8 Years',
|
||||
status: 'Resigned',
|
||||
},
|
||||
{
|
||||
responsiveId: '',
|
||||
id: 31,
|
||||
avatar: '',
|
||||
fullName: 'Teressa Bleakman',
|
||||
post: 'Senior Editor',
|
||||
email: 'tbleakmanu@phpbb.com',
|
||||
city: 'Žebrák',
|
||||
startDate: '09/03/2016',
|
||||
salary: 24875.41,
|
||||
age: '37',
|
||||
experience: '7 Years',
|
||||
status: 'Applied',
|
||||
},
|
||||
]
|
||||
|
||||
const headers = [
|
||||
{
|
||||
title: 'Group by status',
|
||||
key: 'data-table-group',
|
||||
},
|
||||
{
|
||||
title: 'NAME',
|
||||
key: 'fullName',
|
||||
},
|
||||
{
|
||||
title: 'EMAIL',
|
||||
key: 'email',
|
||||
},
|
||||
{
|
||||
title: 'DATE',
|
||||
key: 'startDate',
|
||||
},
|
||||
{
|
||||
title: 'SALARY',
|
||||
key: 'salary',
|
||||
},
|
||||
{
|
||||
title: 'AGE',
|
||||
key: 'age',
|
||||
},
|
||||
{
|
||||
title: 'STATUS',
|
||||
key: 'status',
|
||||
},
|
||||
]
|
||||
|
||||
const groupBy = [{ key: 'status' }]
|
||||
|
||||
const resolveStatusVariant = status => {
|
||||
if (status === 'Current')
|
||||
return { color: 'primary' }
|
||||
else if (status === 'Professional')
|
||||
return { color: 'success' }
|
||||
else if (status === 'Rejected')
|
||||
return { color: 'error' }
|
||||
else if (status === 'Resigned')
|
||||
return { color: 'warning' }
|
||||
else
|
||||
return { color: 'info' }
|
||||
}
|
||||
|
||||
const getIcon = props => props.icon
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDataTable
|
||||
:headers="headers"
|
||||
:items="userList"
|
||||
:items-per-page="10"
|
||||
:group-by="groupBy"
|
||||
>
|
||||
<!-- full name -->
|
||||
<template #item.fullName="{ item }">
|
||||
<div class="d-flex align-center">
|
||||
<VAvatar
|
||||
size="32"
|
||||
:color="item.avatar ? '' : 'primary'"
|
||||
:class="item.avatar ? '' : 'v-avatar-light-bg primary--text'"
|
||||
:variant="!item.avatar ? 'tonal' : undefined"
|
||||
>
|
||||
<VImg
|
||||
v-if="item.avatar"
|
||||
:src="item.avatar"
|
||||
/>
|
||||
<span v-else>{{ avatarText(item.fullName) }}</span>
|
||||
</VAvatar>
|
||||
<div class="d-flex flex-column ms-3">
|
||||
<span class="d-block font-weight-medium text-high-emphasis text-truncate">{{ item.fullName }}</span>
|
||||
<small>{{ item.post }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #item.status="{ item }">
|
||||
<VChip
|
||||
:color="resolveStatusVariant(item.status).color"
|
||||
|
||||
size="small"
|
||||
class="font-weight-medium"
|
||||
>
|
||||
{{ item.status }}
|
||||
</VChip>
|
||||
</template>
|
||||
|
||||
<template #data-table-group="{ props, item, count }">
|
||||
<td>
|
||||
<VBtn
|
||||
v-bind="props"
|
||||
variant="text"
|
||||
density="comfortable"
|
||||
>
|
||||
<VIcon
|
||||
class="flip-in-rtl"
|
||||
:icon="getIcon(props)"
|
||||
/>
|
||||
</VBtn>
|
||||
|
||||
<span>{{ item.value }}</span>
|
||||
<span>({{ count }})</span>
|
||||
</td>
|
||||
</template>
|
||||
</VDataTable>
|
||||
</template>
|
||||
@@ -0,0 +1,305 @@
|
||||
<script setup>
|
||||
const {
|
||||
data: productList,
|
||||
error,
|
||||
} = await useApi('pages/datatable')
|
||||
|
||||
const search = ref('')
|
||||
|
||||
// headers
|
||||
const headers = [
|
||||
{
|
||||
title: 'PRODUCT',
|
||||
key: 'product.name',
|
||||
},
|
||||
{
|
||||
title: 'DATE',
|
||||
key: 'date',
|
||||
},
|
||||
{
|
||||
title: 'CATEGORY',
|
||||
key: 'product.category',
|
||||
},
|
||||
{
|
||||
title: 'BUYERS',
|
||||
key: 'buyer.name',
|
||||
},
|
||||
{
|
||||
title: 'PAYMENT',
|
||||
key: 'payment',
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
title: 'STATUS',
|
||||
key: 'status',
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
title: 'DELETE',
|
||||
key: 'delete',
|
||||
sortable: false,
|
||||
},
|
||||
]
|
||||
|
||||
const deleteItem = itemId => {
|
||||
if (!productList.value)
|
||||
return
|
||||
const index = productList.value.findIndex(item => item.product.id === itemId)
|
||||
|
||||
productList.value.splice(index, 1)
|
||||
}
|
||||
|
||||
const categoryIcons = [
|
||||
{
|
||||
name: 'Mouse',
|
||||
icon: 'tabler-mouse',
|
||||
color: 'warning',
|
||||
},
|
||||
{
|
||||
name: 'Glass',
|
||||
icon: 'tabler-eyeglass',
|
||||
color: 'primary',
|
||||
},
|
||||
{
|
||||
name: 'Smart Watch',
|
||||
icon: 'tabler-device-watch',
|
||||
color: 'success',
|
||||
},
|
||||
{
|
||||
name: 'Bag',
|
||||
icon: 'tabler-briefcase',
|
||||
color: 'info',
|
||||
},
|
||||
{
|
||||
name: 'Storage Device',
|
||||
icon: 'tabler-device-audio-tape',
|
||||
color: 'warning',
|
||||
},
|
||||
{
|
||||
name: 'Bluetooth',
|
||||
icon: 'tabler-bluetooth',
|
||||
color: 'error',
|
||||
},
|
||||
{
|
||||
name: 'Gaming',
|
||||
icon: 'tabler-device-gamepad-2',
|
||||
color: 'warning',
|
||||
},
|
||||
{
|
||||
name: 'Home',
|
||||
icon: 'tabler-home',
|
||||
color: 'error',
|
||||
},
|
||||
{
|
||||
name: 'VR',
|
||||
icon: 'tabler-badge-vr',
|
||||
color: 'primary',
|
||||
},
|
||||
{
|
||||
name: 'Shoes',
|
||||
icon: 'tabler-shoe',
|
||||
color: 'success',
|
||||
},
|
||||
{
|
||||
name: 'Electronics',
|
||||
icon: 'tabler-cpu',
|
||||
color: 'info',
|
||||
},
|
||||
{
|
||||
name: 'Projector',
|
||||
icon: 'tabler-theater',
|
||||
color: 'warning',
|
||||
},
|
||||
{
|
||||
name: 'iPod',
|
||||
icon: 'tabler-device-airpods',
|
||||
color: 'error',
|
||||
},
|
||||
{
|
||||
name: 'Keyboard',
|
||||
icon: 'tabler-keyboard',
|
||||
color: 'primary',
|
||||
},
|
||||
{
|
||||
name: 'Smart Phone',
|
||||
icon: 'tabler-device-mobile',
|
||||
color: 'success',
|
||||
},
|
||||
{
|
||||
name: 'Smart TV',
|
||||
icon: 'tabler-device-tv',
|
||||
color: 'info',
|
||||
},
|
||||
{
|
||||
name: 'Google Home',
|
||||
icon: 'tabler-brand-google',
|
||||
color: 'warning',
|
||||
},
|
||||
{
|
||||
name: 'Mac',
|
||||
icon: 'tabler-brand-apple',
|
||||
color: 'error',
|
||||
},
|
||||
{
|
||||
name: 'Headphone',
|
||||
icon: 'tabler-headphones',
|
||||
color: 'primary',
|
||||
},
|
||||
{
|
||||
name: 'iMac',
|
||||
icon: 'tabler-device-imac',
|
||||
color: 'success',
|
||||
},
|
||||
{
|
||||
name: 'iPhone',
|
||||
icon: 'tabler-brand-apple',
|
||||
color: 'warning',
|
||||
},
|
||||
]
|
||||
|
||||
const resolveStatusColor = status => {
|
||||
if (status === 'Confirmed')
|
||||
return 'primary'
|
||||
if (status === 'Completed')
|
||||
return 'success'
|
||||
if (status === 'Cancelled')
|
||||
return 'error'
|
||||
}
|
||||
|
||||
const categoryIconFilter = categoryName => {
|
||||
const index = categoryIcons.findIndex(category => category.name === categoryName)
|
||||
if (index !== -1)
|
||||
return [{
|
||||
icon: categoryIcons[index].icon,
|
||||
color: categoryIcons[index].color,
|
||||
}]
|
||||
|
||||
return [{
|
||||
icon: 'tabler-help-circle',
|
||||
color: 'primary',
|
||||
}]
|
||||
}
|
||||
|
||||
if (error.value)
|
||||
console.error(error.value)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
offset-md="8"
|
||||
md="4"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="search"
|
||||
placeholder="Search ..."
|
||||
append-inner-icon="tabler-search"
|
||||
single-line
|
||||
hide-details
|
||||
dense
|
||||
outlined
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
|
||||
<!-- 👉 Data Table -->
|
||||
<VDataTable
|
||||
:headers="headers"
|
||||
:items="productList || []"
|
||||
:search="search"
|
||||
:items-per-page="5"
|
||||
class="text-no-wrap"
|
||||
>
|
||||
<!-- product -->
|
||||
<template #item.product.name="{ item }">
|
||||
<div class="d-flex align-center">
|
||||
<div>
|
||||
<VImg
|
||||
:src="item.product.image"
|
||||
height="40"
|
||||
width="40"
|
||||
/>
|
||||
</div>
|
||||
<div class="d-flex flex-column ms-3">
|
||||
<span class="d-block font-weight-medium text-truncate text-high-emphasis">{{ item.product.name }}</span>
|
||||
<span class="text-xs">{{ item.product.brand }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- category -->
|
||||
<template #item.product.category="{ item }">
|
||||
<div class="d-flex align-center">
|
||||
<VAvatar
|
||||
v-for="(category, index) in categoryIconFilter(item.product.category)"
|
||||
:key="index"
|
||||
size="26"
|
||||
:color="category.color"
|
||||
variant="tonal"
|
||||
>
|
||||
<VIcon
|
||||
size="20"
|
||||
:color="category.color"
|
||||
class="rounded-0"
|
||||
>
|
||||
{{ category.icon }}
|
||||
</VIcon>
|
||||
</VAvatar>
|
||||
<span class="ms-1 text-no-wrap">{{ item.product.category }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- buyer -->
|
||||
<template #item.buyer.name="{ item }">
|
||||
<div class="d-flex align-center">
|
||||
<VAvatar
|
||||
size="1.875rem"
|
||||
:color="!item.buyer.avatar ? 'primary' : undefined"
|
||||
:variant="!item.buyer.avatar ? 'tonal' : undefined"
|
||||
>
|
||||
<VImg
|
||||
v-if="item.buyer.avatar"
|
||||
:src="item.buyer.avatar"
|
||||
/>
|
||||
<span v-else>{{ item.buyer.name.slice(0, 2).toUpperCase() }}</span>
|
||||
</VAvatar>
|
||||
<span class="text-no-wrap font-weight-medium text-high-emphasis ms-2">{{ item.buyer.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Payment -->
|
||||
<template #item.payment="{ item }">
|
||||
<div class="d-flex flex-column">
|
||||
<div class="d-flex align-center">
|
||||
<span class="text-high-emphasis font-weight-medium">${{ item.payment.paidAmount }}</span>
|
||||
<span v-if="item.payment.paidAmount !== item.payment.total">/{{ item.payment.total }}</span>
|
||||
</div>
|
||||
<span class="text-xs text-no-wrap">{{ item.payment.receivedPaymentStatus }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Status -->
|
||||
<template #item.status="{ item }">
|
||||
<VChip
|
||||
:color="resolveStatusColor(item.payment.status)"
|
||||
:class="`text-${resolveStatusColor(item.payment.status)}`"
|
||||
size="small"
|
||||
class="font-weight-medium"
|
||||
>
|
||||
{{ item.payment.status }}
|
||||
</VChip>
|
||||
</template>
|
||||
|
||||
<!-- Delete -->
|
||||
<template #item.delete="{ item }">
|
||||
<IconBtn @click="deleteItem(item.product.id)">
|
||||
<VIcon icon="tabler-trash" />
|
||||
</IconBtn>
|
||||
</template>
|
||||
</VDataTable>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,338 @@
|
||||
<script setup>
|
||||
import data from '@/views/demos/forms/tables/data-table/datatable'
|
||||
|
||||
const editDialog = ref(false)
|
||||
const deleteDialog = ref(false)
|
||||
|
||||
const defaultItem = ref({
|
||||
responsiveId: '',
|
||||
id: -1,
|
||||
avatar: '',
|
||||
fullName: '',
|
||||
post: '',
|
||||
email: '',
|
||||
city: '',
|
||||
startDate: '',
|
||||
salary: -1,
|
||||
age: '',
|
||||
experience: '',
|
||||
status: -1,
|
||||
})
|
||||
|
||||
const editedItem = ref(defaultItem.value)
|
||||
const editedIndex = ref(-1)
|
||||
const userList = ref([])
|
||||
|
||||
// status options
|
||||
const selectedOptions = [
|
||||
{
|
||||
text: 'Current',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
text: 'Professional',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
text: 'Rejected',
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
text: 'Resigned',
|
||||
value: 4,
|
||||
},
|
||||
{
|
||||
text: 'Applied',
|
||||
value: 5,
|
||||
},
|
||||
]
|
||||
|
||||
// headers
|
||||
const headers = [
|
||||
{
|
||||
title: 'NAME',
|
||||
key: 'fullName',
|
||||
},
|
||||
{
|
||||
title: 'EMAIL',
|
||||
key: 'email',
|
||||
},
|
||||
{
|
||||
title: 'DATE',
|
||||
key: 'startDate',
|
||||
},
|
||||
{
|
||||
title: 'SALARY',
|
||||
key: 'salary',
|
||||
},
|
||||
{
|
||||
title: 'AGE',
|
||||
key: 'age',
|
||||
},
|
||||
{
|
||||
title: 'STATUS',
|
||||
key: 'status',
|
||||
},
|
||||
{
|
||||
title: 'ACTIONS',
|
||||
key: 'actions',
|
||||
},
|
||||
]
|
||||
|
||||
const resolveStatusVariant = status => {
|
||||
if (status === 1)
|
||||
return {
|
||||
color: 'primary',
|
||||
text: 'Current',
|
||||
}
|
||||
else if (status === 2)
|
||||
return {
|
||||
color: 'success',
|
||||
text: 'Professional',
|
||||
}
|
||||
else if (status === 3)
|
||||
return {
|
||||
color: 'error',
|
||||
text: 'Rejected',
|
||||
}
|
||||
else if (status === 4)
|
||||
return {
|
||||
color: 'warning',
|
||||
text: 'Resigned',
|
||||
}
|
||||
else
|
||||
return {
|
||||
color: 'info',
|
||||
text: 'Applied',
|
||||
}
|
||||
}
|
||||
|
||||
const editItem = item => {
|
||||
editedIndex.value = userList.value.indexOf(item)
|
||||
editedItem.value = { ...item }
|
||||
editDialog.value = true
|
||||
}
|
||||
|
||||
const deleteItem = item => {
|
||||
editedIndex.value = userList.value.indexOf(item)
|
||||
editedItem.value = { ...item }
|
||||
deleteDialog.value = true
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
editDialog.value = false
|
||||
editedIndex.value = -1
|
||||
editedItem.value = { ...defaultItem.value }
|
||||
}
|
||||
|
||||
const closeDelete = () => {
|
||||
deleteDialog.value = false
|
||||
editedIndex.value = -1
|
||||
editedItem.value = { ...defaultItem.value }
|
||||
}
|
||||
|
||||
const save = () => {
|
||||
if (editedIndex.value > -1)
|
||||
Object.assign(userList.value[editedIndex.value], editedItem.value)
|
||||
else
|
||||
userList.value.push(editedItem.value)
|
||||
close()
|
||||
}
|
||||
|
||||
const deleteItemConfirm = () => {
|
||||
userList.value.splice(editedIndex.value, 1)
|
||||
closeDelete()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
userList.value = JSON.parse(JSON.stringify(data))
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 👉 Datatable -->
|
||||
<VDataTable
|
||||
:headers="headers"
|
||||
:items="userList"
|
||||
:items-per-page="5"
|
||||
>
|
||||
<!-- full name -->
|
||||
<template #item.fullName="{ item }">
|
||||
<div class="d-flex align-center">
|
||||
<!-- avatar -->
|
||||
<VAvatar
|
||||
size="32"
|
||||
:color="item.avatar ? '' : 'primary'"
|
||||
:class="item.avatar ? '' : 'v-avatar-light-bg primary--text'"
|
||||
:variant="!item.avatar ? 'tonal' : undefined"
|
||||
>
|
||||
<VImg
|
||||
v-if="item.avatar"
|
||||
:src="item.avatar"
|
||||
/>
|
||||
<span v-else>{{ avatarText(item.fullName) }}</span>
|
||||
</VAvatar>
|
||||
|
||||
<div class="d-flex flex-column ms-3">
|
||||
<span class="d-block font-weight-medium text-high-emphasis text-truncate">{{ item.fullName }}</span>
|
||||
<small>{{ item.post }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- status -->
|
||||
<template #item.status="{ item }">
|
||||
<VChip
|
||||
:color="resolveStatusVariant(item.status).color"
|
||||
size="small"
|
||||
>
|
||||
{{ resolveStatusVariant(item.status).text }}
|
||||
</VChip>
|
||||
</template>
|
||||
|
||||
<!-- Actions -->
|
||||
<template #item.actions="{ item }">
|
||||
<div class="d-flex gap-1">
|
||||
<IconBtn @click="editItem(item)">
|
||||
<VIcon icon="tabler-edit" />
|
||||
</IconBtn>
|
||||
<IconBtn @click="deleteItem(item)">
|
||||
<VIcon icon="tabler-trash" />
|
||||
</IconBtn>
|
||||
</div>
|
||||
</template>
|
||||
</VDataTable>
|
||||
|
||||
<!-- 👉 Edit Dialog -->
|
||||
<VDialog
|
||||
v-model="editDialog"
|
||||
max-width="600px"
|
||||
>
|
||||
<VCard title="Edit Item">
|
||||
<VCardText>
|
||||
<div class="text-body-1 mb-6">
|
||||
Name: <span class="text-h6">{{ editedItem?.fullName }}</span>
|
||||
</div>
|
||||
<VRow>
|
||||
<!-- fullName -->
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="editedItem.fullName"
|
||||
label="User name"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- email -->
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="editedItem.email"
|
||||
label="Email"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- salary -->
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="editedItem.salary"
|
||||
label="Salary"
|
||||
prefix="$"
|
||||
type="number"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- age -->
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="editedItem.age"
|
||||
label="Age"
|
||||
type="number"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- start date -->
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="editedItem.startDate"
|
||||
label="Date"
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<!-- status -->
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<AppSelect
|
||||
v-model="editedItem.status"
|
||||
:items="selectedOptions"
|
||||
item-title="text"
|
||||
item-value="value"
|
||||
label="Standard"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
|
||||
<VCardText>
|
||||
<div class="self-align-end d-flex gap-4 justify-end">
|
||||
<VBtn
|
||||
color="error"
|
||||
variant="outlined"
|
||||
@click="close"
|
||||
>
|
||||
Cancel
|
||||
</VBtn>
|
||||
<VBtn
|
||||
color="success"
|
||||
variant="elevated"
|
||||
@click="save"
|
||||
>
|
||||
Save
|
||||
</VBtn>
|
||||
</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
|
||||
<!-- 👉 Delete Dialog -->
|
||||
<VDialog
|
||||
v-model="deleteDialog"
|
||||
max-width="500px"
|
||||
>
|
||||
<VCard title="Are you sure you want to delete this item?">
|
||||
<VCardText>
|
||||
<div class="d-flex justify-center gap-4">
|
||||
<VBtn
|
||||
color="error"
|
||||
variant="outlined"
|
||||
@click="closeDelete"
|
||||
>
|
||||
Cancel
|
||||
</VBtn>
|
||||
<VBtn
|
||||
color="success"
|
||||
variant="elevated"
|
||||
@click="deleteItemConfirm"
|
||||
>
|
||||
OK
|
||||
</VBtn>
|
||||
</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
</template>
|
||||
@@ -0,0 +1,100 @@
|
||||
<script setup>
|
||||
import data from '@/views/demos/forms/tables/data-table/datatable'
|
||||
|
||||
const headers = [
|
||||
{
|
||||
title: 'NAME',
|
||||
key: 'fullName',
|
||||
},
|
||||
{
|
||||
title: 'EMAIL',
|
||||
key: 'email',
|
||||
},
|
||||
{
|
||||
title: 'DATE',
|
||||
key: 'startDate',
|
||||
},
|
||||
{
|
||||
title: 'SALARY',
|
||||
key: 'salary',
|
||||
},
|
||||
{
|
||||
title: 'AGE',
|
||||
key: 'age',
|
||||
},
|
||||
{
|
||||
title: 'STATUS',
|
||||
key: 'status',
|
||||
},
|
||||
]
|
||||
|
||||
const resolveStatusVariant = status => {
|
||||
if (status === 1)
|
||||
return {
|
||||
color: 'primary',
|
||||
text: 'Current',
|
||||
}
|
||||
else if (status === 2)
|
||||
return {
|
||||
color: 'success',
|
||||
text: 'Professional',
|
||||
}
|
||||
else if (status === 3)
|
||||
return {
|
||||
color: 'error',
|
||||
text: 'Rejected',
|
||||
}
|
||||
else if (status === 4)
|
||||
return {
|
||||
color: 'warning',
|
||||
text: 'Resigned',
|
||||
}
|
||||
else
|
||||
return {
|
||||
color: 'info',
|
||||
text: 'Applied',
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDataTable
|
||||
:headers="headers"
|
||||
:items="data"
|
||||
:items-per-page="5"
|
||||
show-select
|
||||
>
|
||||
<!-- full name -->
|
||||
<template #item.fullName="{ item }">
|
||||
<div class="d-flex align-center">
|
||||
<VAvatar
|
||||
size="32"
|
||||
:color="item.avatar ? '' : 'primary'"
|
||||
:class="item.avatar ? '' : 'v-avatar-light-bg primary--text'"
|
||||
:variant="!item.avatar ? 'tonal' : undefined"
|
||||
>
|
||||
<VImg
|
||||
v-if="item.avatar"
|
||||
:src="item.avatar"
|
||||
/>
|
||||
<span v-else>{{ avatarText(item.fullName) }}</span>
|
||||
</VAvatar>
|
||||
<div class="d-flex flex-column ms-3">
|
||||
<span class="d-block font-weight-medium text-high-emphasis text-truncate">{{ item.fullName }}</span>
|
||||
<small>{{ item.post }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- status -->
|
||||
<template #item.status="{ item }">
|
||||
<VChip
|
||||
:color="resolveStatusVariant(item.status).color"
|
||||
class="font-weight-medium"
|
||||
size="small"
|
||||
>
|
||||
{{ resolveStatusVariant(item.status).text }}
|
||||
</VChip>
|
||||
</template>
|
||||
</VDataTable>
|
||||
</template>
|
||||
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<v-dialog
|
||||
:model-value="modelValue"
|
||||
@update:model-value="handleClose"
|
||||
:max-width="isMobile ? '95%' : '500px'"
|
||||
persistent
|
||||
>
|
||||
<v-card>
|
||||
<v-card-title class="d-flex align-center">
|
||||
<VIcon icon="tabler-alert-triangle" color="error" class="me-3" />
|
||||
<span class="text-h6">Confirm Delete</span>
|
||||
</v-card-title>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-text class="py-6">
|
||||
<div class="text-body-1 mb-4">
|
||||
Are you sure you want to delete this user?
|
||||
</div>
|
||||
|
||||
<v-card
|
||||
v-if="selectedRowData"
|
||||
variant="outlined"
|
||||
class="pa-3 bg-grey-lighten-5"
|
||||
>
|
||||
<div class="d-flex flex-column gap-2">
|
||||
<div class="d-flex align-center">
|
||||
<VIcon icon="tabler-user" size="small" class="me-2" />
|
||||
<strong class="me-2">Name:</strong>
|
||||
{{ selectedRowData.fullName || 'Unknown' }}
|
||||
</div>
|
||||
<div class="d-flex align-center">
|
||||
<VIcon icon="tabler-mail" size="small" class="me-2" />
|
||||
<strong class="me-2">Email:</strong>
|
||||
{{ selectedRowData.email || 'Unknown' }}
|
||||
</div>
|
||||
</div>
|
||||
</v-card>
|
||||
|
||||
<v-alert
|
||||
type="warning"
|
||||
variant="tonal"
|
||||
class="mt-4"
|
||||
>
|
||||
<template #prepend>
|
||||
<VIcon icon="tabler-alert-circle" />
|
||||
</template>
|
||||
<template #text>
|
||||
This action cannot be undone and will permanently delete the user data.
|
||||
</template>
|
||||
</v-alert>
|
||||
</v-card-text>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-actions class="pa-4">
|
||||
<v-spacer />
|
||||
|
||||
<v-btn
|
||||
variant="outlined"
|
||||
color="grey"
|
||||
@click="handleCancel"
|
||||
:class="isMobile ? 'flex-grow-1 me-2' : ''"
|
||||
>
|
||||
<template #prepend>
|
||||
<VIcon icon="tabler-x" />
|
||||
</template>
|
||||
Cancel
|
||||
</v-btn>
|
||||
|
||||
<v-btn
|
||||
variant="flat"
|
||||
color="error"
|
||||
@click="handleConfirm"
|
||||
:class="isMobile ? 'flex-grow-1' : ''"
|
||||
>
|
||||
<template #prepend>
|
||||
<VIcon icon="tabler-trash" />
|
||||
</template>
|
||||
Delete
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
selectedRowData: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
isMobile: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:model-value', 'confirm', 'cancel'])
|
||||
|
||||
const handleClose = (value) => {
|
||||
if (!value) {
|
||||
emit('update:model-value', false)
|
||||
emit('cancel')
|
||||
}
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
emit('update:model-value', false)
|
||||
emit('cancel')
|
||||
}
|
||||
|
||||
const handleConfirm = () => {
|
||||
emit('update:model-value', false)
|
||||
emit('confirm')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-card-title {
|
||||
background: rgba(var(--v-theme-error), 0.1);
|
||||
border-bottom: 1px solid rgba(var(--v-theme-error), 0.2);
|
||||
}
|
||||
|
||||
.v-alert {
|
||||
border-left: 4px solid rgb(var(--v-theme-warning));
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.v-card-actions {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.v-btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,113 @@
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
isMobile: Boolean,
|
||||
isTablet: Boolean,
|
||||
quickFilter: String
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:quick-filter', 'export-csv', 'export-excel', 'export-pdf'])
|
||||
|
||||
const updateQuickFilter = value => {
|
||||
emit('update:quick-filter', value)
|
||||
}
|
||||
|
||||
const handleExportCsv = () => emit('export-csv')
|
||||
const handleExportExcel = () => emit('export-excel')
|
||||
const handleExportPdf = () => emit('export-pdf')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card-title
|
||||
class="d-flex align-center py-4 px-4"
|
||||
:class="{
|
||||
'flex-column ga-4': isMobile,
|
||||
'flex-row': !isMobile
|
||||
}"
|
||||
>
|
||||
<div class="d-flex align-center">
|
||||
<span class="text-h6 font-weight-medium">Data Table</span>
|
||||
</div>
|
||||
|
||||
<v-spacer v-if="!isMobile" />
|
||||
|
||||
<div
|
||||
class="d-flex align-center"
|
||||
:class="{
|
||||
'flex-column ga-3 w-100': isMobile,
|
||||
'ga-3': !isMobile
|
||||
}"
|
||||
>
|
||||
<v-text-field
|
||||
:model-value="quickFilter"
|
||||
@update:model-value="updateQuickFilter"
|
||||
append-inner-icon="tabler-search"
|
||||
label="Search..."
|
||||
single-line
|
||||
hide-details
|
||||
density="compact"
|
||||
:style="getSearchFieldWidth"
|
||||
variant="outlined"
|
||||
clearable
|
||||
placeholder="Search in all columns..."
|
||||
/>
|
||||
|
||||
<div
|
||||
class="d-flex ga-2"
|
||||
:class="{ 'w-100': isMobile }"
|
||||
>
|
||||
<v-btn
|
||||
color="success"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
@click="handleExportCsv"
|
||||
:class="{ 'flex-1-1-0': isMobile }"
|
||||
>
|
||||
<VIcon icon="tabler-file-text" size="18" />
|
||||
<span v-if="!isMobile" class="ms-2">Export CSV</span>
|
||||
<span v-else-if="isTablet" class="ms-1">CSV</span>
|
||||
<span v-else class="ms-1 text-caption">CSV</span>
|
||||
</v-btn>
|
||||
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
@click="handleExportExcel"
|
||||
:class="{ 'flex-1-1-0': isMobile }"
|
||||
>
|
||||
<VIcon icon="tabler-file-spreadsheet" size="18" />
|
||||
<span v-if="!isMobile" class="ms-2">Export Excel</span>
|
||||
<span v-else-if="isTablet" class="ms-1">Excel</span>
|
||||
<span v-else class="ms-1 text-caption">Excel</span>
|
||||
</v-btn>
|
||||
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
@click="handleExportPdf"
|
||||
:class="{ 'flex-1-1-0': isMobile }"
|
||||
>
|
||||
<VIcon icon="tabler-file-filled" size="18" />
|
||||
<span v-if="!isMobile" class="ms-2">Export PDF</span>
|
||||
<span v-else-if="isTablet" class="ms-1">PDF</span>
|
||||
<span v-else class="ms-1 text-caption">PDF</span>
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-title>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
computed: {
|
||||
getSearchFieldWidth() {
|
||||
if (this.isMobile) return 'width: 100%'
|
||||
if (this.isTablet) return 'width: 400px'
|
||||
return 'width: 500px'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,430 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
modelValue: Boolean,
|
||||
selectedRowData: Object,
|
||||
isMobile: Boolean
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'save-user'])
|
||||
|
||||
const isEditMode = ref(false)
|
||||
const editedData = ref({})
|
||||
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
if (newVal && props.selectedRowData) {
|
||||
editedData.value = { ...props.selectedRowData }
|
||||
}
|
||||
})
|
||||
|
||||
const handleEnterKey = () => {
|
||||
if (isEditMode.value) {
|
||||
saveChanges()
|
||||
}
|
||||
}
|
||||
|
||||
const closeDialog = () => {
|
||||
isEditMode.value = false
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
const toggleEditMode = () => {
|
||||
if (!isEditMode.value) {
|
||||
editedData.value = { ...props.selectedRowData }
|
||||
}
|
||||
isEditMode.value = !isEditMode.value
|
||||
}
|
||||
|
||||
const saveChanges = () => {
|
||||
emit('save-user', editedData.value)
|
||||
isEditMode.value = false
|
||||
}
|
||||
|
||||
const cancelEdit = () => {
|
||||
editedData.value = { ...props.selectedRowData }
|
||||
isEditMode.value = false
|
||||
}
|
||||
|
||||
const formatDate = (dateValue) => {
|
||||
if (!dateValue) return 'N/A'
|
||||
|
||||
const date = new Date(dateValue)
|
||||
if (isNaN(date.getTime())) return 'Invalid Date'
|
||||
|
||||
const day = date.getDate().toString().padStart(2, '0')
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||||
const year = date.getFullYear()
|
||||
|
||||
return `${day}/${month}/${year}`
|
||||
}
|
||||
|
||||
const formatDateForInput = (dateValue) => {
|
||||
if (!dateValue) return ''
|
||||
const date = new Date(dateValue)
|
||||
if (isNaN(date.getTime())) return ''
|
||||
return date.toISOString().split('T')[0]
|
||||
}
|
||||
|
||||
const formatSalary = (salaryValue) => {
|
||||
if (!salaryValue) return 'N/A'
|
||||
const intValue = Math.floor(Number(salaryValue))
|
||||
return `$${intValue.toLocaleString()}`
|
||||
}
|
||||
|
||||
const getStatusLabel = (statusValue) => {
|
||||
const statusMap = {
|
||||
0: 'Applied',
|
||||
1: 'Current',
|
||||
2: 'Professional',
|
||||
3: 'Rejected',
|
||||
4: 'Resigned'
|
||||
}
|
||||
return statusMap[statusValue] || 'Applied'
|
||||
}
|
||||
|
||||
const getStatusColor = (statusValue) => {
|
||||
const colorMap = {
|
||||
0: 'info',
|
||||
1: 'primary',
|
||||
2: 'success',
|
||||
3: 'error',
|
||||
4: 'warning'
|
||||
}
|
||||
return colorMap[statusValue] || 'grey'
|
||||
}
|
||||
|
||||
const statusOptions = [
|
||||
{ value: 0, title: 'Applied', color: 'info' },
|
||||
{ value: 1, title: 'Current', color: 'primary' },
|
||||
{ value: 2, title: 'Professional', color: 'success' },
|
||||
{ value: 3, title: 'Rejected', color: 'error' },
|
||||
{ value: 4, title: 'Resigned', color: 'warning' }
|
||||
]
|
||||
|
||||
const getFieldIcon = (field) => {
|
||||
const iconMap = {
|
||||
name: 'tabler-user',
|
||||
email: 'tabler-mail',
|
||||
startDate: 'tabler-calendar',
|
||||
salary: 'tabler-currency-dollar',
|
||||
age: 'tabler-hourglass',
|
||||
status: 'tabler-badge'
|
||||
}
|
||||
return iconMap[field] || 'tabler-info-circle'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-dialog :model-value="modelValue" @update:model-value="closeDialog" :max-width="isMobile ? '95%' : '700px'" persistent>
|
||||
<v-card
|
||||
class="elevation-12"
|
||||
rounded="lg"
|
||||
:style="{
|
||||
background: 'rgba(var(--v-theme-surface), 0.15)',
|
||||
backdropFilter: 'blur(20px)',
|
||||
webkitBackdropFilter: 'blur(20px)',
|
||||
border: '1px solid rgba(var(--v-theme-outline), 0.2)',
|
||||
boxShadow: '0 25px 45px rgba(0,0,0,0.1)'
|
||||
}"
|
||||
>
|
||||
<v-card-title class="bg-primary text-white pa-4">
|
||||
<div class="d-flex justify-space-between align-center w-100">
|
||||
<div class="d-flex align-center">
|
||||
<v-icon icon="tabler-user-circle" size="28" class="me-3" />
|
||||
<span class="text-h5">{{ isEditMode ? 'Edit User' : 'User Details' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-center gap-2">
|
||||
<template v-if="!isEditMode">
|
||||
<v-btn
|
||||
icon="tabler-edit"
|
||||
variant="text"
|
||||
size="small"
|
||||
color="white"
|
||||
:disabled="!selectedRowData"
|
||||
@click="toggleEditMode"
|
||||
title="Edit"
|
||||
/>
|
||||
<v-btn
|
||||
icon="tabler-x"
|
||||
variant="text"
|
||||
size="small"
|
||||
color="white"
|
||||
@click="closeDialog"
|
||||
title="Close"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-btn
|
||||
icon="tabler-x"
|
||||
variant="text"
|
||||
size="small"
|
||||
color="white"
|
||||
@click="cancelEdit"
|
||||
title="Cancel"
|
||||
/>
|
||||
<v-btn
|
||||
icon="tabler-check"
|
||||
variant="text"
|
||||
size="small"
|
||||
color="white"
|
||||
@click="saveChanges"
|
||||
title="Save"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text class="pa-6" v-if="selectedRowData">
|
||||
<v-form @keyup.enter="handleEnterKey">
|
||||
<v-container fluid class="pa-0">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" class="mb-2">
|
||||
<v-card variant="outlined" class="pa-4 h-100" color="surface-variant">
|
||||
<div class="d-flex align-center">
|
||||
<v-avatar
|
||||
class="me-4"
|
||||
size="40"
|
||||
:style="{
|
||||
background: 'rgba(var(--v-theme-primary), 0.2)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
webkitBackdropFilter: 'blur(10px)',
|
||||
border: '1px solid rgba(var(--v-theme-primary), 0.3)'
|
||||
}"
|
||||
>
|
||||
<v-icon :icon="getFieldIcon('name')" color="primary" />
|
||||
</v-avatar>
|
||||
<div class="flex-grow-1">
|
||||
<v-card-subtitle class="pa-0 text-caption text-medium-emphasis">Full Name</v-card-subtitle>
|
||||
<v-card-title v-if="!isEditMode" class="pa-0 text-h6 text-high-emphasis">
|
||||
{{ selectedRowData.fullName || 'N/A' }}
|
||||
</v-card-title>
|
||||
<v-text-field
|
||||
v-else
|
||||
v-model="editedData.fullName"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
class="mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" class="mb-2">
|
||||
<v-card variant="outlined" class="pa-4 h-100" color="surface-variant">
|
||||
<div class="d-flex align-center">
|
||||
<v-avatar
|
||||
class="me-4"
|
||||
size="40"
|
||||
:style="{
|
||||
background: 'rgba(var(--v-theme-secondary), 0.2)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
webkitBackdropFilter: 'blur(10px)',
|
||||
border: '1px solid rgba(var(--v-theme-secondary), 0.3)'
|
||||
}"
|
||||
>
|
||||
<v-icon :icon="getFieldIcon('email')" color="secondary" />
|
||||
</v-avatar>
|
||||
<div class="flex-grow-1">
|
||||
<v-card-subtitle class="pa-0 text-caption text-medium-emphasis">Email Address</v-card-subtitle>
|
||||
<v-card-title v-if="!isEditMode" class="pa-0 text-body-1 text-high-emphasis">
|
||||
{{ selectedRowData.email || 'N/A' }}
|
||||
</v-card-title>
|
||||
<v-text-field
|
||||
v-else
|
||||
v-model="editedData.email"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
type="email"
|
||||
class="mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" class="mb-2">
|
||||
<v-card variant="outlined" class="pa-4 h-100" color="surface-variant">
|
||||
<div class="d-flex align-center">
|
||||
<v-avatar
|
||||
class="me-4"
|
||||
size="40"
|
||||
:style="{
|
||||
background: 'rgba(var(--v-theme-info), 0.2)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
webkitBackdropFilter: 'blur(10px)',
|
||||
border: '1px solid rgba(var(--v-theme-info), 0.3)'
|
||||
}"
|
||||
>
|
||||
<v-icon :icon="getFieldIcon('startDate')" color="info" />
|
||||
</v-avatar>
|
||||
<div class="flex-grow-1">
|
||||
<v-card-subtitle class="pa-0 text-caption text-medium-emphasis">Start Date</v-card-subtitle>
|
||||
<v-card-title v-if="!isEditMode" class="pa-0 text-h6 text-high-emphasis">
|
||||
{{ formatDate(selectedRowData.startDate) }}
|
||||
</v-card-title>
|
||||
<v-text-field
|
||||
v-else
|
||||
v-model="editedData.startDate"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
type="date"
|
||||
class="mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" class="mb-2">
|
||||
<v-card variant="outlined" class="pa-4 h-100" color="surface-variant">
|
||||
<div class="d-flex align-center">
|
||||
<v-avatar
|
||||
class="me-4"
|
||||
size="40"
|
||||
:style="{
|
||||
background: 'rgba(var(--v-theme-success), 0.2)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
webkitBackdropFilter: 'blur(10px)',
|
||||
border: '1px solid rgba(var(--v-theme-success), 0.3)'
|
||||
}"
|
||||
>
|
||||
<v-icon :icon="getFieldIcon('salary')" color="success" />
|
||||
</v-avatar>
|
||||
<div class="flex-grow-1">
|
||||
<v-card-subtitle class="pa-0 text-caption text-medium-emphasis">Salary</v-card-subtitle>
|
||||
<v-card-title v-if="!isEditMode" class="pa-0 text-h6 text-success font-weight-bold">
|
||||
{{ formatSalary(selectedRowData.salary) }}
|
||||
</v-card-title>
|
||||
<v-text-field
|
||||
v-else
|
||||
v-model="editedData.salary"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
type="number"
|
||||
prefix="$"
|
||||
class="mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" class="mb-2">
|
||||
<v-card variant="outlined" class="pa-4 h-100" color="surface-variant">
|
||||
<div class="d-flex align-center">
|
||||
<v-avatar
|
||||
class="me-4"
|
||||
size="40"
|
||||
:style="{
|
||||
background: 'rgba(var(--v-theme-warning), 0.2)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
webkitBackdropFilter: 'blur(10px)',
|
||||
border: '1px solid rgba(var(--v-theme-warning), 0.3)'
|
||||
}"
|
||||
>
|
||||
<v-icon :icon="getFieldIcon('age')" color="warning" />
|
||||
</v-avatar>
|
||||
<div class="flex-grow-1">
|
||||
<v-card-subtitle class="pa-0 text-caption text-medium-emphasis">Age</v-card-subtitle>
|
||||
<v-card-title v-if="!isEditMode" class="pa-0 text-h6 text-high-emphasis">
|
||||
{{ selectedRowData.age ? selectedRowData.age + ' years' : 'N/A' }}
|
||||
</v-card-title>
|
||||
<v-text-field
|
||||
v-else
|
||||
v-model="editedData.age"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
type="number"
|
||||
suffix="years"
|
||||
class="mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" class="mb-2">
|
||||
<v-card variant="outlined" class="pa-4 h-100" color="surface-variant">
|
||||
<div class="d-flex align-center">
|
||||
<v-avatar
|
||||
class="me-4"
|
||||
size="40"
|
||||
:style="{
|
||||
background: `rgba(var(--v-theme-${getStatusColor(isEditMode ? editedData.status : selectedRowData.status)}), 0.2)`,
|
||||
backdropFilter: 'blur(10px)',
|
||||
webkitBackdropFilter: 'blur(10px)',
|
||||
border: `1px solid rgba(var(--v-theme-${getStatusColor(isEditMode ? editedData.status : selectedRowData.status)}), 0.3)`
|
||||
}"
|
||||
>
|
||||
<v-icon :icon="getFieldIcon('status')" :color="getStatusColor(isEditMode ? editedData.status : selectedRowData.status)" />
|
||||
</v-avatar>
|
||||
<div class="flex-grow-1">
|
||||
<v-card-subtitle class="pa-0 text-caption text-medium-emphasis">Status</v-card-subtitle>
|
||||
<div v-if="!isEditMode" class="pt-1">
|
||||
<v-chip
|
||||
:color="getStatusColor(selectedRowData.status)"
|
||||
size="default"
|
||||
variant="flat"
|
||||
class="font-weight-medium"
|
||||
>
|
||||
<v-icon start icon="tabler-check" size="small" />
|
||||
{{ getStatusLabel(selectedRowData.status) }}
|
||||
</v-chip>
|
||||
</div>
|
||||
<v-select
|
||||
v-else
|
||||
v-model="editedData.status"
|
||||
:items="statusOptions"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
class="mt-1"
|
||||
>
|
||||
<template #item="{ props, item }">
|
||||
<v-list-item v-bind="props" :title="item.title">
|
||||
<template #prepend>
|
||||
<v-chip :color="item.raw.color" size="small" class="me-2">{{ item.title }}</v-chip>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<template #selection="{ item }">
|
||||
<v-chip :color="item.raw.color" size="small">{{ item.title }}</v-chip>
|
||||
</template>
|
||||
</v-select>
|
||||
</div>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row class="mt-4">
|
||||
<v-col cols="12">
|
||||
<v-alert type="info" variant="tonal" class="mb-0" icon="tabler-info-circle">
|
||||
<v-alert-title class="text-body-1 font-weight-medium">Summary Information</v-alert-title>
|
||||
<div class="mt-2 text-body-2">
|
||||
Employee
|
||||
<strong>{{ (isEditMode ? editedData.fullName : selectedRowData.fullName) || 'Unknown' }}</strong>
|
||||
{{ (isEditMode ? editedData.age : selectedRowData.age) ? `(${isEditMode ? editedData.age : selectedRowData.age} years old)` : '' }}
|
||||
has been with the company since
|
||||
<strong>{{ formatDate(isEditMode ? editedData.startDate : selectedRowData.startDate) }}</strong>
|
||||
with a current status of
|
||||
<strong>{{ getStatusLabel(isEditMode ? editedData.status : selectedRowData.status) }}</strong>.
|
||||
</div>
|
||||
</v-alert>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
|
||||
<v-divider />
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
@@ -0,0 +1,210 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
export function useDataTableActions(gridApi, rowData) {
|
||||
const showDetailsDialog = ref(false)
|
||||
const showDeleteDialog = ref(false)
|
||||
const selectedRowData = ref(null)
|
||||
const deleteRowData = ref(null)
|
||||
|
||||
const getRowDataByIndex = (rowIndex) => {
|
||||
if (!gridApi.value) {
|
||||
console.error('Grid API not available')
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const node = gridApi.value.getDisplayedRowAtIndex(rowIndex)
|
||||
if (node && node.data) {
|
||||
return node.data
|
||||
}
|
||||
|
||||
if (rowData.value && rowData.value[rowIndex]) {
|
||||
return rowData.value[rowIndex]
|
||||
}
|
||||
|
||||
console.error('Row data not found for index:', rowIndex)
|
||||
return null
|
||||
} catch (error) {
|
||||
console.error('Error getting row data:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const showDetails = (rowIndex, nodeId) => {
|
||||
console.log('Show details called with:', { rowIndex, nodeId })
|
||||
|
||||
const rowData = getRowDataByIndex(rowIndex)
|
||||
if (rowData) {
|
||||
selectedRowData.value = { ...rowData }
|
||||
showDetailsDialog.value = true
|
||||
console.log('Selected row data:', selectedRowData.value)
|
||||
} else {
|
||||
console.error('Failed to get row data for details')
|
||||
}
|
||||
}
|
||||
|
||||
const editRow = (rowIndex, nodeId) => {
|
||||
console.log('Edit row called with:', { rowIndex, nodeId })
|
||||
|
||||
if (!gridApi.value) {
|
||||
console.error('Grid API not available for editing')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
gridApi.value.startEditingCell({
|
||||
rowIndex: rowIndex,
|
||||
colKey: 'fullName'
|
||||
})
|
||||
console.log('Started inline editing for row:', rowIndex)
|
||||
} catch (error) {
|
||||
console.error('Error starting inline edit:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const deleteRow = (rowIndex, nodeId) => {
|
||||
console.log('Delete row called with:', { rowIndex, nodeId })
|
||||
|
||||
const rowData = getRowDataByIndex(rowIndex)
|
||||
if (rowData) {
|
||||
deleteRowData.value = { ...rowData }
|
||||
showDeleteDialog.value = true
|
||||
console.log('Delete row data:', deleteRowData.value)
|
||||
} else {
|
||||
console.error('Failed to get row data for delete')
|
||||
}
|
||||
}
|
||||
|
||||
const confirmDelete = () => {
|
||||
if (deleteRowData.value && gridApi.value) {
|
||||
try {
|
||||
const indexToDelete = rowData.value.findIndex(item => {
|
||||
return (
|
||||
(item.id && item.id === deleteRowData.value.id) ||
|
||||
(item.email && item.email === deleteRowData.value.email) ||
|
||||
(item.fullName === deleteRowData.value.fullName &&
|
||||
item.salary === deleteRowData.value.salary)
|
||||
)
|
||||
})
|
||||
|
||||
if (indexToDelete !== -1) {
|
||||
rowData.value.splice(indexToDelete, 1)
|
||||
|
||||
gridApi.value.applyTransaction({
|
||||
remove: [deleteRowData.value]
|
||||
})
|
||||
|
||||
console.log('Row deleted successfully')
|
||||
} else {
|
||||
console.error('Row not found for deletion')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting row:', error)
|
||||
}
|
||||
}
|
||||
|
||||
showDeleteDialog.value = false
|
||||
deleteRowData.value = null
|
||||
}
|
||||
|
||||
const cancelDelete = () => {
|
||||
showDeleteDialog.value = false
|
||||
deleteRowData.value = null
|
||||
}
|
||||
|
||||
const saveUser = (editedData) => {
|
||||
if (!editedData || !gridApi.value) return
|
||||
|
||||
try {
|
||||
const indexToUpdate = rowData.value.findIndex(item => {
|
||||
return (
|
||||
(item.id && item.id === editedData.id) ||
|
||||
(item.email && item.email === selectedRowData.value.email) ||
|
||||
(item.fullName === selectedRowData.value.fullName)
|
||||
)
|
||||
})
|
||||
|
||||
if (indexToUpdate !== -1) {
|
||||
rowData.value[indexToUpdate] = { ...editedData }
|
||||
|
||||
const node = gridApi.value.getRowNode(indexToUpdate)
|
||||
if (node) {
|
||||
node.setData(editedData)
|
||||
}
|
||||
|
||||
console.log('User updated successfully:', editedData)
|
||||
} else {
|
||||
console.error('User not found for update')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating user:', error)
|
||||
}
|
||||
|
||||
showDetailsDialog.value = false
|
||||
selectedRowData.value = null
|
||||
}
|
||||
|
||||
const exportToCSV = () => {
|
||||
if (gridApi.value) {
|
||||
gridApi.value.exportDataAsCsv({
|
||||
fileName: `datatable-export-${new Date().toISOString().split('T')[0]}.csv`,
|
||||
columnSeparator: ',',
|
||||
suppressQuotes: false,
|
||||
onlySelected: false,
|
||||
skipGroups: true,
|
||||
skipHeader: false,
|
||||
skipFooters: true,
|
||||
skipPinnedTop: true,
|
||||
skipPinnedBottom: true,
|
||||
allColumns: false,
|
||||
onlySelectedAllPages: false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const exportToExcel = () => {
|
||||
if (gridApi.value) {
|
||||
gridApi.value.exportDataAsExcel({
|
||||
fileName: `datatable-export-${new Date().toISOString().split('T')[0]}.xlsx`,
|
||||
sheetName: 'Data Export',
|
||||
onlySelected: false,
|
||||
skipGroups: true,
|
||||
skipHeader: false,
|
||||
skipFooters: true,
|
||||
skipPinnedTop: true,
|
||||
skipPinnedBottom: true,
|
||||
allColumns: false,
|
||||
onlySelectedAllPages: false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const setupGlobalHandlers = () => {
|
||||
window.handleShowDetails = showDetails
|
||||
window.handleEditRow = editRow
|
||||
window.handleDeleteRow = deleteRow
|
||||
}
|
||||
|
||||
const cleanupGlobalHandlers = () => {
|
||||
delete window.handleShowDetails
|
||||
delete window.handleEditRow
|
||||
delete window.handleDeleteRow
|
||||
}
|
||||
|
||||
return {
|
||||
showDetailsDialog,
|
||||
showDeleteDialog,
|
||||
selectedRowData,
|
||||
deleteRowData,
|
||||
showDetails,
|
||||
editRow,
|
||||
deleteRow,
|
||||
confirmDelete,
|
||||
cancelDelete,
|
||||
saveUser,
|
||||
exportToCSV,
|
||||
exportToExcel,
|
||||
setupGlobalHandlers,
|
||||
cleanupGlobalHandlers
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,614 @@
|
||||
import { ref, computed } from "vue";
|
||||
|
||||
export function useDataTableGrid(data, isMobile, isTablet) {
|
||||
let gridApi = ref(null);
|
||||
const rowData = ref([]);
|
||||
|
||||
const statusCellRenderer = (params) => {
|
||||
const s = params.value;
|
||||
let label = "Applied";
|
||||
let colorClass = "info";
|
||||
|
||||
if (s === 1) {
|
||||
label = "Current";
|
||||
colorClass = "primary";
|
||||
}
|
||||
if (s === 2) {
|
||||
label = "Professional";
|
||||
colorClass = "success";
|
||||
}
|
||||
if (s === 3) {
|
||||
label = "Rejected";
|
||||
colorClass = "error";
|
||||
}
|
||||
if (s === 4) {
|
||||
label = "Resigned";
|
||||
colorClass = "warning";
|
||||
}
|
||||
|
||||
const chipSize = isMobile.value ? "x-small" : "small";
|
||||
const fontSize = isMobile.value ? "10px" : "12px";
|
||||
|
||||
return `<div class="v-chip v-chip--size-${chipSize} v-chip--variant-flat bg-${colorClass}"
|
||||
style="
|
||||
height:${isMobile.value ? "20px" : "24px"};
|
||||
padding:0 ${isMobile.value ? "6px" : "8px"};
|
||||
display:inline-flex;
|
||||
align-items:center;
|
||||
border-radius:12px;
|
||||
font-size:${fontSize};
|
||||
font-weight:500;
|
||||
color:white;
|
||||
border: inherit;
|
||||
">${label}</div>`;
|
||||
};
|
||||
|
||||
const statusCellEditor = () => {
|
||||
const statusOptions = [
|
||||
{ value: 0, label: "Applied" },
|
||||
{ value: 1, label: "Current" },
|
||||
{ value: 2, label: "Professional" },
|
||||
{ value: 3, label: "Rejected" },
|
||||
{ value: 4, label: "Resigned" },
|
||||
];
|
||||
let currentValue = 0;
|
||||
let selectElement = null;
|
||||
|
||||
const editorObject = {
|
||||
init: (params) => {
|
||||
currentValue = params.value || 0;
|
||||
selectElement = document.createElement("select");
|
||||
selectElement.style.cssText =
|
||||
"width: 100%; height: 100%; border: none; outline: none; font-size: 12px; background: white; color: black; padding: 4px;";
|
||||
|
||||
statusOptions.forEach((option) => {
|
||||
const optionElement = document.createElement("option");
|
||||
optionElement.value = option.value;
|
||||
optionElement.text = option.label;
|
||||
if (option.value == currentValue) {
|
||||
optionElement.selected = true;
|
||||
}
|
||||
selectElement.appendChild(optionElement);
|
||||
});
|
||||
|
||||
selectElement.addEventListener("change", (e) => {
|
||||
currentValue = parseInt(e.target.value);
|
||||
});
|
||||
|
||||
selectElement.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter" || e.key === "Tab") {
|
||||
e.preventDefault();
|
||||
currentValue = parseInt(selectElement.value);
|
||||
params.stopEditing();
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
params.stopEditing(true);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getGui: () => selectElement,
|
||||
|
||||
getValue: () => parseInt(currentValue),
|
||||
|
||||
afterGuiAttached: () => {
|
||||
selectElement.focus();
|
||||
selectElement.click();
|
||||
},
|
||||
|
||||
isPopup: () => false,
|
||||
};
|
||||
return editorObject;
|
||||
};
|
||||
|
||||
const actionButtonsRenderer = (params) => {
|
||||
const iconSize = isMobile.value ? "14px" : "16px";
|
||||
const buttonSize = isMobile.value ? "24px" : "28px";
|
||||
const gap = isMobile.value ? "3px" : "4px";
|
||||
|
||||
const rowIndex = params.node.rowIndex;
|
||||
const nodeId = params.node.id;
|
||||
|
||||
return `
|
||||
<div style="display: flex; gap: ${gap}; align-items: center; justify-content: center; padding: 2px 0;">
|
||||
<button
|
||||
class="action-btn details-btn"
|
||||
style="
|
||||
background: rgba(var(--v-theme-primary), 0.15);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(var(--v-theme-primary), 0.3);
|
||||
color: rgb(var(--v-theme-primary));
|
||||
border-radius: 12px;
|
||||
width: ${buttonSize};
|
||||
height: ${buttonSize};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 8px 32px rgba(var(--v-theme-primary), 0.12);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
"
|
||||
onmouseover="
|
||||
this.style.transform='translateY(-2px) scale(1.05)';
|
||||
this.style.background='rgba(var(--v-theme-primary), 0.25)';
|
||||
this.style.borderColor='rgba(var(--v-theme-primary), 0.5)';
|
||||
this.style.boxShadow='0 12px 40px rgba(var(--v-theme-primary), 0.2)';
|
||||
"
|
||||
onmouseout="
|
||||
this.style.transform='translateY(0) scale(1)';
|
||||
this.style.background='rgba(var(--v-theme-primary), 0.15)';
|
||||
this.style.borderColor='rgba(var(--v-theme-primary), 0.3)';
|
||||
this.style.boxShadow='0 8px 32px rgba(var(--v-theme-primary), 0.12)';
|
||||
"
|
||||
onclick="handleShowDetails(${rowIndex}, '${nodeId}')"
|
||||
title="View Details"
|
||||
>
|
||||
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="action-btn edit-btn"
|
||||
style="
|
||||
background: rgba(var(--v-theme-warning), 0.15);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(var(--v-theme-warning), 0.3);
|
||||
color: rgb(var(--v-theme-warning));
|
||||
border-radius: 12px;
|
||||
width: ${buttonSize};
|
||||
height: ${buttonSize};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 8px 32px rgba(var(--v-theme-warning), 0.12);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
"
|
||||
onmouseover="
|
||||
this.style.transform='translateY(-2px) scale(1.05)';
|
||||
this.style.background='rgba(var(--v-theme-warning), 0.25)';
|
||||
this.style.borderColor='rgba(var(--v-theme-warning), 0.5)';
|
||||
this.style.boxShadow='0 12px 40px rgba(var(--v-theme-warning), 0.2)';
|
||||
"
|
||||
onmouseout="
|
||||
this.style.transform='translateY(0) scale(1)';
|
||||
this.style.background='rgba(var(--v-theme-warning), 0.15)';
|
||||
this.style.borderColor='rgba(var(--v-theme-warning), 0.3)';
|
||||
this.style.boxShadow='0 8px 32px rgba(var(--v-theme-warning), 0.12)';
|
||||
"
|
||||
onclick="handleEditRow(${rowIndex}, '${nodeId}')"
|
||||
title="Edit Row"
|
||||
>
|
||||
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
|
||||
<path d="m18.5 2.5 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="action-btn delete-btn"
|
||||
style="
|
||||
background: rgba(var(--v-theme-error), 0.15);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(var(--v-theme-error), 0.3);
|
||||
color: rgb(var(--v-theme-error));
|
||||
border-radius: 12px;
|
||||
width: ${buttonSize};
|
||||
height: ${buttonSize};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 8px 32px rgba(var(--v-theme-error), 0.12);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
"
|
||||
onmouseover="
|
||||
this.style.transform='translateY(-2px) scale(1.05)';
|
||||
this.style.background='rgba(var(--v-theme-error), 0.25)';
|
||||
this.style.borderColor='rgba(var(--v-theme-error), 0.5)';
|
||||
this.style.boxShadow='0 12px 40px rgba(var(--v-theme-error), 0.2)';
|
||||
"
|
||||
onmouseout="
|
||||
this.style.transform='translateY(0) scale(1)';
|
||||
this.style.background='rgba(var(--v-theme-error), 0.15)';
|
||||
this.style.borderColor='rgba(var(--v-theme-error), 0.3)';
|
||||
this.style.boxShadow='0 8px 32px rgba(var(--v-theme-error), 0.12)';
|
||||
"
|
||||
onclick="handleDeleteRow(${rowIndex}, '${nodeId}')"
|
||||
title="Delete Row"
|
||||
>
|
||||
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||||
<path d="M3 6h18"></path>
|
||||
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6m3 0V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path>
|
||||
<line x1="10" y1="11" x2="10" y2="17"></line>
|
||||
<line x1="14" y1="11" x2="14" y2="17"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
|
||||
const columnDefs = ref([
|
||||
{
|
||||
headerName: "NAME",
|
||||
field: "fullName",
|
||||
sortable: true,
|
||||
filter: true,
|
||||
flex: 1,
|
||||
minWidth: 150,
|
||||
hide: false,
|
||||
editable: true,
|
||||
cellEditor: "agTextCellEditor",
|
||||
cellEditorParams: {
|
||||
maxLength: 50,
|
||||
},
|
||||
onCellValueChanged: (params) => {
|
||||
console.log("Name changed:", params.newValue);
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: "EMAIL",
|
||||
field: "email",
|
||||
sortable: true,
|
||||
filter: true,
|
||||
flex: 1.5,
|
||||
minWidth: 200,
|
||||
hide: false,
|
||||
editable: true,
|
||||
cellEditor: "agTextCellEditor",
|
||||
cellEditorParams: {
|
||||
maxLength: 100,
|
||||
},
|
||||
onCellValueChanged: (params) => {
|
||||
console.log("Email changed:", params.newValue);
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: "DATE",
|
||||
field: "startDate",
|
||||
sortable: true,
|
||||
filter: true,
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
hide: false,
|
||||
editable: true,
|
||||
cellEditor: "agDateCellEditor",
|
||||
cellEditorParams: {
|
||||
preventEdit: (params) => {
|
||||
return false;
|
||||
},
|
||||
},
|
||||
valueFormatter: (params) => {
|
||||
if (!params.value) return "";
|
||||
|
||||
const date = new Date(params.value);
|
||||
if (isNaN(date.getTime())) return "";
|
||||
|
||||
const day = date.getDate().toString().padStart(2, "0");
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
||||
const year = date.getFullYear();
|
||||
|
||||
return `${day}/${month}/${year}`;
|
||||
},
|
||||
valueParser: (params) => {
|
||||
if (!params.newValue) return null;
|
||||
|
||||
const dateStr = params.newValue;
|
||||
if (typeof dateStr === "string" && dateStr.includes("/")) {
|
||||
const parts = dateStr.split("/");
|
||||
if (parts.length === 3) {
|
||||
const day = parseInt(parts[0]);
|
||||
const month = parseInt(parts[1]) - 1;
|
||||
const year = parseInt(parts[2]);
|
||||
|
||||
const date = new Date(year, month, day);
|
||||
if (!isNaN(date.getTime())) {
|
||||
return date;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Date(dateStr);
|
||||
},
|
||||
filterParams: {
|
||||
buttons: ["reset"],
|
||||
closeOnApply: false,
|
||||
suppressAndOrCondition: true,
|
||||
comparator: (filterLocalDateAtMidnight, cellValue) => {
|
||||
if (!cellValue) return -1;
|
||||
|
||||
const cellDate = new Date(cellValue);
|
||||
cellDate.setHours(0, 0, 0, 0);
|
||||
|
||||
if (filterLocalDateAtMidnight.getTime() === cellDate.getTime()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return filterLocalDateAtMidnight < cellDate ? -1 : 1;
|
||||
},
|
||||
},
|
||||
onCellValueChanged: (params) => {
|
||||
if (
|
||||
params.newValue === "" ||
|
||||
params.newValue === null ||
|
||||
params.newValue === undefined
|
||||
) {
|
||||
params.node.setDataValue(params.colDef.field, params.oldValue);
|
||||
}
|
||||
console.log("Date changed:", params.newValue);
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: "SALARY",
|
||||
field: "salary",
|
||||
sortable: true,
|
||||
filter: true,
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
hide: false,
|
||||
editable: true,
|
||||
cellEditor: "agNumberCellEditor",
|
||||
cellEditorParams: {
|
||||
precision: 0,
|
||||
},
|
||||
filterParams: {
|
||||
buttons: ["reset"],
|
||||
closeOnApply: false,
|
||||
suppressAndOrCondition: true,
|
||||
inRangeInclusive: true,
|
||||
debounceMs: 200,
|
||||
},
|
||||
valueFormatter: (params) => {
|
||||
if (params.value) {
|
||||
const intValue = Math.floor(Number(params.value));
|
||||
return `$${intValue.toLocaleString()}`;
|
||||
}
|
||||
return "";
|
||||
},
|
||||
onCellValueChanged: (params) => {
|
||||
if (
|
||||
params.newValue !== null &&
|
||||
params.newValue !== undefined &&
|
||||
params.newValue !== ""
|
||||
) {
|
||||
const numValue = parseInt(params.newValue);
|
||||
if (!isNaN(numValue)) {
|
||||
params.node.setDataValue(params.colDef.field, numValue);
|
||||
} else {
|
||||
params.node.setDataValue(params.colDef.field, params.oldValue);
|
||||
}
|
||||
} else {
|
||||
params.node.setDataValue(params.colDef.field, params.oldValue);
|
||||
}
|
||||
console.log("Salary changed:", params.newValue);
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: "AGE",
|
||||
field: "age",
|
||||
sortable: true,
|
||||
filter: true,
|
||||
flex: 0.7,
|
||||
minWidth: 80,
|
||||
hide: false,
|
||||
editable: true,
|
||||
cellEditor: "agNumberCellEditor",
|
||||
cellEditorParams: {
|
||||
precision: 0,
|
||||
showStepperButtons: false,
|
||||
},
|
||||
|
||||
valueFormatter: (params) => {
|
||||
if (
|
||||
params.value === null ||
|
||||
params.value === undefined ||
|
||||
params.value === ""
|
||||
) {
|
||||
return "";
|
||||
}
|
||||
const numValue = Number(params.value);
|
||||
return isNaN(numValue) ? "" : Math.floor(numValue).toString();
|
||||
},
|
||||
|
||||
onCellValueChanged: (params) => {
|
||||
const newValue = params.newValue;
|
||||
|
||||
if (newValue === null || newValue === undefined || newValue === "") {
|
||||
params.node.setDataValue(params.colDef.field, params.oldValue);
|
||||
return;
|
||||
}
|
||||
|
||||
const cleanValue = String(newValue).trim();
|
||||
if (cleanValue === "") {
|
||||
params.node.setDataValue(params.colDef.field, params.oldValue);
|
||||
return;
|
||||
}
|
||||
|
||||
const parsed = parseInt(cleanValue, 10);
|
||||
if (isNaN(parsed)) {
|
||||
console.warn("Age: Invalid number format:", cleanValue);
|
||||
params.node.setDataValue(params.colDef.field, params.oldValue);
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsed < 1 || parsed > 120) {
|
||||
console.warn("Age: Out of range (1-120):", parsed);
|
||||
params.node.setDataValue(params.colDef.field, params.oldValue);
|
||||
return;
|
||||
}
|
||||
|
||||
params.node.setDataValue(params.colDef.field, parsed);
|
||||
console.log("Age changed from", params.oldValue, "to", parsed);
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: "STATUS",
|
||||
field: "status",
|
||||
sortable: true,
|
||||
filter: true,
|
||||
flex: 1,
|
||||
minWidth: 100,
|
||||
hide: false,
|
||||
editable: true,
|
||||
cellRenderer: statusCellRenderer,
|
||||
cellEditor: statusCellEditor,
|
||||
onCellValueChanged: (params) => {
|
||||
console.log("Status changed:", params.newValue);
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: "ACTION",
|
||||
field: "action",
|
||||
sortable: false,
|
||||
filter: false,
|
||||
flex: 1,
|
||||
minWidth: 100,
|
||||
hide: false,
|
||||
editable: false,
|
||||
cellRenderer: actionButtonsRenderer,
|
||||
suppressMenu: true,
|
||||
suppressSorting: true,
|
||||
suppressFilter: true,
|
||||
},
|
||||
]);
|
||||
|
||||
const defaultColDef = computed(() => ({
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filter: true,
|
||||
floatingFilter: false,
|
||||
cellStyle: { display: "flex", alignItems: "center" },
|
||||
flex: 1,
|
||||
minWidth: isMobile.value ? 80 : 100,
|
||||
maxWidth: 400,
|
||||
wrapText: false,
|
||||
autoHeight: false,
|
||||
suppressMenu: false,
|
||||
}));
|
||||
|
||||
const gridOptions = computed(() => ({
|
||||
theme: "legacy",
|
||||
headerHeight: isMobile.value ? 48 : 56,
|
||||
rowHeight: isMobile.value ? 44 : 52,
|
||||
animateRows: true,
|
||||
rowSelection: { type: "multiRow" },
|
||||
pagination: true,
|
||||
paginationPageSize: isMobile.value ? 5 : 10,
|
||||
paginationPageSizeSelector: isMobile.value ? [5, 10, 20] : [5, 10, 20, 50],
|
||||
suppressRowClickSelection: false,
|
||||
rowMultiSelectWithClick: true,
|
||||
enableCellTextSelection: true,
|
||||
suppressHorizontalScroll: false,
|
||||
alwaysShowHorizontalScroll: false,
|
||||
suppressColumnVirtualisation: false,
|
||||
autoGroupColumnDef: {
|
||||
minWidth: 200,
|
||||
maxWidth: 400,
|
||||
flex: 1,
|
||||
},
|
||||
editType: "fullRow",
|
||||
stopEditingWhenCellsLoseFocus: true,
|
||||
enterNavigatesVertically: true,
|
||||
enterNavigatesVerticallyAfterEdit: true,
|
||||
singleClickEdit: false,
|
||||
suppressClickEdit: false,
|
||||
includeHiddenColumnsInQuickFilter: false,
|
||||
cacheQuickFilter: true,
|
||||
enableAdvancedFilter: false,
|
||||
includeHiddenColumnsInAdvancedFilter: false,
|
||||
suppressBrowserResizeObserver: false,
|
||||
maintainColumnOrder: true,
|
||||
suppressMenuHide: true,
|
||||
enableRangeSelection: true,
|
||||
enableFillHandle: false,
|
||||
enableRangeHandle: false,
|
||||
}));
|
||||
|
||||
const updateColumnVisibility = () => {
|
||||
if (!gridApi.value) return;
|
||||
|
||||
if (isMobile.value) {
|
||||
gridApi.value.setColumnVisible("email", false);
|
||||
gridApi.value.setColumnVisible("startDate", false);
|
||||
gridApi.value.setColumnVisible("age", false);
|
||||
} else if (isTablet.value) {
|
||||
gridApi.value.setColumnVisible("email", true);
|
||||
gridApi.value.setColumnVisible("startDate", false);
|
||||
gridApi.value.setColumnVisible("age", true);
|
||||
} else {
|
||||
gridApi.value.setColumnVisible("email", true);
|
||||
gridApi.value.setColumnVisible("startDate", true);
|
||||
gridApi.value.setColumnVisible("age", true);
|
||||
}
|
||||
};
|
||||
|
||||
const updatePagination = () => {
|
||||
if (!gridApi.value) return;
|
||||
|
||||
const pageSize = isMobile.value ? 5 : 10;
|
||||
const pageSizeSelector = isMobile.value ? [5, 10, 20] : [5, 10, 20, 50];
|
||||
|
||||
gridApi.value.paginationSetPageSize(pageSize);
|
||||
gridApi.value.setGridOption("paginationPageSizeSelector", pageSizeSelector);
|
||||
};
|
||||
|
||||
function onGridReady(params) {
|
||||
gridApi.value = params.api;
|
||||
rowData.value = data.map((item) => ({
|
||||
...item,
|
||||
salary:
|
||||
typeof item.salary === "string" ? parseInt(item.salary) : item.salary,
|
||||
age: typeof item.age === "string" ? parseInt(item.age) : item.age,
|
||||
}));
|
||||
|
||||
updateColumnVisibility();
|
||||
updatePagination();
|
||||
setTimeout(() => {
|
||||
gridApi.value.sizeColumnsToFit();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function onCellValueChanged(params) {
|
||||
console.log("Cell value changed:", {
|
||||
field: params.colDef.field,
|
||||
oldValue: params.oldValue,
|
||||
newValue: params.newValue,
|
||||
data: params.data,
|
||||
});
|
||||
|
||||
const dataIndex = rowData.value.findIndex((item) => {
|
||||
return (
|
||||
(item.id && item.id === params.data.id) ||
|
||||
(item.email && item.email === params.data.email) ||
|
||||
item.fullName === params.data.fullName
|
||||
);
|
||||
});
|
||||
|
||||
if (dataIndex !== -1) {
|
||||
rowData.value[dataIndex] = { ...params.data };
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
gridApi,
|
||||
columnDefs,
|
||||
rowData,
|
||||
defaultColDef,
|
||||
gridOptions,
|
||||
onGridReady,
|
||||
onCellValueChanged,
|
||||
updateColumnVisibility,
|
||||
updatePagination,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
export function useResponsive() {
|
||||
const windowWidth = ref(window.innerWidth)
|
||||
const isMobile = ref(window.innerWidth < 768)
|
||||
const isTablet = ref(window.innerWidth >= 768 && window.innerWidth < 1024)
|
||||
|
||||
const handleResize = () => {
|
||||
windowWidth.value = window.innerWidth
|
||||
isMobile.value = window.innerWidth < 768
|
||||
isTablet.value = window.innerWidth >= 768 && window.innerWidth < 1024
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
})
|
||||
|
||||
return {
|
||||
windowWidth,
|
||||
isMobile,
|
||||
isTablet
|
||||
}
|
||||
}
|
||||
447
resources/js/views/demos/forms/tables/data-table/datatable.js
Normal file
447
resources/js/views/demos/forms/tables/data-table/datatable.js
Normal file
@@ -0,0 +1,447 @@
|
||||
import avatar1 from "@images/avatars/avatar-1.png";
|
||||
import avatar2 from "@images/avatars/avatar-2.png";
|
||||
import avatar3 from "@images/avatars/avatar-3.png";
|
||||
import avatar4 from "@images/avatars/avatar-4.png";
|
||||
import avatar5 from "@images/avatars/avatar-5.png";
|
||||
import avatar6 from "@images/avatars/avatar-6.png";
|
||||
import avatar7 from "@images/avatars/avatar-7.png";
|
||||
import avatar8 from "@images/avatars/avatar-8.png";
|
||||
|
||||
const data = [
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 95,
|
||||
avatar: avatar2,
|
||||
fullName: "Edwina Ebsworth",
|
||||
post: "Human Resources Assistant",
|
||||
email: "eebsworth2m@sbwire.com",
|
||||
city: "Puzi",
|
||||
startDate: "09/27/2018",
|
||||
salary: 19586.23,
|
||||
age: "27",
|
||||
experience: "2 Years",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 1,
|
||||
avatar: avatar8,
|
||||
fullName: "Korrie O'Crevy",
|
||||
post: "Nuclear Power Engineer",
|
||||
email: "kocrevy0@thetimes.co.uk",
|
||||
city: "Krasnosilka",
|
||||
startDate: "09/23/2016",
|
||||
salary: 23896.35,
|
||||
age: "61",
|
||||
experience: "1 Year",
|
||||
status: 2,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 7,
|
||||
avatar: "",
|
||||
fullName: "Eileen Diehn",
|
||||
post: "Environmental Specialist",
|
||||
email: "ediehn6@163.com",
|
||||
city: "Lampuyang",
|
||||
startDate: "10/15/2017",
|
||||
salary: 18991.67,
|
||||
age: "59",
|
||||
experience: "9 Years",
|
||||
status: 3,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 11,
|
||||
avatar: "",
|
||||
fullName: "De Falloon",
|
||||
post: "Sales Representative",
|
||||
email: "dfalloona@ifeng.com",
|
||||
city: "Colima",
|
||||
startDate: "06/12/2018",
|
||||
salary: 19252.12,
|
||||
age: "30",
|
||||
experience: "0 Year",
|
||||
status: 4,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 3,
|
||||
avatar: avatar7,
|
||||
fullName: "Stella Ganderton",
|
||||
post: "Operator",
|
||||
email: "sganderton2@tuttocitta.it",
|
||||
city: "Golcowa",
|
||||
startDate: "03/24/2018",
|
||||
salary: 13076.28,
|
||||
age: "66",
|
||||
experience: "6 Years",
|
||||
status: 5,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 5,
|
||||
avatar: "",
|
||||
fullName: "Harmonia Nisius",
|
||||
post: "Senior Cost Accountant",
|
||||
email: "hnisius4@gnu.org",
|
||||
city: "Lucan",
|
||||
startDate: "08/25/2017",
|
||||
salary: 10909.52,
|
||||
age: "33",
|
||||
experience: "3 Years",
|
||||
status: 2,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 6,
|
||||
avatar: "",
|
||||
fullName: "Genevra Honeywood",
|
||||
post: "Geologist",
|
||||
email: "ghoneywood5@narod.ru",
|
||||
city: "Maofan",
|
||||
startDate: "06/01/2017",
|
||||
salary: 17803.8,
|
||||
age: "61",
|
||||
experience: "1 Year",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 4,
|
||||
avatar: avatar8,
|
||||
fullName: "Dorolice Crossman",
|
||||
post: "Cost Accountant",
|
||||
email: "dcrossman3@google.co.jp",
|
||||
city: "Paquera",
|
||||
startDate: "12/03/2017",
|
||||
salary: 12336.17,
|
||||
age: "22",
|
||||
experience: "2 Years",
|
||||
status: 2,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 8,
|
||||
avatar: avatar7,
|
||||
fullName: "Richardo Aldren",
|
||||
post: "Senior Sales Associate",
|
||||
email: "raldren7@mtv.com",
|
||||
city: "Skoghall",
|
||||
startDate: "11/05/2016",
|
||||
salary: 19230.13,
|
||||
age: "55",
|
||||
experience: "5 Years",
|
||||
status: 3,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 9,
|
||||
avatar: avatar2,
|
||||
fullName: "Allyson Moakler",
|
||||
post: "Safety Technician",
|
||||
email: "amoakler8@shareasale.com",
|
||||
city: "Mogilany",
|
||||
startDate: "12/29/2018",
|
||||
salary: 11677.32,
|
||||
age: "39",
|
||||
experience: "9 Years",
|
||||
status: 5,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 10,
|
||||
avatar: avatar7,
|
||||
fullName: "Merline Penhalewick",
|
||||
post: "Junior Executive",
|
||||
email: "mpenhalewick9@php.net",
|
||||
city: "Kanuma",
|
||||
startDate: "04/19/2019",
|
||||
salary: 15939.52,
|
||||
age: "23",
|
||||
experience: "3 Years",
|
||||
status: 2,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 12,
|
||||
avatar: "",
|
||||
fullName: "Cyrus Gornal",
|
||||
post: "Senior Sales Associate",
|
||||
email: "cgornalb@fda.gov",
|
||||
city: "Boro Utara",
|
||||
startDate: "12/09/2017",
|
||||
salary: 16745.47,
|
||||
age: "22",
|
||||
experience: "2 Years",
|
||||
status: 4,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 13,
|
||||
avatar: "",
|
||||
fullName: "Tallou Balf",
|
||||
post: "Staff Accountant",
|
||||
email: "tbalfc@sina.com.cn",
|
||||
city: "Siliana",
|
||||
startDate: "01/21/2016",
|
||||
salary: 15488.53,
|
||||
age: "36",
|
||||
experience: "6 Years",
|
||||
status: 4,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 14,
|
||||
avatar: "",
|
||||
fullName: "Othilia Extill",
|
||||
post: "Associate Professor",
|
||||
email: "oextilld@theatlantic.com",
|
||||
city: "Brzyska",
|
||||
startDate: "02/01/2016",
|
||||
salary: 18442.34,
|
||||
age: "43",
|
||||
experience: "3 Years",
|
||||
status: 2,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 15,
|
||||
avatar: "",
|
||||
fullName: "Wilmar Bourton",
|
||||
post: "Administrative Assistant",
|
||||
email: "wbourtone@sakura.ne.jp",
|
||||
city: "Bích Động",
|
||||
startDate: "04/25/2018",
|
||||
salary: 13304.45,
|
||||
age: "19",
|
||||
experience: "9 Years",
|
||||
status: 5,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 16,
|
||||
avatar: avatar4,
|
||||
fullName: "Robinson Brazenor",
|
||||
post: "General Manager",
|
||||
email: "rbrazenorf@symantec.com",
|
||||
city: "Gendiwu",
|
||||
startDate: "12/23/2017",
|
||||
salary: 11953.08,
|
||||
age: "66",
|
||||
experience: "6 Years",
|
||||
status: 5,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 17,
|
||||
avatar: "",
|
||||
fullName: "Nadia Bettenson",
|
||||
post: "Environmental Tech",
|
||||
email: "nbettensong@joomla.org",
|
||||
city: "Chabařovice",
|
||||
startDate: "07/11/2018",
|
||||
salary: 20484.44,
|
||||
age: "64",
|
||||
experience: "4 Years",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 18,
|
||||
avatar: "",
|
||||
fullName: "Titus Hayne",
|
||||
post: "Web Designer",
|
||||
email: "thayneh@kickstarter.com",
|
||||
city: "Yangon",
|
||||
startDate: "05/25/2019",
|
||||
salary: 16871.48,
|
||||
age: "59",
|
||||
experience: "9 Years",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 19,
|
||||
avatar: avatar4,
|
||||
fullName: "Roxie Huck",
|
||||
post: "Administrative Assistant",
|
||||
email: "rhucki@ed.gov",
|
||||
city: "Polýkastro",
|
||||
startDate: "04/04/2019",
|
||||
salary: 19653.56,
|
||||
age: "41",
|
||||
experience: "1 Year",
|
||||
status: 4,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 20,
|
||||
avatar: avatar7,
|
||||
fullName: "Latashia Lewtey",
|
||||
post: "Actuary",
|
||||
email: "llewteyj@sun.com",
|
||||
city: "Hougong",
|
||||
startDate: "08/03/2017",
|
||||
salary: 18303.87,
|
||||
age: "35",
|
||||
experience: "5 Years",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 21,
|
||||
avatar: "",
|
||||
fullName: "Natalina Tyne",
|
||||
post: "Software Engineer",
|
||||
email: "ntynek@merriam-webster.com",
|
||||
city: "Yanguan",
|
||||
startDate: "03/16/2019",
|
||||
salary: 15256.4,
|
||||
age: "30",
|
||||
experience: "0 Year",
|
||||
status: 2,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 22,
|
||||
avatar: "",
|
||||
fullName: "Faun Josefsen",
|
||||
post: "Analog Circuit Design manager",
|
||||
email: "fjosefsenl@samsung.com",
|
||||
city: "Wengyang",
|
||||
startDate: "07/08/2017",
|
||||
salary: 11209.16,
|
||||
age: "40",
|
||||
experience: "0 Year",
|
||||
status: 3,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 23,
|
||||
avatar: avatar7,
|
||||
fullName: "Rosmunda Steed",
|
||||
post: "Assistant Media Planner",
|
||||
email: "rsteedm@xing.com",
|
||||
city: "Manzanares",
|
||||
startDate: "12/23/2017",
|
||||
salary: 13778.34,
|
||||
age: "21",
|
||||
experience: "1 Year",
|
||||
status: 5,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 24,
|
||||
avatar: "",
|
||||
fullName: "Scott Jiran",
|
||||
post: "Graphic Designer",
|
||||
email: "sjirann@simplemachines.org",
|
||||
city: "Pinglin",
|
||||
startDate: "05/26/2016",
|
||||
salary: 23081.71,
|
||||
age: "23",
|
||||
experience: "3 Years",
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 25,
|
||||
avatar: "",
|
||||
fullName: "Carmita Medling",
|
||||
post: "Accountant",
|
||||
email: "cmedlingo@hp.com",
|
||||
city: "Bourges",
|
||||
startDate: "07/31/2019",
|
||||
salary: 13602.24,
|
||||
age: "47",
|
||||
experience: "7 Years",
|
||||
status: 3,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 26,
|
||||
avatar: avatar2,
|
||||
fullName: "Morgen Benes",
|
||||
post: "Senior Sales Associate",
|
||||
email: "mbenesp@ted.com",
|
||||
city: "Cà Mau",
|
||||
startDate: "04/10/2016",
|
||||
salary: 16969.63,
|
||||
age: "42",
|
||||
experience: "2 Years",
|
||||
status: 4,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 27,
|
||||
avatar: "",
|
||||
fullName: "Onfroi Doughton",
|
||||
post: "Civil Engineer",
|
||||
email: "odoughtonq@aboutads.info",
|
||||
city: "Utrecht (stad)",
|
||||
startDate: "09/29/2018",
|
||||
salary: 23796.62,
|
||||
age: "28",
|
||||
experience: "8 Years",
|
||||
status: 3,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 28,
|
||||
avatar: "",
|
||||
fullName: "Kliment McGinney",
|
||||
post: "Chief Design Engineer",
|
||||
email: "kmcginneyr@paginegialle.it",
|
||||
city: "Xiaocheng",
|
||||
startDate: "07/09/2018",
|
||||
salary: 24027.81,
|
||||
age: "28",
|
||||
experience: "8 Years",
|
||||
status: 4,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 29,
|
||||
avatar: "",
|
||||
fullName: "Devin Bridgland",
|
||||
post: "Tax Accountant",
|
||||
email: "dbridglands@odnoklassniki.ru",
|
||||
city: "Baoli",
|
||||
startDate: "07/17/2016",
|
||||
salary: 13508.15,
|
||||
age: "48",
|
||||
experience: "8 Years",
|
||||
status: 3,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 30,
|
||||
avatar: avatar6,
|
||||
fullName: "Gilbert McFade",
|
||||
post: "Biostatistician",
|
||||
email: "gmcfadet@irs.gov",
|
||||
city: "Deje",
|
||||
startDate: "08/28/2018",
|
||||
salary: 21632.3,
|
||||
age: "20",
|
||||
experience: "0 Year",
|
||||
status: 2,
|
||||
},
|
||||
{
|
||||
responsiveId: "",
|
||||
id: 31,
|
||||
avatar: "",
|
||||
fullName: "Teressa Bleakman",
|
||||
post: "Senior Editor",
|
||||
email: "tbleakmanu@phpbb.com",
|
||||
city: "Žebrák",
|
||||
startDate: "09/03/2016",
|
||||
salary: 24875.41,
|
||||
age: "37",
|
||||
experience: "7 Years",
|
||||
status: 5,
|
||||
},
|
||||
];
|
||||
|
||||
export default data;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,86 @@
|
||||
<script setup>
|
||||
const desserts = [
|
||||
{
|
||||
dessert: 'Frozen Yogurt',
|
||||
calories: 159,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Ice cream sandwich',
|
||||
calories: 237,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Eclair',
|
||||
calories: 262,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Cupcake',
|
||||
calories: 305,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Gingerbread',
|
||||
calories: 356,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VTable class="text-no-wrap">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Desserts(100g Servings)
|
||||
</th>
|
||||
<th>
|
||||
calories
|
||||
</th>
|
||||
<th>
|
||||
Fat(g)
|
||||
</th>
|
||||
<th>
|
||||
Carbs(g)
|
||||
</th>
|
||||
<th>
|
||||
protein(g)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="item in desserts"
|
||||
:key="item.dessert"
|
||||
>
|
||||
<td>
|
||||
{{ item.dessert }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.calories }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.fat }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.carbs }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.protein }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
</template>
|
||||
@@ -0,0 +1,89 @@
|
||||
<script setup>
|
||||
const desserts = [
|
||||
{
|
||||
dessert: 'Frozen Yogurt',
|
||||
calories: 159,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Ice cream sandwich',
|
||||
calories: 237,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Eclair',
|
||||
calories: 262,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Cupcake',
|
||||
calories: 305,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Gingerbread',
|
||||
calories: 356,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VTable
|
||||
density="compact"
|
||||
class="text-no-wrap"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Desserts(100g Servings)
|
||||
</th>
|
||||
<th>
|
||||
calories
|
||||
</th>
|
||||
<th>
|
||||
Fat(g)
|
||||
</th>
|
||||
<th>
|
||||
Carbs(g)
|
||||
</th>
|
||||
<th>
|
||||
protein(g)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="item in desserts"
|
||||
:key="item.dessert"
|
||||
>
|
||||
<td>
|
||||
{{ item.dessert }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.calories }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.fat }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.carbs }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.protein }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
</template>
|
||||
@@ -0,0 +1,90 @@
|
||||
<script setup>
|
||||
const desserts = [
|
||||
{
|
||||
dessert: 'Frozen Yogurt',
|
||||
calories: 159,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Ice cream sandwich',
|
||||
calories: 237,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Eclair',
|
||||
calories: 262,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Cupcake',
|
||||
calories: 305,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Gingerbread',
|
||||
calories: 356,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VTable
|
||||
height="250"
|
||||
fixed-header
|
||||
class="text-no-wrap"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Desserts(100g Servings)
|
||||
</th>
|
||||
<th>
|
||||
calories
|
||||
</th>
|
||||
<th>
|
||||
Fat(g)
|
||||
</th>
|
||||
<th>
|
||||
Carbs(g)
|
||||
</th>
|
||||
<th>
|
||||
protein(g)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="item in desserts"
|
||||
:key="item.dessert"
|
||||
>
|
||||
<td>
|
||||
{{ item.dessert }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.calories }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.fat }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.carbs }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.protein }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
</template>
|
||||
@@ -0,0 +1,89 @@
|
||||
<script setup>
|
||||
const desserts = [
|
||||
{
|
||||
dessert: 'Frozen Yogurt',
|
||||
calories: 159,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Ice cream sandwich',
|
||||
calories: 237,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Eclair',
|
||||
calories: 262,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Cupcake',
|
||||
calories: 305,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Gingerbread',
|
||||
calories: 356,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VTable
|
||||
height="250"
|
||||
class="text-no-wrap"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Desserts(100g Servings)
|
||||
</th>
|
||||
<th>
|
||||
calories
|
||||
</th>
|
||||
<th>
|
||||
Fat(g)
|
||||
</th>
|
||||
<th>
|
||||
Carbs(g)
|
||||
</th>
|
||||
<th>
|
||||
protein(g)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="item in desserts"
|
||||
:key="item.dessert"
|
||||
>
|
||||
<td>
|
||||
{{ item.dessert }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.calories }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.fat }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.carbs }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.protein }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
</template>
|
||||
@@ -0,0 +1,90 @@
|
||||
<script setup>
|
||||
const desserts = [
|
||||
{
|
||||
dessert: 'Frozen Yogurt',
|
||||
calories: 159,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Ice cream sandwich',
|
||||
calories: 237,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Eclair',
|
||||
calories: 262,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Cupcake',
|
||||
calories: 305,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Gingerbread',
|
||||
calories: 356,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VTable
|
||||
|
||||
theme="dark"
|
||||
class="text-no-wrap rounded-0"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Desserts(100g Servings)
|
||||
</th>
|
||||
<th>
|
||||
calories
|
||||
</th>
|
||||
<th>
|
||||
Fat(g)
|
||||
</th>
|
||||
<th>
|
||||
Carbs(g)
|
||||
</th>
|
||||
<th>
|
||||
protein(g)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="item in desserts"
|
||||
:key="item.dessert"
|
||||
>
|
||||
<td>
|
||||
{{ item.dessert }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.calories }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.fat }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.carbs }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.protein }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
</template>
|
||||
@@ -0,0 +1,898 @@
|
||||
export const basic = { ts: `<script setup lang="ts">
|
||||
const desserts = [
|
||||
{
|
||||
dessert: 'Frozen Yogurt',
|
||||
calories: 159,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Ice cream sandwich',
|
||||
calories: 237,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Eclair',
|
||||
calories: 262,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Cupcake',
|
||||
calories: 305,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Gingerbread',
|
||||
calories: 356,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VTable class="text-no-wrap">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Desserts(100g Servings)
|
||||
</th>
|
||||
<th>
|
||||
calories
|
||||
</th>
|
||||
<th>
|
||||
Fat(g)
|
||||
</th>
|
||||
<th>
|
||||
Carbs(g)
|
||||
</th>
|
||||
<th>
|
||||
protein(g)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="item in desserts"
|
||||
:key="item.dessert"
|
||||
>
|
||||
<td>
|
||||
{{ item.dessert }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.calories }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.fat }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.carbs }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.protein }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
</template>
|
||||
`, js: `<script setup>
|
||||
const desserts = [
|
||||
{
|
||||
dessert: 'Frozen Yogurt',
|
||||
calories: 159,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Ice cream sandwich',
|
||||
calories: 237,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Eclair',
|
||||
calories: 262,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Cupcake',
|
||||
calories: 305,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Gingerbread',
|
||||
calories: 356,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VTable class="text-no-wrap">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Desserts(100g Servings)
|
||||
</th>
|
||||
<th>
|
||||
calories
|
||||
</th>
|
||||
<th>
|
||||
Fat(g)
|
||||
</th>
|
||||
<th>
|
||||
Carbs(g)
|
||||
</th>
|
||||
<th>
|
||||
protein(g)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="item in desserts"
|
||||
:key="item.dessert"
|
||||
>
|
||||
<td>
|
||||
{{ item.dessert }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.calories }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.fat }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.carbs }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.protein }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
</template>
|
||||
` }
|
||||
|
||||
export const density = { ts: `<script setup lang="ts">
|
||||
const desserts = [
|
||||
{
|
||||
dessert: 'Frozen Yogurt',
|
||||
calories: 159,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Ice cream sandwich',
|
||||
calories: 237,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Eclair',
|
||||
calories: 262,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Cupcake',
|
||||
calories: 305,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Gingerbread',
|
||||
calories: 356,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VTable
|
||||
density="compact"
|
||||
class="text-no-wrap"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Desserts(100g Servings)
|
||||
</th>
|
||||
<th>
|
||||
calories
|
||||
</th>
|
||||
<th>
|
||||
Fat(g)
|
||||
</th>
|
||||
<th>
|
||||
Carbs(g)
|
||||
</th>
|
||||
<th>
|
||||
protein(g)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="item in desserts"
|
||||
:key="item.dessert"
|
||||
>
|
||||
<td>
|
||||
{{ item.dessert }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.calories }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.fat }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.carbs }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.protein }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
</template>
|
||||
`, js: `<script setup>
|
||||
const desserts = [
|
||||
{
|
||||
dessert: 'Frozen Yogurt',
|
||||
calories: 159,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Ice cream sandwich',
|
||||
calories: 237,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Eclair',
|
||||
calories: 262,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Cupcake',
|
||||
calories: 305,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Gingerbread',
|
||||
calories: 356,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VTable
|
||||
density="compact"
|
||||
class="text-no-wrap"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Desserts(100g Servings)
|
||||
</th>
|
||||
<th>
|
||||
calories
|
||||
</th>
|
||||
<th>
|
||||
Fat(g)
|
||||
</th>
|
||||
<th>
|
||||
Carbs(g)
|
||||
</th>
|
||||
<th>
|
||||
protein(g)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="item in desserts"
|
||||
:key="item.dessert"
|
||||
>
|
||||
<td>
|
||||
{{ item.dessert }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.calories }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.fat }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.carbs }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.protein }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
</template>
|
||||
` }
|
||||
|
||||
export const fixedHeader = { ts: `<script setup lang="ts">
|
||||
const desserts = [
|
||||
{
|
||||
dessert: 'Frozen Yogurt',
|
||||
calories: 159,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Ice cream sandwich',
|
||||
calories: 237,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Eclair',
|
||||
calories: 262,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Cupcake',
|
||||
calories: 305,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Gingerbread',
|
||||
calories: 356,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VTable
|
||||
height="250"
|
||||
fixed-header
|
||||
class="text-no-wrap"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Desserts(100g Servings)
|
||||
</th>
|
||||
<th>
|
||||
calories
|
||||
</th>
|
||||
<th>
|
||||
Fat(g)
|
||||
</th>
|
||||
<th>
|
||||
Carbs(g)
|
||||
</th>
|
||||
<th>
|
||||
protein(g)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="item in desserts"
|
||||
:key="item.dessert"
|
||||
>
|
||||
<td>
|
||||
{{ item.dessert }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.calories }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.fat }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.carbs }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.protein }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
</template>
|
||||
`, js: `<script setup>
|
||||
const desserts = [
|
||||
{
|
||||
dessert: 'Frozen Yogurt',
|
||||
calories: 159,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Ice cream sandwich',
|
||||
calories: 237,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Eclair',
|
||||
calories: 262,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Cupcake',
|
||||
calories: 305,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Gingerbread',
|
||||
calories: 356,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VTable
|
||||
height="250"
|
||||
fixed-header
|
||||
class="text-no-wrap"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Desserts(100g Servings)
|
||||
</th>
|
||||
<th>
|
||||
calories
|
||||
</th>
|
||||
<th>
|
||||
Fat(g)
|
||||
</th>
|
||||
<th>
|
||||
Carbs(g)
|
||||
</th>
|
||||
<th>
|
||||
protein(g)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="item in desserts"
|
||||
:key="item.dessert"
|
||||
>
|
||||
<td>
|
||||
{{ item.dessert }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.calories }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.fat }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.carbs }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.protein }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
</template>
|
||||
` }
|
||||
|
||||
export const height = { ts: `<script setup lang="ts">
|
||||
const desserts = [
|
||||
{
|
||||
dessert: 'Frozen Yogurt',
|
||||
calories: 159,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Ice cream sandwich',
|
||||
calories: 237,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Eclair',
|
||||
calories: 262,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Cupcake',
|
||||
calories: 305,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Gingerbread',
|
||||
calories: 356,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VTable
|
||||
height="250"
|
||||
class="text-no-wrap"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Desserts(100g Servings)
|
||||
</th>
|
||||
<th>
|
||||
calories
|
||||
</th>
|
||||
<th>
|
||||
Fat(g)
|
||||
</th>
|
||||
<th>
|
||||
Carbs(g)
|
||||
</th>
|
||||
<th>
|
||||
protein(g)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="item in desserts"
|
||||
:key="item.dessert"
|
||||
>
|
||||
<td>
|
||||
{{ item.dessert }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.calories }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.fat }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.carbs }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.protein }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
</template>
|
||||
`, js: `<script setup>
|
||||
const desserts = [
|
||||
{
|
||||
dessert: 'Frozen Yogurt',
|
||||
calories: 159,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Ice cream sandwich',
|
||||
calories: 237,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Eclair',
|
||||
calories: 262,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Cupcake',
|
||||
calories: 305,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Gingerbread',
|
||||
calories: 356,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VTable
|
||||
height="250"
|
||||
class="text-no-wrap"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Desserts(100g Servings)
|
||||
</th>
|
||||
<th>
|
||||
calories
|
||||
</th>
|
||||
<th>
|
||||
Fat(g)
|
||||
</th>
|
||||
<th>
|
||||
Carbs(g)
|
||||
</th>
|
||||
<th>
|
||||
protein(g)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="item in desserts"
|
||||
:key="item.dessert"
|
||||
>
|
||||
<td>
|
||||
{{ item.dessert }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.calories }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.fat }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.carbs }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.protein }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
</template>
|
||||
` }
|
||||
|
||||
export const theme = { ts: `<script setup lang="ts">
|
||||
const desserts = [
|
||||
{
|
||||
dessert: 'Frozen Yogurt',
|
||||
calories: 159,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Ice cream sandwich',
|
||||
calories: 237,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Eclair',
|
||||
calories: 262,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Cupcake',
|
||||
calories: 305,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Gingerbread',
|
||||
calories: 356,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VTable
|
||||
|
||||
theme="dark"
|
||||
class="text-no-wrap rounded-0"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Desserts(100g Servings)
|
||||
</th>
|
||||
<th>
|
||||
calories
|
||||
</th>
|
||||
<th>
|
||||
Fat(g)
|
||||
</th>
|
||||
<th>
|
||||
Carbs(g)
|
||||
</th>
|
||||
<th>
|
||||
protein(g)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="item in desserts"
|
||||
:key="item.dessert"
|
||||
>
|
||||
<td>
|
||||
{{ item.dessert }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.calories }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.fat }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.carbs }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.protein }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
</template>
|
||||
`, js: `<script setup>
|
||||
const desserts = [
|
||||
{
|
||||
dessert: 'Frozen Yogurt',
|
||||
calories: 159,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Ice cream sandwich',
|
||||
calories: 237,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Eclair',
|
||||
calories: 262,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Cupcake',
|
||||
calories: 305,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
{
|
||||
dessert: 'Gingerbread',
|
||||
calories: 356,
|
||||
fat: 6,
|
||||
carbs: 24,
|
||||
protein: 4,
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VTable
|
||||
|
||||
theme="dark"
|
||||
class="text-no-wrap rounded-0"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Desserts(100g Servings)
|
||||
</th>
|
||||
<th>
|
||||
calories
|
||||
</th>
|
||||
<th>
|
||||
Fat(g)
|
||||
</th>
|
||||
<th>
|
||||
Carbs(g)
|
||||
</th>
|
||||
<th>
|
||||
protein(g)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="item in desserts"
|
||||
:key="item.dessert"
|
||||
>
|
||||
<td>
|
||||
{{ item.dessert }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.calories }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.fat }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.carbs }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.protein }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
</template>
|
||||
` }
|
||||
|
||||
Reference in New Issue
Block a user