diff --git a/src/components/CrudModel.vue b/src/components/CrudModel.vue new file mode 100644 index 000000000..6b8c577a0 --- /dev/null +++ b/src/components/CrudModel.vue @@ -0,0 +1,339 @@ + + + + + {{ t('globals.changesToSave') }} + + + + + + + + + + + + + + + + + + + + { + "en": { + "confirmDeletion": "Confirm deletion", + "confirmDeletionMessage": "Are you sure you want to delete this?" + }, + "es": { + "confirmDeletion": "Confirmar eliminación", + "confirmDeletionMessage": "Seguro que quieres eliminar?" + } + } + diff --git a/src/components/FormModel.vue b/src/components/FormModel.vue index 2897997ed..3c05333f5 100644 --- a/src/components/FormModel.vue +++ b/src/components/FormModel.vue @@ -4,12 +4,14 @@ import { onMounted, onUnmounted, computed, ref, watch } from 'vue'; import { useI18n } from 'vue-i18n'; import { useQuasar } from 'quasar'; import { useState } from 'src/composables/useState'; +import { useStateStore } from 'stores/useStateStore'; import { useValidator } from 'src/composables/useValidator'; import SkeletonForm from 'components/ui/SkeletonForm.vue'; const quasar = useQuasar(); -const { t } = useI18n(); const state = useState(); +const stateStore = useStateStore(); +const { t } = useI18n(); const { validate } = useValidator(); const $props = defineProps({ @@ -29,63 +31,41 @@ const $props = defineProps({ type: String, default: null, }, - crud: { - type: Boolean, - default: null, - }, - primaryKey: { - type: String, - default: 'id', - }, - dataRequired: { - type: Object, - default: null, - }, defaultActions: { type: Boolean, default: true, }, }); -const isLoading = ref(false); -const hasChanges = ref(false); -const formData = computed(() => state.get($props.model)); -const originalData = ref(); -const formUrl = computed(() => $props.url); -const stActionsExist = ref(false); - const emit = defineEmits(['onFetch']); defineExpose({ save, - insert, - remove, - onSubmit, - reset, - hasChanges, }); -onMounted(async () => { - await fetch(); - stActionsExist.value = !!document.querySelector('#st-actions'); -}); +onMounted(async () => await fetch()); onUnmounted(() => { state.unset($props.model); }); +const isLoading = ref(false); +const hasChanges = ref(false); +const originalData = ref(); +const formData = computed(() => state.get($props.model)); +const formUrl = computed(() => $props.url); + +function tMobile(...args) { + if (!quasar.platform.is.mobile) return t(...args); +} + async function fetch() { const { data } = await axios.get($props.url, { params: { filter: $props.filter }, }); - if (Array.isArray(data)) { - let $index = 0; - data.map((d) => (d.$index = $index++)); - } - state.set($props.model, data); - originalData.value = JSON.parse(JSON.stringify(data)); + originalData.value = Object.assign({}, data); watch(formData.value, () => (hasChanges.value = true)); @@ -100,29 +80,24 @@ async function save() { }); } isLoading.value = true; - try { - await axios.patch($props.urlUpdate || $props.url, formData.value); - } catch { - return (isLoading.value = false); - } + await axios.patch($props.urlUpdate || $props.url, formData.value); - originalData.value = JSON.parse(JSON.stringify(formData.value)); + originalData.value = formData.value; hasChanges.value = false; isLoading.value = false; } function reset() { state.set($props.model, originalData.value); - watch(formData.value, () => (hasChanges.value = true)); hasChanges.value = false; } // eslint-disable-next-line vue/no-dupe-keys function filter(value, update, filterOptions) { update( () => { - const { options, filterFn, field } = filterOptions; + const { options, filterFn } = filterOptions; - options.value = filterFn(options, value, field); + options.value = filterFn(options, value); }, (ref) => { ref.setOptionIndex(-1); @@ -131,167 +106,42 @@ function filter(value, update, filterOptions) { ); } -async function crudSave() { - if (!hasChanges.value) { - return quasar.notify({ - type: 'negative', - message: t('globals.noChanges'), - }); - } - isLoading.value = true; - await saveChanges(); -} - -async function saveChanges() { - const changes = getChanges(); - try { - await axios.post($props.urlUpdate || $props.url + '/crud', changes); - } catch (e) { - return (isLoading.value = false); - } - - originalData.value = JSON.parse(JSON.stringify(formData.value)); - hasChanges.value = false; - isLoading.value = false; - if (changes.creates?.length) await fetch(); -} - -async function insert() { - const $index = formData.value[formData.value.length - 1].$index + 1; - formData.value.push(Object.assign({ $index }, $props.dataRequired)); - hasChanges.value = true; -} - -// function addRemove(ids) { -// for (let id of ids) { -// const index = removed.value.indexOf(id); -// if (index > -1) { -// removed.value = removed.value.slice(index, 1); -// continue; -// } -// removed.value.push(id); -// } -// } - -async function remove(data) { - if (!data.length) - return quasar.notify({ - type: 'warning', - message: t('globals.noChanges'), - }); - - const pk = $props.primaryKey; - let ids = data.map((d) => d[pk]).filter(Boolean); - let preRemove = data.map((d) => d.$index).filter(Boolean); - let newData = formData.value; - - // addRemove(ids); - if (preRemove.length) { - newData = newData.filter( - (form) => !preRemove.some((index) => index == form.$index) - ); - state.set($props.model, newData); - const changes = getChanges(); - if (!changes.creates?.length && !changes.updates?.length) - hasChanges.value = false; - } - if (ids.length) { - await saveChanges({ deletes: ids }); - newData = newData.filter((form) => !ids.some((id) => id == form[pk])); - state.set($props.model, newData); - } -} - -async function onSubmit() { - if ($props.crud) return crudSave(); - return save(); -} - watch(formUrl, async () => { originalData.value = null; reset(); fetch(); }); - -function getChanges() { - const updates = []; - const creates = []; - - const pk = $props.primaryKey; - - for (const [i, row] of formData.value.entries()) { - if (!row[pk]) { - creates.push(row); - } else if (originalData.value) { - const data = getDifferences(originalData.value[i], row); - if (!isEmpty(data)) { - updates.push({ - data, - where: { [pk]: row[pk] }, - }); - } - } - } - const changes = { updates, creates }; - - for (let prop in changes) { - if (changes[prop].length === 0) changes[prop] = undefined; - } - - return changes; -} - -function getDifferences(obj1, obj2) { - let diff = {}; - for (let key in obj1) { - if (obj2[key] && obj1[key] !== obj2[key]) { - diff[key] = obj2[key]; - } - } - for (let key in obj2) { - if (obj1[key] === undefined && obj2[key]) { - diff[key] = obj2[key]; - } - } - return diff; -} - -function isEmpty(obj) { - if (obj == null) return true; - if (obj === undefined) return true; - if (Object.keys(obj).length === 0) return true; - - if (obj.length > 0) return false; -} {{ t('globals.changesToSave') }} - + - - - - + + + + - - + + diff --git a/src/components/ui/SkeletonTable.vue b/src/components/ui/SkeletonTable.vue new file mode 100644 index 000000000..d58253f90 --- /dev/null +++ b/src/components/ui/SkeletonTable.vue @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/ui/VnPaginate.vue b/src/components/ui/VnPaginate.vue index 2a5b878f5..9c8cd6223 100644 --- a/src/components/ui/VnPaginate.vue +++ b/src/components/ui/VnPaginate.vue @@ -46,6 +46,10 @@ const props = defineProps({ type: Number, default: 500, }, + skeleton: { + type: Boolean, + default: true, + }, }); const emit = defineEmits(['onFetch', 'onPaginate']); @@ -144,7 +148,10 @@ async function onLoad(...params) { {{ t('No results found') }} - + diff --git a/src/css/app.scss b/src/css/app.scss index 466219906..1f12fb1cc 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -35,4 +35,19 @@ body.body--light { color: white; } } + --vn-text: #000000; + --vn-gray: #dddddd; + --vn-label: #5f5f5f; + --vn-dark: white; +} + +body.body--dark { + --vn-text: #ffffff; + --vn-gray: #313131; + --vn-label: #a8a8a8; + --vn-dark: #292929; +} + +.bg-vn-dark { + background-color: var(--vn-dark); } diff --git a/src/pages/Claim/Card/ClaimCard.vue b/src/pages/Claim/Card/ClaimCard.vue index 141b6e258..142657ba5 100644 --- a/src/pages/Claim/Card/ClaimCard.vue +++ b/src/pages/Claim/Card/ClaimCard.vue @@ -29,6 +29,7 @@ const claimSections = [ let salixUrl; onMounted(async () => { salixUrl = await getUrl(`claim/${entityId.value}`); + stateStore.setSubtoolbar(); }); @@ -64,10 +65,8 @@ onMounted(async () => { - - - {{ route.meta?.title && t(`claim.pageTitles.${route.meta.title}`) }} - + + diff --git a/src/pages/Claim/Card/ClaimDevelopment.vue b/src/pages/Claim/Card/ClaimDevelopment.vue index da297cd81..764b64def 100644 --- a/src/pages/Claim/Card/ClaimDevelopment.vue +++ b/src/pages/Claim/Card/ClaimDevelopment.vue @@ -1,8 +1,8 @@ - + - {{ t('Claimed lines') }} - {{ t('Amount') }} @@ -211,7 +170,7 @@ function showImportDialog() { - + - @@ -361,46 +325,12 @@ function showImportDialog() { - + - - - - {{ t('globals.remove') }} - - - {{ t('globals.add') }} - - - - - - - - - - - - - + + @@ -421,7 +351,6 @@ en: You are about to remove {count} row | You are about to remove {count} rows' es: - Claimed lines: Líneas reclamadas Delivered: Entregado Quantity: Cantidad Claimed: Reclamada diff --git a/src/stores/useStateStore.js b/src/stores/useStateStore.js index 8704c46e4..cf259a767 100644 --- a/src/stores/useStateStore.js +++ b/src/stores/useStateStore.js @@ -5,6 +5,7 @@ export const useStateStore = defineStore('stateStore', () => { const isMounted = ref(false); const leftDrawer = ref(false); const rightDrawer = ref(false); + const subToolbar = ref(false); function toggleLeftDrawer() { leftDrawer.value = !leftDrawer.value; @@ -18,6 +19,10 @@ export const useStateStore = defineStore('stateStore', () => { isMounted.value = true; } + function setSubtoolbar() { + subToolbar.value = true; + } + function isHeaderMounted() { return isMounted.value; } @@ -30,6 +35,10 @@ export const useStateStore = defineStore('stateStore', () => { return rightDrawer.value; } + function isSubToolbarShown() { + return subToolbar.value; + } + return { leftDrawer, rightDrawer, @@ -39,5 +48,7 @@ export const useStateStore = defineStore('stateStore', () => { toggleRightDrawer, isLeftDrawerShown, isRightDrawerShown, + setSubtoolbar, + isSubToolbarShown, }; }); diff --git a/test/cypress/integration/claimDevelopment.spec.js b/test/cypress/integration/claimDevelopment.spec.js new file mode 100755 index 000000000..ec41e92a3 --- /dev/null +++ b/test/cypress/integration/claimDevelopment.spec.js @@ -0,0 +1,55 @@ +/// +describe('ClaimPhoto', () => { + beforeEach(() => { + const claimId = 1; + cy.login('developer'); + cy.visit(`/#/claim/${claimId}/photos`); + }); + + it('should add new file', () => { + cy.get('label > .q-btn').click(); + cy.get('label > .q-btn input').selectFile('test/cypress/fixtures/image.jpg', { + force: true, + }); + cy.get('.q-notification__message').should('have.text', 'Data saved'); + }); + + it('should add new file with drag and drop', () => { + cy.get('.container').selectFile('test/cypress/fixtures/image.jpg', { + action: 'drag-drop', + }); + cy.get('.q-notification__message').should('have.text', 'Data saved'); + }); + + it('should open first image dialog change to second and close', () => { + cy.get( + ':nth-child(1) > .q-card > .q-img > .q-img__container > .q-img__image' + ).click(); + cy.get('.q-carousel__slide > .q-img > .q-img__container > .q-img__image').should( + 'be.visible' + ); + + cy.get('.q-carousel__control > .q-btn > .q-btn__content > .q-icon').click(); + + cy.get( + '.q-dialog__inner > .q-toolbar > .q-btn > .q-btn__content > .q-icon' + ).click(); + cy.get('.q-carousel__slide > .q-img > .q-img__container > .q-img__image').should( + 'not.be.visible' + ); + }); + + it('should remove third and fourth file', () => { + cy.get( + '.multimediaParent > :nth-child(3) > .q-btn > .q-btn__content > .q-icon' + ).click(); + cy.get('.q-btn--unelevated > .q-btn__content > .block').click(); + cy.get('.q-notification__message').should('have.text', 'Data deleted'); + + cy.get( + '.multimediaParent > :nth-child(3) > .q-btn > .q-btn__content > .q-icon' + ).click(); + cy.get('.q-btn--unelevated > .q-btn__content > .block').click(); + cy.get('.q-notification__message').should('have.text', 'Data deleted'); + }); +});