diff --git a/package.json b/package.json index b713c906a8..8568d507dc 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "lint": "eslint --ext .js,.vue ./", "format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore", "test:e2e": "cypress open", - "test:e2e:ci": "cypress run --browser chromium", + "test:e2e:ci": "cypress run --browser chrome", "test": "echo \"See package.json => scripts for available tests.\" && exit 0", "test:unit": "vitest", "test:unit:ci": "vitest run" diff --git a/src/components/FetchData.vue b/src/components/FetchData.vue index 251d7502a1..f0d9089723 100644 --- a/src/components/FetchData.vue +++ b/src/components/FetchData.vue @@ -46,7 +46,7 @@ async function fetch() { if ($props.limit) filter.limit = $props.limit; const { data } = await axios.get($props.url, { - params: { filter }, + params: { filter: JSON.stringify(filter) }, }); emit('onFetch', data); diff --git a/src/components/common/VnJsonValue.vue b/src/components/common/VnJsonValue.vue new file mode 100644 index 0000000000..a2e858d0d4 --- /dev/null +++ b/src/components/common/VnJsonValue.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/src/components/common/VnLog.vue b/src/components/common/VnLog.vue index 1213c8bbcc..7e3cfc4086 100644 --- a/src/components/common/VnLog.vue +++ b/src/components/common/VnLog.vue @@ -1,128 +1,641 @@ - + 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 + Creates: Creates + Edits: Edits + Deletes: Deletes + Accesses: Accesses models: - Claim: Claim - ClaimDms: Document - ClaimBeginning: Claimed Sales - ClaimObservation: Observation + Claim: Reclamación + ClaimDms: Documento + ClaimBeginning: Comienzo + ClaimObservation: Observación + Users: + User: Usuario + All: Todo + System: Sistema properties: id: ID claimFk: Claim ID @@ -172,6 +1071,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 +1084,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 0000000000..b325e985f1 --- /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/useFirstUpper.js b/src/composables/useFirstUpper.js new file mode 100644 index 0000000000..36378c05fc --- /dev/null +++ b/src/composables/useFirstUpper.js @@ -0,0 +1,3 @@ +export function useFirstUpper(str) { + return str && str.charAt(0).toUpperCase() + str.substr(1); +} diff --git a/src/composables/useIso8601.js b/src/composables/useIso8601.js new file mode 100644 index 0000000000..848e60de83 --- /dev/null +++ b/src/composables/useIso8601.js @@ -0,0 +1,18 @@ +export function useIso8601(dateString) { + // Crear un objeto Date a partir de la cadena de texto + const date = new Date(dateString); + + // Obtener los componentes de fecha y hora + const year = date.getUTCFullYear(); + const month = String(date.getUTCMonth() + 1).padStart(2, '0'); + const day = String(date.getUTCDate()).padStart(2, '0'); + const hours = String(date.getUTCHours()).padStart(2, '0'); + const minutes = String(date.getUTCMinutes()).padStart(2, '0'); + const seconds = String(date.getUTCSeconds()).padStart(2, '0'); + const milliseconds = String(date.getUTCMilliseconds()).padStart(3, '0'); + + // Formatear la cadena en el formato ISO 8601 + const formattedDate = `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${milliseconds}Z`; + + return formattedDate; +} diff --git a/src/css/app.scss b/src/css/app.scss index 3c8cc50b64..9ed51c1c72 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -14,6 +14,10 @@ a { color: $orange-4; } +.rounded--full { + border-radius: 50%; +} + // Removes chrome autofill background input:-webkit-autofill, select:-webkit-autofill { @@ -32,10 +36,12 @@ body.body--light { --vn-text: #000000; --vn-gray: #f5f5f5; --vn-label: #5f5f5f; + --vn-header: #e9e9e9; } body.body--dark { --vn-text: #ffffff; --vn-gray: #313131; --vn-label: #a8a8a8; + --vn-header: #212121; } diff --git a/src/filters/index.js b/src/filters/index.js index 158ce1009b..b0c441641c 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 0000000000..76e67dbeae --- /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 d7519ba533..285fb778f9 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', }, errors: { statusUnauthorized: 'Access denied', diff --git a/src/i18n/es/index.js b/src/i18n/es/index.js index fc2c80f551..97001eab85 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', }, errors: { statusUnauthorized: 'Acceso denegado', diff --git a/src/stores/useValidationsStore.js b/src/stores/useValidationsStore.js new file mode 100644 index 0000000000..c658a90af4 --- /dev/null +++ b/src/stores/useValidationsStore.js @@ -0,0 +1,19 @@ +import axios from 'axios'; +import { defineStore } from 'pinia'; + +export const useValidationsStore = defineStore('validationsStore', { + state: () => ({ + validations: null, + }), + actions: { + async fetchModels() { + if (this.validations) return; + 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/ClaimNotes.spec.js b/test/cypress/integration/ClaimNotes.spec.js index 5b52dd3398..9f52c29e4b 100644 --- a/test/cypress/integration/ClaimNotes.spec.js +++ b/test/cypress/integration/ClaimNotes.spec.js @@ -8,6 +8,7 @@ describe('ClaimNotes', () => { it('should add a new note', () => { const message = 'This is a new message.'; cy.get('.q-page-sticky button').click(); + cy.get('.q-page-sticky > div > button').click(); cy.get('.q-dialog .q-card__section:nth-child(2)').type(message); cy.get('.q-card__actions button:nth-child(2)').click(); cy.get('.q-card .q-card__section:nth-child(2)') diff --git a/test/cypress/integration/vnLog.spec.js b/test/cypress/integration/vnLog.spec.js new file mode 100644 index 0000000000..3d6d9b95de --- /dev/null +++ b/test/cypress/integration/vnLog.spec.js @@ -0,0 +1,31 @@ +/// +describe('ClaimNotes', () => { + beforeEach(() => { + cy.login('developer'); + cy.visit(`/#/claim/${1}/log`); + }); + + it('should have just one record', () => { + cy.get('.model-info .q-btn').eq(1).click(); + cy.get('.user-log .model-log').its('length').should('eq', 1); + cy.get('.q-page-sticky .q-btn').click(); + cy.get('.user-log .model-log').its('length').should('eq', 4); + }); + + it('should filter by insert actions', () => { + cy.get('.q-gutter-x-sm > .q-btn > .q-btn__content > .q-icon').click(); + cy.get('.q-checkbox__inner').eq(0).click(); + cy.get('.q-page > .q-drawer-container > .fullscreen').click(); + cy.get('.model-info .q-chip__content').eq(0).should('have.text', 'Document'); + cy.get('.model-info .q-chip__content').eq(1).should('have.text', 'Beginning'); + }); + + it('should show the point record', () => { + cy.get('.pit').eq(0).click(); + cy.get('.q-menu .q-card .header').should('have.text', 'Observation #1'); + cy.get('.q-menu .q-card .json-string').should( + 'have.text', + 'Waiting for customer' + ); + }); +}); diff --git a/test/cypress/integration/workerList.spec.js b/test/cypress/integration/workerList.spec.js index d76958367a..8d4dd770d9 100644 --- a/test/cypress/integration/workerList.spec.js +++ b/test/cypress/integration/workerList.spec.js @@ -8,17 +8,17 @@ describe('WorkerList', () => { it('should load workers', () => { cy.get('.card-list-body > .list-items > :nth-child(2) > .value > span') .eq(0) - .should('have.text', 'victorvd'); - cy.get('.card-list-body > .list-items > :nth-child(2) > .value > span') - .eq(1) .should('have.text', 'JessicaJones'); cy.get('.card-list-body > .list-items > :nth-child(2) > .value > span') - .eq(2) + .eq(1) .should('have.text', 'BruceBanner'); + cy.get('.card-list-body > .list-items > :nth-child(2) > .value > span') + .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('.card-list-body .actions .q-btn:nth-child(2)').eq(0).click(); 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');