forked from verdnatura/salix-front
Merge pull request '4183- Smart Card component' (#18) from 4183-smart-list into dev
Reviewed-on: verdnatura/salix-front#18
This commit is contained in:
commit
10157230d6
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
|
@ -18,10 +18,10 @@
|
||||||
"test:e2e:ci": "cross-env E2E_TEST=true start-test \"quasar dev\" http-get://localhost:8080 \"cypress run\""
|
"test:e2e:ci": "cross-env E2E_TEST=true start-test \"quasar dev\" http-get://localhost:8080 \"cypress run\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@quasar/extras": "^1.0.0",
|
"@quasar/extras": "^1.14.0",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"quasar": "^2.6.0",
|
"quasar": "^2.7.1",
|
||||||
"vue": "^3.0.0",
|
"vue": "^3.0.0",
|
||||||
"vue-i18n": "^9.0.0",
|
"vue-i18n": "^9.0.0",
|
||||||
"vue-router": "^4.0.0"
|
"vue-router": "^4.0.0"
|
||||||
|
@ -29,17 +29,17 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/eslint-parser": "^7.13.14",
|
"@babel/eslint-parser": "^7.13.14",
|
||||||
"@intlify/vue-i18n-loader": "^4.1.0",
|
"@intlify/vue-i18n-loader": "^4.1.0",
|
||||||
"@quasar/app-webpack": "^3.0.0",
|
"@quasar/app-webpack": "^3.5.3",
|
||||||
"@quasar/quasar-app-extension-testing-e2e-cypress": "^4.0.1",
|
"@quasar/quasar-app-extension-testing-e2e-cypress": "^4.1.2",
|
||||||
"@quasar/quasar-app-extension-testing-unit-jest": "^3.0.0-alpha.9",
|
"@quasar/quasar-app-extension-testing-unit-jest": "^3.0.0-alpha.10",
|
||||||
"eslint": "^8.10.0",
|
"eslint": "^8.10.0",
|
||||||
"eslint-config-prettier": "^8.1.0",
|
"eslint-config-prettier": "^8.1.0",
|
||||||
"eslint-plugin-cypress": "^2.11.3",
|
"eslint-plugin-cypress": "^2.11.3",
|
||||||
"eslint-plugin-jest": "^25.2.2",
|
"eslint-plugin-jest": "^25.2.2",
|
||||||
"eslint-plugin-vue": "^8.5.0",
|
"eslint-plugin-vue": "^8.5.0",
|
||||||
"eslint-webpack-plugin": "^3.1.1",
|
"eslint-webpack-plugin": "^3.1.1",
|
||||||
"prettier": "^2.5.1",
|
"jest-junit": "^13.0.0",
|
||||||
"jest-junit": "^13.0.0"
|
"prettier": "^2.5.1"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"last 10 Chrome versions",
|
"last 10 Chrome versions",
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
<script setup>
|
||||||
|
import axios from 'axios';
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
autoLoad: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
sortBy: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
rowsPerPage: {
|
||||||
|
type: Number,
|
||||||
|
default: 10,
|
||||||
|
},
|
||||||
|
offset: {
|
||||||
|
type: Number,
|
||||||
|
default: 500,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
defineEmits(['onNavigate']);
|
||||||
|
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const hasMoreData = ref(false);
|
||||||
|
const pagination = ref({
|
||||||
|
sortBy: $props.sortBy,
|
||||||
|
rowsPerPage: $props.rowsPerPage,
|
||||||
|
page: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const rows = ref([]);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if ($props.autoLoad) fetch();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function fetch() {
|
||||||
|
const { page, rowsPerPage, sortBy, descending } = pagination.value;
|
||||||
|
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
const filter = {
|
||||||
|
limit: rowsPerPage,
|
||||||
|
skip: rowsPerPage * (page - 1),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (sortBy) filter.order = sortBy;
|
||||||
|
|
||||||
|
const { data } = await axios.get($props.url, {
|
||||||
|
params: { filter },
|
||||||
|
});
|
||||||
|
|
||||||
|
hasMoreData.value = data.length === rowsPerPage;
|
||||||
|
|
||||||
|
for (const row of data) rows.value.push(row);
|
||||||
|
|
||||||
|
pagination.value.rowsNumber = rows.value.length;
|
||||||
|
pagination.value.page = page;
|
||||||
|
pagination.value.rowsPerPage = rowsPerPage;
|
||||||
|
pagination.value.sortBy = sortBy;
|
||||||
|
pagination.value.descending = descending;
|
||||||
|
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onLoad(...params) {
|
||||||
|
const done = params[1];
|
||||||
|
if (rows.value.length === 0) return done(false);
|
||||||
|
|
||||||
|
pagination.value.page = pagination.value.page + 1;
|
||||||
|
|
||||||
|
await fetch();
|
||||||
|
|
||||||
|
const endOfPages = !hasMoreData.value;
|
||||||
|
done(endOfPages);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-infinite-scroll @load="onLoad" :offset="offset" class="column items-center">
|
||||||
|
<div class="card-list q-gutter-y-md">
|
||||||
|
<q-card class="card" v-for="row of rows" :key="row.id">
|
||||||
|
<q-item v-ripple class="q-pa-none items-start cursor-pointer q-hoverable">
|
||||||
|
<q-item-section class="q-pa-md" @click="$emit('onNavigate', row.id)">
|
||||||
|
<slot name="header" :row="row">
|
||||||
|
<div class="text-h6">{{ row.name }}</div>
|
||||||
|
<q-item-label caption>#{{ row.id }}</q-item-label>
|
||||||
|
</slot>
|
||||||
|
<slot name="labels" :row="row"></slot>
|
||||||
|
</q-item-section>
|
||||||
|
<q-separator vertical />
|
||||||
|
<q-card-actions vertical class="justify-between">
|
||||||
|
<slot name="actions" :row="row">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
color="orange"
|
||||||
|
icon="arrow_circle_right"
|
||||||
|
@click="$emit('onNavigate', row.id)"
|
||||||
|
>
|
||||||
|
<q-tooltip>{{ t('components.smartCard.openCard') }}</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
<q-btn flat round color="grey-7" icon="preview">
|
||||||
|
<q-tooltip>{{ t('components.smartCard.openSummary') }}</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</slot>
|
||||||
|
</q-card-actions>
|
||||||
|
</q-item>
|
||||||
|
</q-card>
|
||||||
|
<div v-if="!rows.length && !isLoading" class="info-row q-pa-md text-center">
|
||||||
|
<h5>
|
||||||
|
{{ t('components.smartCard.noData') }}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div v-if="isLoading" class="info-row q-pa-md text-center">
|
||||||
|
<q-spinner color="orange" size="md" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-infinite-scroll>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.card-list {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 60em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-row {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,151 @@
|
||||||
|
import { jest, describe, expect, it, beforeAll } from '@jest/globals';
|
||||||
|
import { createWrapper, axios } from 'app/tests/jest/jestHelpers';
|
||||||
|
import SmartCard from '../SmartCard.vue';
|
||||||
|
|
||||||
|
const mockPush = jest.fn();
|
||||||
|
|
||||||
|
jest.mock('vue-router', () => ({
|
||||||
|
useRouter: () => ({
|
||||||
|
push: mockPush,
|
||||||
|
currentRoute: { value: 'myCurrentRoute' }
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('SmartCard', () => {
|
||||||
|
const expectedUrl = '/api/customers';
|
||||||
|
let vm;
|
||||||
|
beforeAll(() => {
|
||||||
|
const options = {
|
||||||
|
attrs: {
|
||||||
|
url: expectedUrl,
|
||||||
|
sortBy: 'id DESC',
|
||||||
|
rowsPerPage: 3
|
||||||
|
}
|
||||||
|
};
|
||||||
|
vm = createWrapper(SmartCard, options).vm;
|
||||||
|
|
||||||
|
jest.spyOn(axios, 'get').mockResolvedValue({
|
||||||
|
data: [
|
||||||
|
{ id: 1, name: 'Tony Stark' },
|
||||||
|
{ id: 2, name: 'Jessica Jones' },
|
||||||
|
{ id: 3, name: 'Bruce Wayne' },
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vm.rows = [];
|
||||||
|
vm.pagination.page = 1;
|
||||||
|
vm.hasMoreData = true;
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('fetch()', () => {
|
||||||
|
it('should call to the fetch() method and set the data on the rows property', async () => {
|
||||||
|
const expectedOptions = {
|
||||||
|
params: {
|
||||||
|
filter: {
|
||||||
|
order: 'id DESC',
|
||||||
|
limit: 3,
|
||||||
|
skip: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await vm.fetch();
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(expectedUrl, expectedOptions);
|
||||||
|
expect(vm.rows.length).toEqual(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call to the fetch() method and then call it again to paginate', async () => {
|
||||||
|
const expectedOptions = {
|
||||||
|
params: {
|
||||||
|
filter: {
|
||||||
|
order: 'id DESC',
|
||||||
|
limit: 3,
|
||||||
|
skip: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await vm.fetch();
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(expectedUrl, expectedOptions);
|
||||||
|
expect(vm.rows.length).toEqual(3);
|
||||||
|
|
||||||
|
const expectedOptionsPaginated = {
|
||||||
|
params: {
|
||||||
|
filter: {
|
||||||
|
order: 'id DESC',
|
||||||
|
limit: 3,
|
||||||
|
skip: 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.pagination.page = 2;
|
||||||
|
|
||||||
|
await vm.fetch();
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(expectedUrl, expectedOptionsPaginated);
|
||||||
|
expect(vm.rows.length).toEqual(6);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onLoad()', () => {
|
||||||
|
it('should call to the done() callback and not increment the pagination', async () => {
|
||||||
|
const index = 1;
|
||||||
|
const done = jest.fn();
|
||||||
|
|
||||||
|
await vm.onLoad(index, done);
|
||||||
|
|
||||||
|
expect(vm.pagination.page).toEqual(1);
|
||||||
|
expect(done).toHaveBeenCalledWith(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should increment the pagination and then call to the done() callback', async () => {
|
||||||
|
vm.rows = [
|
||||||
|
{ id: 1, name: 'Tony Stark' },
|
||||||
|
{ id: 2, name: 'Jessica Jones' },
|
||||||
|
{ id: 3, name: 'Bruce Wayne' },
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(vm.pagination.page).toEqual(1);
|
||||||
|
|
||||||
|
const index = 1;
|
||||||
|
const done = jest.fn();
|
||||||
|
|
||||||
|
await vm.onLoad(index, done);
|
||||||
|
|
||||||
|
expect(vm.pagination.page).toEqual(2);
|
||||||
|
expect(done).toHaveBeenCalledWith(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call to the done() callback with true as argument to finish pagination', async () => {
|
||||||
|
jest.spyOn(axios, 'get').mockResolvedValue({
|
||||||
|
data: [
|
||||||
|
{ id: 1, name: 'Tony Stark' },
|
||||||
|
{ id: 2, name: 'Jessica Jones' }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
vm.rows = [
|
||||||
|
{ id: 1, name: 'Tony Stark' },
|
||||||
|
{ id: 2, name: 'Jessica Jones' },
|
||||||
|
{ id: 3, name: 'Bruce Wayne' },
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(vm.pagination.page).toEqual(1);
|
||||||
|
|
||||||
|
const index = 1;
|
||||||
|
const done = jest.fn();
|
||||||
|
|
||||||
|
vm.hasMoreData = false;
|
||||||
|
|
||||||
|
await vm.onLoad(index, done);
|
||||||
|
|
||||||
|
expect(vm.pagination.page).toEqual(2);
|
||||||
|
expect(done).toHaveBeenCalledWith(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -40,6 +40,12 @@ export default {
|
||||||
list: 'List',
|
list: 'List',
|
||||||
createCustomer: 'Create customer',
|
createCustomer: 'Create customer',
|
||||||
basicData: 'Basic Data'
|
basicData: 'Basic Data'
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
phone: 'Phone',
|
||||||
|
email: 'Email',
|
||||||
|
customerOrders: 'Display customer orders',
|
||||||
|
moreOptions: 'More options'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ticket: {
|
ticket: {
|
||||||
|
@ -56,5 +62,10 @@ export default {
|
||||||
settings: 'Settings',
|
settings: 'Settings',
|
||||||
logOut: 'Log Out',
|
logOut: 'Log Out',
|
||||||
},
|
},
|
||||||
|
smartCard: {
|
||||||
|
noData: 'No data to display',
|
||||||
|
openCard: 'View card',
|
||||||
|
openSummary: 'Open summary'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,7 +11,7 @@ export default {
|
||||||
favoriteModules: 'Módulos favoritos',
|
favoriteModules: 'Módulos favoritos',
|
||||||
theme: 'Tema',
|
theme: 'Tema',
|
||||||
logOut: 'Cerrar sesión',
|
logOut: 'Cerrar sesión',
|
||||||
dataSaved: 'Datos guardados',
|
dataSaved: 'Datos guardados'
|
||||||
},
|
},
|
||||||
moduleIndex: {
|
moduleIndex: {
|
||||||
allModules: 'Todos los módulos'
|
allModules: 'Todos los módulos'
|
||||||
|
@ -40,6 +40,12 @@ export default {
|
||||||
list: 'Listado',
|
list: 'Listado',
|
||||||
createCustomer: 'Crear cliente',
|
createCustomer: 'Crear cliente',
|
||||||
basicData: 'Datos básicos'
|
basicData: 'Datos básicos'
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
phone: 'Teléfono',
|
||||||
|
email: 'Email',
|
||||||
|
customerOrders: 'Mostrar órdenes del cliente',
|
||||||
|
moreOptions: 'Más opciones'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ticket: {
|
ticket: {
|
||||||
|
@ -56,5 +62,10 @@ export default {
|
||||||
settings: 'Configuración',
|
settings: 'Configuración',
|
||||||
logOut: 'Cerrar sesión',
|
logOut: 'Cerrar sesión',
|
||||||
},
|
},
|
||||||
|
smartCard: {
|
||||||
|
noData: 'Sin datos que mostrar',
|
||||||
|
openCard: 'Ver ficha',
|
||||||
|
openSummary: 'Abrir detalles'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<q-card>Basic Data</q-card>
|
<q-page class="q-pa-md">
|
||||||
|
<q-card class="q-pa-md">Basic Data</q-card>
|
||||||
|
</q-page>
|
||||||
</template>
|
</template>
|
|
@ -1,24 +1,138 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue';
|
import { computed, ref, onMounted } from 'vue';
|
||||||
import { useState } from 'src/composables/useState';
|
import { useState } from 'src/composables/useState';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
const state = useState();
|
const state = useState();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await fetch();
|
||||||
|
});
|
||||||
|
|
||||||
const entityId = computed(function () {
|
const entityId = computed(function () {
|
||||||
return router.currentRoute.value.params.id;
|
return router.currentRoute.value.params.id;
|
||||||
});
|
});
|
||||||
|
const customer = ref({});
|
||||||
|
|
||||||
|
async function fetch() {
|
||||||
|
const { data } = await axios.get(`/api/Clients/${entityId.value}`);
|
||||||
|
|
||||||
|
if (data) customer.value = data;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<q-drawer v-model="state.drawer.value" show-if-above :width="200" :breakpoint="500">
|
<q-drawer v-model="state.drawer.value" show-if-above :width="256" :breakpoint="500">
|
||||||
<q-scroll-area class="fit text-grey-8">
|
<q-scroll-area class="fit text-grey-8 descriptor">
|
||||||
<router-link :to="{ path: '/customer/list' }">
|
<div class="header bg-orange q-pa-sm">
|
||||||
<q-icon name="arrow_back" size="md" color="primary" />
|
<router-link :to="{ path: '/customer/list' }">
|
||||||
</router-link>
|
<q-btn round flat dense size="md" icon="view_list" color="white">
|
||||||
<div>Customer ID: {{ entityId }}</div>
|
<q-tooltip>Customer list</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</router-link>
|
||||||
|
<router-link :to="{ path: '/customer/list' }">
|
||||||
|
<q-btn round flat dense size="md" icon="launch" color="white">
|
||||||
|
<q-tooltip>Customer preview</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<q-btn round flat dense size="md" icon="more_vert" color="white">
|
||||||
|
<q-tooltip>More options</q-tooltip>
|
||||||
|
<q-menu>
|
||||||
|
<q-list>
|
||||||
|
<q-item clickable v-ripple>Option 1</q-item>
|
||||||
|
<q-item clickable v-ripple>Option 2</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-menu>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h5>{{ customer.name }}</h5>
|
||||||
|
|
||||||
|
<q-list>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>Customer ID</q-item-label>
|
||||||
|
<q-item-label>#{{ customer.id }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label caption>Email</q-item-label>
|
||||||
|
<q-item-label>{{ customer.email }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
|
||||||
|
<q-card-actions>
|
||||||
|
<q-btn size="md" icon="vn:ticket" color="orange">
|
||||||
|
<q-tooltip>Ticket list</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
<q-btn size="md" icon="vn:invoice-out" color="orange">
|
||||||
|
<q-tooltip>Invoice Out list</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
<q-btn size="md" icon="vn:basketadd" color="orange">
|
||||||
|
<q-tooltip>Order list</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
<q-btn size="md" icon="face" color="orange">
|
||||||
|
<q-tooltip>View user</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
<q-btn size="md" icon="expand_more" color="orange">
|
||||||
|
<q-tooltip>More options</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</q-card-actions>
|
||||||
|
|
||||||
|
<q-separator />
|
||||||
|
|
||||||
|
<q-list>
|
||||||
|
<q-item :to="{ name: 'CustomerBasicData' }" clickable v-ripple active-class="text-orange">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="person" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>Basic data</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable v-ripple>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="notes" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>Notes</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-expansion-item icon="more" label="More options" expand-icon-toggle expand-separator>
|
||||||
|
<q-list>
|
||||||
|
<q-item clickable v-ripple>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="person" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>Option</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-expansion-item>
|
||||||
|
</q-list>
|
||||||
</q-scroll-area>
|
</q-scroll-area>
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
<q-page-container>
|
<q-page-container>
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
</q-page-container>
|
</q-page-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.descriptor {
|
||||||
|
h5 {
|
||||||
|
margin: 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-card__actions {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,26 +1,10 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
const router = useRouter();
|
import SmartCard from 'src/components/SmartCard.vue';
|
||||||
|
|
||||||
const customers = [
|
const router = useRouter();
|
||||||
{
|
const { t } = useI18n();
|
||||||
id: 1101,
|
|
||||||
name: 'Bruce Wayne',
|
|
||||||
username: 'batman',
|
|
||||||
email: 'batman@gotham',
|
|
||||||
phone: '555-555-5555',
|
|
||||||
expanded: ref(false),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 1102,
|
|
||||||
name: 'James Gordon',
|
|
||||||
username: 'jamesgordon',
|
|
||||||
email: 'jamesgordon@gotham',
|
|
||||||
phone: '555-555-1111',
|
|
||||||
expanded: ref(false),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
function navigate(id) {
|
function navigate(id) {
|
||||||
router.push({ path: `/customer/${id}` });
|
router.push({ path: `/customer/${id}` });
|
||||||
|
@ -29,82 +13,54 @@ function navigate(id) {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-page class="q-pa-md">
|
<q-page class="q-pa-md">
|
||||||
<div class="column items-center q-gutter-y-md">
|
<smart-card url="/api/Clients" sort-by="id DESC" @on-navigate="navigate" auto-load>
|
||||||
<q-card v-for="customer in customers" :key="customer.id" class="card">
|
<template #labels="{ row }">
|
||||||
<!-- v-ripple :to="{ path: '/dashboard' }" -->
|
<q-list>
|
||||||
<q-item v-ripple class="q-pa-none items-start cursor-pointer q-hoverable">
|
<q-item class="q-pa-none">
|
||||||
<q-item-section class="q-pa-md">
|
<q-item-section>
|
||||||
<div class="text-h6">{{ customer.name }}</div>
|
<q-item-label caption>{{ t('customer.list.email') }}</q-item-label>
|
||||||
<q-item-label caption>@{{ customer.username }}</q-item-label>
|
<q-item-label>{{ row.email }}</q-item-label>
|
||||||
<div class="q-mt-md">
|
</q-item-section>
|
||||||
<q-list>
|
</q-item>
|
||||||
<q-item class="q-pa-none">
|
<q-item class="q-pa-none">
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label caption>Email</q-item-label>
|
<q-item-label caption>{{ t('customer.list.phone') }}</q-item-label>
|
||||||
<q-item-label>{{ customer.email }}</q-item-label>
|
<q-item-label>{{ row.phone }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item class="q-pa-none">
|
</q-list>
|
||||||
<q-item-section>
|
</template>
|
||||||
<q-item-label caption>Phone</q-item-label>
|
<template #actions="{ row }">
|
||||||
<q-item-label>{{ customer.phone }}</q-item-label>
|
<q-btn color="grey-7" round flat icon="more_vert">
|
||||||
</q-item-section>
|
<q-tooltip>{{ t('customer.list.moreOptions') }}</q-tooltip>
|
||||||
</q-item>
|
<q-menu cover auto-close>
|
||||||
</q-list>
|
<q-list>
|
||||||
</div>
|
<q-item clickable>
|
||||||
</q-item-section>
|
<q-item-section avatar>
|
||||||
<q-btn color="grey-7" round flat icon="more_vert">
|
<q-icon name="add" />
|
||||||
<q-menu cover auto-close>
|
</q-item-section>
|
||||||
<q-list>
|
<q-item-section>Add a note</q-item-section>
|
||||||
<q-item clickable>
|
</q-item>
|
||||||
<q-item-section>Action 1</q-item-section>
|
<q-item clickable>
|
||||||
</q-item>
|
<q-item-section avatar>
|
||||||
<q-item clickable>
|
<q-icon name="history" />
|
||||||
<q-item-section>Action 2</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
<q-item-section>Display customer history</q-item-section>
|
||||||
</q-list>
|
</q-item>
|
||||||
</q-menu>
|
</q-list>
|
||||||
</q-btn>
|
</q-menu>
|
||||||
<q-separator vertical />
|
</q-btn>
|
||||||
<q-card-actions vertical class="justify-between">
|
|
||||||
<q-btn flat round color="orange" icon="arrow_circle_right" @click="navigate(customer.id)" />
|
<q-btn flat round color="orange" icon="arrow_circle_right" @click="navigate(row.id)">
|
||||||
<q-btn flat round color="accent" icon="preview" />
|
<q-tooltip>{{ t('components.smartCard.openCard') }}</q-tooltip>
|
||||||
<q-btn flat round color="accent" icon="vn:ticket" />
|
</q-btn>
|
||||||
<q-card-actions>
|
<q-btn flat round color="grey-7" icon="preview">
|
||||||
<q-btn
|
<q-tooltip>{{ t('components.smartCard.openSummary') }}</q-tooltip>
|
||||||
color="grey"
|
</q-btn>
|
||||||
round
|
<q-btn flat round color="grey-7" icon="vn:ticket">
|
||||||
flat
|
<q-tooltip>{{ t('customer.list.customerOrders') }}</q-tooltip>
|
||||||
dense
|
</q-btn>
|
||||||
:icon="customer.expanded.value ? 'keyboard_arrow_up' : 'keyboard_arrow_down'"
|
</template>
|
||||||
@click="customer.expanded.value = !customer.expanded.value"
|
</smart-card>
|
||||||
/>
|
|
||||||
</q-card-actions>
|
|
||||||
</q-card-actions>
|
|
||||||
</q-item>
|
|
||||||
<q-slide-transition>
|
|
||||||
<div v-show="customer.expanded.value">
|
|
||||||
<q-separator />
|
|
||||||
<q-card-section class="text-subitle2">
|
|
||||||
<q-list>
|
|
||||||
<q-item clickable>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>Address</q-item-label>
|
|
||||||
<q-item-label caption>Avenue 11</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
</q-card-section>
|
|
||||||
</div>
|
|
||||||
</q-slide-transition>
|
|
||||||
</q-card>
|
|
||||||
</div>
|
|
||||||
</q-page>
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.card {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 60em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -11,12 +11,17 @@ installQuasarPlugin({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export function createWrapper(component) {
|
export function createWrapper(component, options) {
|
||||||
const wrapper = mount(component, {
|
const mountOptions = {
|
||||||
global: {
|
global: {
|
||||||
plugins: [i18n]
|
plugins: [i18n]
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (options instanceof Object)
|
||||||
|
Object.assign(mountOptions, options)
|
||||||
|
|
||||||
|
const wrapper = mount(component, mountOptions);
|
||||||
const vm = wrapper.vm;
|
const vm = wrapper.vm;
|
||||||
|
|
||||||
return { vm, wrapper };
|
return { vm, wrapper };
|
||||||
|
|
Loading…
Reference in New Issue