feat: reusable ag grid
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
|
||||
<v-card-text class="py-6">
|
||||
<div class="text-body-1 mb-4">
|
||||
Are you sure you want to delete this user?
|
||||
Are you sure you want to delete this record?
|
||||
</div>
|
||||
|
||||
<v-card
|
||||
@@ -24,15 +24,14 @@
|
||||
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
|
||||
v-for="(val, key) in previewFields"
|
||||
:key="key"
|
||||
class="d-flex align-center"
|
||||
>
|
||||
<VIcon icon="tabler-dot" size="small" class="me-2" />
|
||||
<strong class="me-2">{{ key }}:</strong>
|
||||
{{ formatValue(val) }}
|
||||
</div>
|
||||
</div>
|
||||
</v-card>
|
||||
@@ -85,7 +84,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@@ -97,6 +96,10 @@ defineProps({
|
||||
isMobile: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
columns: {
|
||||
type: [Array, Object],
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
@@ -118,6 +121,26 @@ const handleConfirm = () => {
|
||||
emit('update:model-value', false)
|
||||
emit('confirm')
|
||||
}
|
||||
|
||||
const previewFields = computed(() => {
|
||||
const cols = Array.isArray(props.columns) ? props.columns : (props.columns?.value || [])
|
||||
const prefer = ['title','name','fullName','todo','email','id','userId']
|
||||
const keys = []
|
||||
for (const p of prefer) {
|
||||
if (props.selectedRowData && props.selectedRowData[p] !== undefined) keys.push(p)
|
||||
}
|
||||
for (const c of cols) {
|
||||
if (c.field && !keys.includes(c.field) && c.field !== 'action' && keys.length < 4) keys.push(c.field)
|
||||
}
|
||||
const map = {}
|
||||
keys.forEach(k => { map[k] = props.selectedRowData?.[k] })
|
||||
return map
|
||||
})
|
||||
|
||||
const formatValue = (v) => {
|
||||
if (Array.isArray(v) || typeof v === 'object') return JSON.stringify(v)
|
||||
return v ?? 'N/A'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
const props = defineProps({
|
||||
modelValue: Boolean,
|
||||
selectedRowData: Object,
|
||||
isMobile: Boolean
|
||||
isMobile: Boolean,
|
||||
columns: {
|
||||
type: [Array, Object],
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'save-user'])
|
||||
@@ -111,6 +115,11 @@ const getFieldIcon = (field) => {
|
||||
}
|
||||
return iconMap[field] || 'tabler-info-circle'
|
||||
}
|
||||
|
||||
const visibleColumns = computed(() => {
|
||||
const list = Array.isArray(props.columns) ? props.columns : (props.columns?.value || [])
|
||||
return list.filter(c => c.field && c.field !== 'action')
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -179,7 +188,11 @@ const getFieldIcon = (field) => {
|
||||
<v-form @keyup.enter="handleEnterKey">
|
||||
<v-container fluid class="pa-0">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" class="mb-2">
|
||||
<v-col
|
||||
v-for="col in visibleColumns"
|
||||
:key="col.field"
|
||||
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
|
||||
@@ -192,212 +205,35 @@ const getFieldIcon = (field) => {
|
||||
border: '1px solid rgba(var(--v-theme-primary), 0.3)'
|
||||
}"
|
||||
>
|
||||
<v-icon :icon="getFieldIcon('name')" color="primary" />
|
||||
<v-icon :icon="getFieldIcon(col.field)" 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>
|
||||
<v-card-subtitle class="pa-0 text-caption text-medium-emphasis">{{ col.headerName || col.field }}</v-card-subtitle>
|
||||
<template v-if="!isEditMode">
|
||||
<v-card-title class="pa-0 text-body-1 text-high-emphasis">
|
||||
{{ Array.isArray(selectedRowData[col.field]) || typeof selectedRowData[col.field] === 'object' ? JSON.stringify(selectedRowData[col.field]) : (selectedRowData[col.field] ?? 'N/A') }}
|
||||
</v-card-title>
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-text-field
|
||||
v-if="col.editable !== false && col.field !== 'status'"
|
||||
v-model="editedData[col.field]"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
class="mt-1"
|
||||
:type="col.field.toLowerCase().includes('date') ? 'date' : (typeof selectedRowData[col.field] === 'number' ? 'number' : 'text')"
|
||||
/>
|
||||
<v-select
|
||||
v-else-if="col.field === 'status'"
|
||||
v-model="editedData.status"
|
||||
:items="statusOptions"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
class="mt-1"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</v-card>
|
||||
|
||||
Reference in New Issue
Block a user