-
+
{{ t('globals.collapseMenu') }}
@@ -131,28 +627,390 @@ function actionColor(action) {
-
+
+
+
+ selectFilter('search')"
+ @focusout="() => selectFilter('search')"
+ @clear="() => selectFilter('search')"
+ >
+
+
+ {{ t('tooltips.search') }}
+
+
+
+
+
+ selectFilter('action')"
+ hide-selected
+ />
+
+
+
+
+ {{ t(`Users.${label}`) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ opt.name }}
+ {{ opt.nickname }}
+
+
+
+
+
+
+
+
+
+
+ {{
+ t('tooltips.changes')
+ }}
+
+
+
+
+
+
+
+
+ evt.target.blur()"
+ @clear="selectFilter('date', 'to')"
+ v-model="dateFrom"
+ clearable
+ clear-icon="close"
+ />
+
+
+ evt.target.blur()"
+ @clear="selectFilter('date', 'from')"
+ v-model="dateTo"
+ clearable
+ clear-icon="close"
+ />
+
+
+
+ {
+ dateFromDialog = false;
+ dateFrom = date.formatDate(value, 'DD-MM-YYYY');
+ selectFilter('date', 'from');
+ }
+ "
+ />
+
+
+ {
+ dateToDialog = false;
+ dateTo = date.formatDate(value, 'DD-MM-YYYY');
+ selectFilter('date', 'to');
+ }
+ "
+ />
+
+
+
+
-
en:
+ to: To
+ pointRecord: View record at this point in time
+ recordChanges: show all record changes
+ tooltips:
+ search: Search by id or concept
+ changes: Search by changes
actions:
- insert: Creates
- update: Updates
- delete: Deletes
- models:
- Claim: Claim
- ClaimDms: Document
- ClaimBeginning: Claimed Sales
- ClaimObservation: Observation
+ Creates: Creates
+ Edits: Edits
+ Deletes: Deletes
+ Accesses: Accesses
+ Users:
+ User: Usuario
+ All: Todo
+ System: Sistema
properties:
id: ID
claimFk: Claim ID
@@ -172,6 +1030,12 @@ en:
responsibility: Responsibility
packages: Packages
es:
+ to: Hasta
+ pointRecord: Ver el registro en este punto
+ recordChanges: Mostrar todos los cambios realizados en el registro
+ tooltips:
+ search: Buscar por identificador o concepto
+ changes: Buscar por cambios. Los atributos deben buscarse por su nombre interno, para obtenerlo situar el cursor sobre el atributo.
Audit logs: Registros de auditoría
Property: Propiedad
Before: Antes
@@ -179,14 +1043,14 @@ es:
Yes: Si
Nothing: Nada
actions:
- insert: Crea
- update: Actualiza
- delete: Elimina
- models:
- Claim: Reclamación
- ClaimDms: Documento
- ClaimBeginning: Línea reclamada
- ClaimObservation: Observación
+ Creates: Crea
+ Edits: Modifica
+ Deletes: Elimina
+ Accesses: Accede
+ Users:
+ User: Usuario
+ All: Todo
+ System: Sistema
properties:
id: ID
claimFk: ID reclamación
diff --git a/src/composables/useColor.js b/src/composables/useColor.js
new file mode 100644
index 000000000..b325e985f
--- /dev/null
+++ b/src/composables/useColor.js
@@ -0,0 +1,35 @@
+export function djb2a(string) {
+ let hash = 5381;
+ for (let i = 0; i < string.length; i++)
+ hash = ((hash << 5) + hash) ^ string.charCodeAt(i);
+ return hash >>> 0;
+}
+
+export function useColor(value) {
+ return '#' + colors[djb2a(value || '') % colors.length];
+}
+
+const colors = [
+ 'b5b941', // Yellow
+ 'ae9681', // Peach
+ 'd78767', // Salmon
+ 'cc7000', // Orange bright
+ 'e2553d', // Coral
+ '8B0000', // Red dark
+ 'de4362', // Red crimson
+ 'FF1493', // Ping intense
+ 'be39a2', // Pink light
+ 'b754cf', // Purple middle
+ 'a87ba8', // Pink
+ '8a69cd', // Blue lavender
+ 'ab20ab', // Purple dark
+ '00b5b8', // Turquoise
+ '1fa8a1', // Green ocean
+ '5681cf', // Blue steel
+ '3399fe', // Blue sky
+ '6d9c3e', // Green chartreuse
+ '51bb51', // Green lime
+ '518b8b', // Gray board
+ '7e7e7e', // Gray
+ '5d5d5d', // Gray dark
+];
diff --git a/src/composables/useValidator.js b/src/composables/useValidator.js
index bc48332a2..3f9f00367 100644
--- a/src/composables/useValidator.js
+++ b/src/composables/useValidator.js
@@ -1,19 +1,12 @@
-import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
-import axios from 'axios';
import validator from 'validator';
-
-const models = ref(null);
+import { useValidationsStore } from 'src/stores/useValidationsStore';
export function useValidator() {
- if (!models.value) fetch();
-
- function fetch() {
- axios.get('Schemas/ModelInfo').then((response) => (models.value = response.data));
- }
+ const models = useValidationsStore().validations;
function validate(propertyRule) {
- const modelInfo = models.value;
+ const modelInfo = models;
if (!modelInfo || !propertyRule) return;
const rule = propertyRule.split('.');
@@ -75,5 +68,6 @@ export function useValidator() {
return {
validate,
+ models,
};
}
diff --git a/src/filters/index.js b/src/filters/index.js
index 158ce1009..b0c441641 100644
--- a/src/filters/index.js
+++ b/src/filters/index.js
@@ -2,6 +2,7 @@ import toLowerCase from './toLowerCase';
import toDate from './toDate';
import toDateString from './toDateString';
import toDateHour from './toDateHour';
+import toRelativeDate from './toRelativeDate';
import toCurrency from './toCurrency';
import toPercentage from './toPercentage';
import toLowerCamel from './toLowerCamel';
@@ -13,6 +14,7 @@ export {
toDate,
toDateString,
toDateHour,
+ toRelativeDate,
toCurrency,
toPercentage,
dashIfEmpty,
diff --git a/src/filters/toRelativeDate.js b/src/filters/toRelativeDate.js
new file mode 100644
index 000000000..76e67dbea
--- /dev/null
+++ b/src/filters/toRelativeDate.js
@@ -0,0 +1,32 @@
+import { useI18n } from 'vue-i18n';
+
+export default function formatDate(dateVal) {
+ const { t } = useI18n();
+ const today = new Date();
+ if (dateVal == null) return '';
+
+ const date = new Date(dateVal);
+ const dateZeroTime = new Date(dateVal);
+ dateZeroTime.setHours(0, 0, 0, 0);
+ const diff = Math.trunc(
+ (today.getTime() - dateZeroTime.getTime()) / (1000 * 3600 * 24)
+ );
+ let format;
+ if (diff === 0) format = t('globals.today');
+ else if (diff === 1) format = t('globals.yesterday');
+ else if (diff > 1 && diff < 7) {
+ const options = { weekday: 'short' };
+ format = date.toLocaleDateString(t('globals.dateFormat'), options);
+ } else if (today.getFullYear() === date.getFullYear()) {
+ const options = { day: 'numeric', month: 'short' };
+ format = date.toLocaleDateString(t('globals.dateFormat'), options);
+ } else {
+ const options = { year: 'numeric', month: '2-digit', day: '2-digit' };
+ format = date.toLocaleDateString(t('globals.dateFormat'), options);
+ }
+
+ // Formatear la hora en HH:mm
+ const hours = date.getHours().toString().padStart(2, '0');
+ const minutes = date.getMinutes().toString().padStart(2, '0');
+ return `${format} ${hours}:${minutes}`;
+}
diff --git a/src/i18n/en/index.js b/src/i18n/en/index.js
index 148ce743d..41bf03c5e 100644
--- a/src/i18n/en/index.js
+++ b/src/i18n/en/index.js
@@ -5,6 +5,9 @@ export default {
en: 'English',
},
language: 'Language',
+ entity: 'Entity',
+ user: 'User',
+ details: 'Details',
collapseMenu: 'Collapse left menu',
backToDashboard: 'Return to dashboard',
notifications: 'Notifications',
@@ -13,8 +16,11 @@ export default {
pinnedModules: 'Pinned modules',
darkMode: 'Dark mode',
logOut: 'Log out',
+ date: 'Date',
dataSaved: 'Data saved',
dataDeleted: 'Data deleted',
+ search: 'Search',
+ changes: 'Changes',
add: 'Add',
create: 'Create',
save: 'Save',
@@ -36,6 +42,9 @@ export default {
summary: {
basicData: 'Basic data',
},
+ today: 'Today',
+ yesterday: 'Yesterday',
+ dateFormat: 'en-GB',
microsip: 'Open in MicroSIP',
noSelectedRows: `You don't have any line selected`,
},
diff --git a/src/i18n/es/index.js b/src/i18n/es/index.js
index de9a2888b..83893ece4 100644
--- a/src/i18n/es/index.js
+++ b/src/i18n/es/index.js
@@ -5,6 +5,9 @@ export default {
en: 'Inglés',
},
language: 'Idioma',
+ entity: 'Entidad',
+ user: 'Usuario',
+ details: 'Detalles',
collapseMenu: 'Contraer menú lateral',
backToDashboard: 'Volver al tablón',
notifications: 'Notificaciones',
@@ -13,8 +16,11 @@ export default {
pinnedModules: 'Módulos fijados',
darkMode: 'Modo oscuro',
logOut: 'Cerrar sesión',
+ date: 'Fecha',
dataSaved: 'Datos guardados',
dataDeleted: 'Datos eliminados',
+ search: 'Buscar',
+ changes: 'Cambios',
add: 'Añadir',
create: 'Crear',
save: 'Guardar',
@@ -36,6 +42,9 @@ export default {
summary: {
basicData: 'Datos básicos',
},
+ today: 'Hoy',
+ yesterday: 'Ayer',
+ dateFormat: 'es-ES',
noSelectedRows: `No tienes ninguna línea seleccionada`,
microsip: 'Abrir en MicroSIP',
},
diff --git a/src/stores/useValidationsStore.js b/src/stores/useValidationsStore.js
new file mode 100644
index 000000000..e00812379
--- /dev/null
+++ b/src/stores/useValidationsStore.js
@@ -0,0 +1,18 @@
+import axios from 'axios';
+import { defineStore } from 'pinia';
+
+export const useValidationsStore = defineStore('validationsStore', {
+ state: () => ({
+ validations: null,
+ }),
+ actions: {
+ async fetchModels() {
+ try {
+ const { data } = await axios.get('Schemas/modelinfo');
+ this.validations = data;
+ } catch (error) {
+ console.error('Error al obtener las validaciones:', error);
+ }
+ },
+ },
+});
diff --git a/test/cypress/integration/vnLog.spec.js b/test/cypress/integration/vnLog.spec.js
new file mode 100644
index 000000000..d120b8ad4
--- /dev/null
+++ b/test/cypress/integration/vnLog.spec.js
@@ -0,0 +1,25 @@
+///
+describe('VnLog', () => {
+ const chips = [
+ ':nth-child(1) > :nth-child(1) > .q-item__label > .q-chip > .q-chip__content',
+ ':nth-child(2) > :nth-child(1) > .q-item__label > .q-chip > .q-chip__content',
+ ];
+ beforeEach(() => {
+ cy.login('developer');
+ cy.visit(`/#/claim/${1}/log`);
+ cy.openRightMenu('.timeline');
+ });
+
+ it('should filter by insert actions', () => {
+ cy.checkOption(':nth-child(7) > .q-checkbox');
+ cy.get('.q-page').click();
+ cy.validateContent(chips[0], 'Document');
+ cy.validateContent(chips[1], 'Beginning');
+ });
+
+ it('should filter by entity', () => {
+ cy.selectOption('.q-drawer--right .q-item > .q-select', 'Claim');
+ cy.get('.q-page').click();
+ cy.validateContent(chips[0], 'Claim');
+ });
+});
diff --git a/test/cypress/integration/worker/workerList.spec.js b/test/cypress/integration/worker/workerList.spec.js
index 219633263..2ab84c623 100644
--- a/test/cypress/integration/worker/workerList.spec.js
+++ b/test/cypress/integration/worker/workerList.spec.js
@@ -1,4 +1,6 @@
describe('WorkerList', () => {
+ const workerFieldNames =
+ '.card-list-body > .list-items > :nth-child(2) > .value > span';
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');
@@ -6,20 +8,14 @@ describe('WorkerList', () => {
});
it('should load workers', () => {
- cy.get('.card-list-body > .list-items > :nth-child(2) > .value > span')
- .eq(0)
- .should('have.text', 'JessicaJones');
- cy.get('.card-list-body > .list-items > :nth-child(2) > .value > span')
- .eq(1)
- .should('have.text', 'BruceBanner');
- cy.get('.card-list-body > .list-items > :nth-child(2) > .value > span')
- .eq(2)
- .should('have.text', 'CharlesXavier');
+ cy.get(workerFieldNames).eq(0).should('have.text', 'JessicaJones');
+ cy.get(workerFieldNames).eq(1).should('have.text', 'BruceBanner');
+ cy.get(workerFieldNames).eq(2).should('have.text', 'CharlesXavier');
});
it('should open the worker summary', () => {
- cy.get('.card-list-body .actions .q-btn:nth-child(2)').eq(1).click();
- cy.get('.summaryHeader div').should('have.text', '1109 - Bruce Banner');
+ cy.openListSummary(0);
+ cy.get('.summaryHeader div').should('have.text', '1110 - Jessica Jones');
cy.get('.summary .header').eq(0).invoke('text').should('include', 'Basic data');
cy.get('.summary .header').eq(1).should('have.text', 'User data');
});
diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js
index 0b518ca26..4dfde6e21 100755
--- a/test/cypress/support/commands.js
+++ b/test/cypress/support/commands.js
@@ -51,15 +51,14 @@ Cypress.Commands.add('getValue', (selector) => {
return cy.get(selector + '.q-checkbox__inner');
}
// Si es un QSelect
- else if ($el.find('.q-select__dropdown-icon').length) {
+ if ($el.find('.q-select__dropdown-icon').length) {
return cy.get(
selector +
'> .q-field > .q-field__inner > .q-field__control > .q-field__control-container > .q-field__native > input'
);
- } else {
- // Puedes añadir un log o lanzar un error si el elemento no es reconocido
- cy.log('Elemento no soportado');
}
+ // Puedes añadir un log o lanzar un error si el elemento no es reconocido
+ cy.log('Elemento no soportado');
});
});
@@ -70,7 +69,7 @@ Cypress.Commands.add('selectOption', (selector, option) => {
});
Cypress.Commands.add('checkOption', (selector) => {
- cy.wrap(selector).find('.q-checkbox__inner').click();
+ cy.get(selector).find('.q-checkbox__inner').click();
});
// Global buttons
@@ -158,4 +157,16 @@ Cypress.Commands.add('removeRow', (rowIndex) => {
});
});
});
+Cypress.Commands.add('openListSummary', (row) => {
+ cy.get('.card-list-body .actions .q-btn:nth-child(2)').eq(row).click();
+});
+
+Cypress.Commands.add('openRightMenu', (element) => {
+ if (element) cy.waitForElement(element);
+ cy.get('#actions-append').click();
+});
+
+Cypress.Commands.add('validateContent', (selector, expectedValue) => {
+ cy.get(selector).should('have.text', expectedValue);
+});
// registerCommands();
diff --git a/test/vitest/__tests__/components/common/VnLog.spec.js b/test/vitest/__tests__/components/common/VnLog.spec.js
index 6787b6d51..dbcb30272 100644
--- a/test/vitest/__tests__/components/common/VnLog.spec.js
+++ b/test/vitest/__tests__/components/common/VnLog.spec.js
@@ -1,75 +1,134 @@
import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest';
-import { createWrapper } from 'app/test/vitest/helper';
+import { createWrapper, axios } from 'app/test/vitest/helper';
import VnLog from 'src/components/common/VnLog.vue';
describe('VnLog', () => {
let vm;
- beforeAll(() => {
- vm = createWrapper(VnLog, {
- global: {
- stubs: ['FetchData', 'VnPaginate'],
- mocks: {
- fetch: vi.fn(),
+ const fakeLogTreeData = [
+ {
+ id: 2,
+ originFk: 1,
+ userFk: 18,
+ action: 'update',
+ changedModel: 'ClaimObservation',
+ oldInstance: {},
+ newInstance: {
+ claimFk: 1,
+ text: 'Waiting for customer',
+ },
+ creationDate: '2023-09-18T12:25:34.000Z',
+ changedModelId: '1',
+ changedModelValue: null,
+ description: null,
+ user: {
+ id: 18,
+ name: 'salesPerson',
+ nickname: 'salesPersonNick',
+ image: '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd',
+ worker: {
+ id: 18,
+ userFk: 18,
},
},
+ },
+ {
+ id: 1,
+ originFk: 1,
+ userFk: 18,
+ action: 'update',
+ changedModel: 'Claim',
+ oldInstance: {
+ hasToPickUp: false,
+ },
+ newInstance: {
+ hasToPickUp: true,
+ },
+ creationDate: '2023-09-18T12:25:34.000Z',
+ changedModelId: '1',
+ changedModelValue: null,
+ description: null,
+ user: {
+ id: 18,
+ name: 'salesPerson',
+ nickname: 'salesPersonNick',
+ image: '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd',
+ worker: {
+ id: 18,
+ userFk: 18,
+ },
+ },
+ },
+ ];
+ const mockValidations = {
+ Claim: {
+ locale: {
+ name: 'reclamación',
+ },
+ },
+ ClaimObservation: {
+ locale: {
+ name: 'observación',
+ },
+ },
+ ClaimDms: {
+ locale: {
+ name: 'documento',
+ },
+ },
+ ClaimBeginning: {
+ locale: {
+ name: 'comienzo',
+ },
+ },
+ };
+
+ beforeAll(async () => {
+ axios.get.mockImplementation(() => {
+ return { data: fakeLogTreeData };
+ });
+
+ vm = createWrapper(VnLog, {
+ global: {
+ stubs: [],
+ mocks: {},
+ },
propsData: {
- model: "Claim",
+ model: 'Claim',
},
}).vm;
+ vm.validations = mockValidations;
});
afterEach(() => {
vi.clearAllMocks();
});
- describe('formatValue()', () => {
- it('should return Yes if has a true boolean', async () => {
- const result = vm.formatValue(true);
-
- expect(result).toEqual('Yes');
- });
- it('should return No if has a true boolean', async () => {
- const result = vm.formatValue(false);
-
- expect(result).toEqual('No');
- });
- it('should return Nothing if has no params', async () => {
- const result = vm.formatValue();
-
- expect(result).toEqual('Nothing');
- });
- it('should return a string from a string value', async () => {
- const result = vm.formatValue('Something');
-
- expect(result).toEqual(`"Something"`);
- });
- it('should call to format a date', async () => {
- vi.mock('src/filters', () => ({
- toDate: ()=>{
- return "Date formatted"
- },
- }));
-
- const result = vm.formatValue('01-01-1970');
- expect(result).toEqual("Date formatted");
- });
+ it('should correctly set logTree', async () => {
+ vm.logTree = vm.getLogs(fakeLogTreeData);
+ expect(vm.logTree[0].originFk).toEqual(1);
+ expect(vm.logTree[0].logs[0].user.name).toEqual('salesPerson');
});
- describe('actionColor()', () => {
- it('should return positive if insert', async () => {
- const result = vm.actionColor('insert');
+ it('should correctly set the selectedFilters when filtering', () => {
+ vm.searchInput = '1';
+ vm.userSelect = '21';
+ vm.checkboxOptions.insert.selected = true;
+ vm.checkboxOptions.update.selected = true;
- expect(result).toEqual('positive');
- });
- it('should return positive if update', async () => {
- const result = vm.actionColor('update');
+ vm.selectFilter('search');
+ vm.selectFilter('userSelect');
- expect(result).toEqual('positive');
- });
- it('should return negative if delete', async () => {
- const result = vm.actionColor('delete');
+ expect(vm.selectedFilters.changedModelId).toEqual('1');
+ expect(vm.selectedFilters.userFk).toEqual('21');
+ expect(vm.selectedFilters.action).toEqual({ inq: ['insert', 'update'] });
+ });
- expect(result).toEqual('negative');
- });
+ it('should correctly set the date from', () => {
+ vm.dateFrom = '18-09-2023';
+ vm.selectFilter('date', 'from');
+ expect(vm.selectedFilters.creationDate.between).toEqual([
+ new Date('2023-09-18T00:00:00.000Z'),
+ new Date('2023-09-18T21:59:59.999Z'),
+ ]);
});
});
diff --git a/test/vitest/helper.js b/test/vitest/helper.js
index 7cc2bdfa5..89cc640fd 100644
--- a/test/vitest/helper.js
+++ b/test/vitest/helper.js
@@ -13,7 +13,6 @@ installQuasarPlugin({
Dialog,
},
});
-
const pinia = createTestingPinia({ createSpy: vi.fn, stubActions: false });
const mockPush = vi.fn();
@@ -35,8 +34,10 @@ vi.mock('vue-router', () => ({
}),
}));
+vi.mock('axios');
+
vi.spyOn(useValidator, 'useValidator').mockImplementation(() => {
- return { validate: vi.fn(), fetch: vi.fn() };
+ return { validate: vi.fn() };
});
class FormDataMock {