diff --git a/package-lock.json b/package-lock.json index dc1ebb2f..86b5f683 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@vuelidate/core": "^2.0.3", "@vuelidate/validators": "^2.0.4", "@vueuse/core": "^11.0.3", - "@webitel/ui-sdk": "^24.12.8", + "@webitel/ui-sdk": "^24.12.13", "axios": "^1.7.7", "deep-equal": "^2.2.1", "dompurify": "^3.1.2", @@ -24,7 +24,7 @@ "vue-i18n": "^9.13.1", "vue-router": "^4.3.2", "vuex": "^4.1.0", - "webitel-sdk": "^24.4.16" + "webitel-sdk": "^24.10.2" }, "devDependencies": { "@vitejs/plugin-vue": "5.1.3", @@ -2073,9 +2073,9 @@ } }, "node_modules/@webitel/ui-sdk": { - "version": "24.12.8", - "resolved": "https://registry.npmjs.org/@webitel/ui-sdk/-/ui-sdk-24.12.8.tgz", - "integrity": "sha512-RbfKCwFKxdpn7z4u/fS3JEWbWuxKllUyXI6hwlSu3AyPo4Uvg5SdNs9x3eIK1Y75p4Rs0XK/fZyBtZBJAWZT0A==", + "version": "24.12.13", + "resolved": "https://registry.npmjs.org/@webitel/ui-sdk/-/ui-sdk-24.12.13.tgz", + "integrity": "sha512-+sCrne9hD/76WDYGcgxxCXimWejfiGtClhGZy52uGWurDfnZGq6LmiqJrZr1zH6DBeLUAMiivI2lRFcc4Z8K3w==", "dependencies": { "@floating-ui/vue": "^1.1.5", "@morev/vue-transitions": "^3.0.5", @@ -10451,9 +10451,10 @@ } }, "node_modules/webitel-sdk": { - "version": "24.8.8", - "resolved": "https://registry.npmjs.org/webitel-sdk/-/webitel-sdk-24.8.8.tgz", - "integrity": "sha512-YwivCEjnnsH/8VedFUcMjCX9Kdhy+KSp2MkKtladyKxMKjttGysM7D3jEyqAAH5jSL1XYL/sr8D3ntmr2FOg/A==", + "version": "24.10.2", + "resolved": "https://registry.npmjs.org/webitel-sdk/-/webitel-sdk-24.10.2.tgz", + "integrity": "sha512-gkyMawfT5EDQwq44RlXP5MkeE2Cx8SqV7JXsQ/HCQSMIEQQWoSTVvX/6UMVOWgyZocxiHAK/wHU8QFGe+GTs+w==", + "license": "MIT", "dependencies": { "@types/webrtc": "~0.0.41", "deep-copy": "1.4.2", diff --git a/package.json b/package.json index 97b16fba..7304d469 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "@vuelidate/core": "^2.0.3", "@vuelidate/validators": "^2.0.4", "@vueuse/core": "^11.0.3", - "@webitel/ui-sdk": "^24.12.8", + "@webitel/ui-sdk": "^24.12.13", "axios": "^1.7.7", "deep-equal": "^2.2.1", "dompurify": "^3.1.2", @@ -27,11 +27,7 @@ "vue-i18n": "^9.13.1", "vue-router": "^4.3.2", "vuex": "^4.1.0", - "webitel-sdk": "^24.4.16" - }, - "optionalDependencies": { - "@rollup/rollup-linux-x64-gnu": "4.9.5", - "@esbuild/darwin-arm64": "^0.24.0" + "webitel-sdk": "^24.10.2" }, "devDependencies": { "@vitejs/plugin-vue": "5.1.3", @@ -47,5 +43,9 @@ "vite-plugin-node-polyfills": "^0.22.0", "vite-plugin-svg-sprite": "^0.5.2", "vitest": "^2.0.5" + }, + "optionalDependencies": { + "@esbuild/darwin-arm64": "^0.24.0", + "@rollup/rollup-linux-x64-gnu": "4.9.5" } } diff --git a/src/app/css/main.scss b/src/app/css/main.scss index 62373937..62b03527 100644 --- a/src/app/css/main.scss +++ b/src/app/css/main.scss @@ -16,19 +16,6 @@ body { min-height: 100%; } -.wt-page-wrapper { - flex-grow: 1; - display: flex; - max-height: 100%; - - :deep(.wt-page-wrapper__main) { - display: flex; - flex-direction: column; - max-height: 100%; - min-height: 0; - } -} - .table-wrapper { flex-grow: 1; display: flex; @@ -78,3 +65,26 @@ a { text-decoration: none; color: #000; } + + +.wt-page-wrapper { + width: 100%; + + &__main { + flex-grow: 1; + display: flex; + max-height: 100%; + } + + .main-container { + display: flex; + flex-direction: column; + width: 100%; + + & > section { + display: flex; + flex-direction: column; + height: 100%; + } + } +} diff --git a/src/app/locale/en/en.js b/src/app/locale/en/en.js index 5c6aecfe..5faf9024 100644 --- a/src/app/locale/en/en.js +++ b/src/app/locale/en/en.js @@ -7,6 +7,8 @@ import AccessMode from '../../../modules/contacts/modules/permissions/enums/AccessMode.enum.js'; import TimelineTaskStatusEnum from '../../../modules/contacts/modules/timeline/enums/TimelineTaskStatus.enum.js'; +import TypesSourcesEnum + from '../../../modules/configuration/modules/lookups/modules/sources/enums/TypesSources.enum.js'; export default { crm: 'CRM', @@ -93,7 +95,7 @@ export default { name: 'Contacts', text: 'In this section, you can work with your contacts: clients, customers etc.', }, - 'configuration': { + configuration: { name: 'Configuration', text: 'In this section, you will set up the Case management process.', }, @@ -103,9 +105,25 @@ export default { slas: { slas: 'SLA | SLAs', + conditions: 'Condition | Conditions', + reactionTime: 'Reaction time', + resolutionTime: 'Resolution time', + validFrom: 'Valid from', + validTo: 'Valid to', }, + sources: { + sources: 'Case source | Case sources', + types: { + [TypesSourcesEnum.CALL]: 'Call', + [TypesSourcesEnum.CHAT]: 'Chat', + [TypesSourcesEnum.SOCIAL_MEDIA]: 'Social Media', + [TypesSourcesEnum.EMAIL]: 'Email', + [TypesSourcesEnum.API]: 'API', + [TypesSourcesEnum.MANUAL]: 'Manual', + } + }, + [CrmSections.CONTACT_GROUPS]: 'Contact groups', [CrmSections.STATUSES]: 'Statuses', - [CrmSections.SOURCES]: 'Sources', }, }; diff --git a/src/app/locale/ru/ru.js b/src/app/locale/ru/ru.js index 62c4e4fa..9f16cf68 100644 --- a/src/app/locale/ru/ru.js +++ b/src/app/locale/ru/ru.js @@ -2,6 +2,8 @@ import { WebitelContactsTimelineEventType } from 'webitel-sdk'; import ChatGatewayProvider from '@webitel/ui-sdk/src/enums/ChatGatewayProvider/ChatGatewayProvider.enum.js'; import CrmSections from '@webitel/ui-sdk/src/enums/WebitelApplications/CrmSections.enum'; +import TypesSourcesEnum + from '../../../modules/configuration/modules/lookups/modules/sources/enums/TypesSources.enum.js'; import AccessMode from '../../../modules/contacts/modules/permissions/enums/AccessMode.enum.js'; import TimelineTaskStatusEnum @@ -92,7 +94,7 @@ export default { name: 'Контакты', text: 'В этом разделе вы можете работать с Контактами: клиентами, партнерами и т.д.', }, - 'configuration': { + configuration: { name: 'Конфигурация', text: 'Здесь вы можете просматривать Обращения, зарегистрированные в системе.', }, @@ -102,9 +104,25 @@ export default { slas: { slas: 'SLA | SLAs', + conditions: 'Условие | Условия', + reactionTime: 'Плановое время реакции', + resolutionTime: 'Плановое время решения', + validFrom: 'Действителен с', + validTo: 'Действителен до', + }, + + sources: { + sources: 'Источник обращений | Источники обращений', + types: { + [TypesSourcesEnum.CALL]: 'Звонок', + [TypesSourcesEnum.CHAT]: 'Чат', + [TypesSourcesEnum.SOCIAL_MEDIA]: 'Социальная сеть', + [TypesSourcesEnum.EMAIL]: 'Письмо', + [TypesSourcesEnum.API]: 'API', + [TypesSourcesEnum.MANUAL]: 'Созданное вручную', + }, }, [CrmSections.CONTACT_GROUPS]: 'Группы контактов', [CrmSections.STATUSES]: 'Статусы', - [CrmSections.SOURCES]: 'Источники', }, }; diff --git a/src/app/locale/ua/ua.js b/src/app/locale/ua/ua.js index 91c42c0b..e23fa9af 100644 --- a/src/app/locale/ua/ua.js +++ b/src/app/locale/ua/ua.js @@ -2,6 +2,8 @@ import { WebitelContactsTimelineEventType } from 'webitel-sdk'; import ChatGatewayProvider from '@webitel/ui-sdk/src/enums/ChatGatewayProvider/ChatGatewayProvider.enum.js'; import CrmSections from '@webitel/ui-sdk/src/enums/WebitelApplications/CrmSections.enum'; +import TypesSourcesEnum + from '../../../modules/configuration/modules/lookups/modules/sources/enums/TypesSources.enum.js'; import AccessMode from '../../../modules/contacts/modules/permissions/enums/AccessMode.enum.js'; import TimelineTaskStatusEnum @@ -92,7 +94,7 @@ export default { name: 'Контакти', text: 'У цьому розділі ви можете працювати з Контактами: клієнтами, партнерами тощо.', }, - 'configuration': { + configuration: { name: 'Конфігурація', text: 'Тут ви можете переглядати Звернення, зареєстровані у системі.', }, @@ -102,10 +104,26 @@ export default { slas: { slas: 'SLA | SLAs', + conditions: 'Умова | Умови', + reactionTime: 'Плановий час реакції', + resolutionTime: 'Плановий час вирішення', + validFrom: 'Дійсний з', + validTo: 'Дійсний до', }, + sources: { + sources: 'Джерело звернень | Джерела звернень', + + types: { + [TypesSourcesEnum.CALL]: 'Дзвінок', + [TypesSourcesEnum.CHAT]: 'Чат', + [TypesSourcesEnum.SOCIAL_MEDIA]: 'Соціальна мережа', + [TypesSourcesEnum.EMAIL]: 'Лист', + [TypesSourcesEnum.API]: 'API', + [TypesSourcesEnum.MANUAL]: 'Створене вручну', + }, + }, [CrmSections.CONTACT_GROUPS]: 'Групи контактів', [CrmSections.STATUSES]: 'Статуси', - [CrmSections.SOURCES]: 'Джерела', }, }; diff --git a/src/app/router/index.js b/src/app/router/index.js index 3ee9a97f..406a625d 100644 --- a/src/app/router/index.js +++ b/src/app/router/index.js @@ -16,7 +16,18 @@ import AccessDenied from '../components/utils/access-denied-component.vue'; import TheStartPage from '../../modules/start-page/components/the-start-page.vue'; import TheContacts from '../../modules/contacts/components/the-contacts.vue'; - +import TheSlas + from '../../modules/configuration/modules/lookups/modules/slas/components/the-slas.vue'; +import OpenedSla from '../../modules/configuration/modules/lookups/modules/slas/components/opened-sla.vue'; +import OpenedSlaGeneral from '../../modules/configuration/modules/lookups/modules/slas/components/opened-sla-general.vue'; +import TheSources + from '../../modules/configuration/modules/lookups/modules/sources/components/the-sources.vue'; +import OpenedSource + from '../../modules/configuration/modules/lookups/modules/sources/components/opened-source.vue'; +import OpenedSourceGeneral + from '../../modules/configuration/modules/lookups/modules/sources/components/opened-source-general.vue'; +import OpenedSlaConditions + from '../../modules/configuration/modules/lookups/modules/slas/modules/conditions/components/opened-sla-conditions.vue'; import store from '../store'; import TheConfiguration from '../../modules/configuration/components/the-configuration.vue'; @@ -124,6 +135,45 @@ const routes = [ { path: 'slas', name: CrmSections.SLAS, + component: TheSlas, + // beforeEnter: checkRouteAccess, + }, + { + path: 'slas/:id', + name: `${CrmSections.SLAS}-card`, + component: OpenedSla, + redirect: { name: `${CrmSections.SLAS}-general` }, + children: [ + { + path: 'general', + name: `${CrmSections.SLAS}-general`, + component: OpenedSlaGeneral, + }, + { + path: 'conditions/:conditionId?', + name: `${CrmSections.SLAS}-conditions`, + component: OpenedSlaConditions, + }, + ], + }, + { + path: 'sources', + name: CrmSections.SOURCES, + component: TheSources, + // beforeEnter: checkRouteAccess, + }, + { + path: 'sources/:id', + name: `${CrmSections.SOURCES}-card`, + component: OpenedSource, + redirect: { name: `${CrmSections.SOURCES}-general` }, + children: [ + { + path: 'general', + name: `${CrmSections.SOURCES}-general`, + component: OpenedSourceGeneral, + }, + ], }, ], }, diff --git a/src/app/store/index.js b/src/app/store/index.js index 83f58fb1..f8d3a936 100644 --- a/src/app/store/index.js +++ b/src/app/store/index.js @@ -3,7 +3,7 @@ import contacts from '../../modules/contacts/store/contacts'; import userinfo from '../../modules/userinfo/store/userinfo'; import appearance from '../../modules/appearance/store/appearance'; import instance from '../api/instance'; -// import configuration from '../../modules/configuration/store/configuration'; +import configuration from '../../modules/configuration/store/configuration'; export default createStore({ state: { @@ -23,5 +23,6 @@ export default createStore({ contacts, userinfo, appearance, + configuration, }, }); diff --git a/src/modules/configuration/components/the-configuration.vue b/src/modules/configuration/components/the-configuration.vue index 13711b9a..06902861 100644 --- a/src/modules/configuration/components/the-configuration.vue +++ b/src/modules/configuration/components/the-configuration.vue @@ -20,7 +20,7 @@ const nav = [ subNav: [ { value: CrmSections.SOURCES, - name: t('lookups.sources'), + name: t('lookups.sources.sources', 2), route: 'lookups/sources', }, { diff --git a/src/modules/configuration/modules/lookups/modules/ priorities/api/priorities.js b/src/modules/configuration/modules/lookups/modules/ priorities/api/priorities.js new file mode 100644 index 00000000..9664e086 --- /dev/null +++ b/src/modules/configuration/modules/lookups/modules/ priorities/api/priorities.js @@ -0,0 +1,59 @@ +import { + getDefaultGetListResponse, + getDefaultGetParams, + getDefaultInstance, +} from '@webitel/ui-sdk/src/api/defaults/index.js'; +import applyTransform, { + camelToSnake, + generateUrl, + merge, + mergeEach, + notify, + sanitize, + snakeToCamel, + starToSearch, +} from '@webitel/ui-sdk/src/api/transformers/index.js'; + +const instance = getDefaultInstance(); + +const baseUrl = '/cases/priorities'; + +const fieldsToSend = ['name', 'description', 'color']; + +const getPrioritiesList = async (params) => { + const fieldsToSend = ['page', 'size', 'q', 'sort', 'fields', 'id']; + + const url = applyTransform(params, [ + merge(getDefaultGetParams()), + starToSearch('search'), + (params) => ({ ...params, q: params.search }), + sanitize(fieldsToSend), + camelToSnake(), + generateUrl(baseUrl), + ]); + try { + const response = await instance.get(url); + const { items, next } = applyTransform(response.data, [ + merge(getDefaultGetListResponse()), + ]); + return { + items: applyTransform(items, []), + next, + }; + } catch (err) { + throw applyTransform(err, [notify]); + } +}; + +const getConditionsLookup = (params) => + getPrioritiesList({ + ...params, + fields: params.fields || ['id', 'name'], + }); + +const PrioritiesAPI = { + getList: getPrioritiesList, + getLookup: getConditionsLookup, +} + +export default PrioritiesAPI; diff --git a/src/modules/configuration/modules/lookups/modules/slas/api/slas.js b/src/modules/configuration/modules/lookups/modules/slas/api/slas.js new file mode 100644 index 00000000..704cb121 --- /dev/null +++ b/src/modules/configuration/modules/lookups/modules/slas/api/slas.js @@ -0,0 +1,133 @@ +import { + getDefaultGetListResponse, + getDefaultGetParams, + getDefaultInstance, + getDefaultOpenAPIConfig, +} from '@webitel/ui-sdk/src/api/defaults/index.js'; +import applyTransform, { + camelToSnake, + merge, + notify, + sanitize, + snakeToCamel, + starToSearch, +} from '@webitel/ui-sdk/src/api/transformers/index.js'; +import { SLAsApiFactory } from 'webitel-sdk'; + +const instance = getDefaultInstance(); +const configuration = getDefaultOpenAPIConfig(); + +const slaService = new SLAsApiFactory(configuration, '', instance); + +const fieldsToSend = ['name', 'description', 'valid_from', 'valid_to', 'calendar', 'reaction_time', 'resolution_time']; + +const getSlasList = async (params) => { + const fieldsToSend = ['page', 'size', 'q', 'sort', 'fields', 'id']; + + const { + page, + size, + fields, + sort, + id, + q, + } = applyTransform(params, [ + merge(getDefaultGetParams()), + starToSearch('search'), + (params) => ({ ...params, q: params.search }), + sanitize(fieldsToSend), + camelToSnake(), + ]); + try { + const response = await slaService.listSLAs( + page, + size, + fields, + sort, + id, + q, + ); + const { items, next } = applyTransform(response.data, [ + merge(getDefaultGetListResponse()), + ]); + return { + items: applyTransform(items, []), + next, + }; + } catch (err) { + throw applyTransform(err, [notify]); + } +}; + +const getSla = async ({ itemId: id }) => { + const itemResponseHandler = (item) => { + return item.sla; + }; + + try { + const response = await slaService.locateSLA(id, fieldsToSend); + return applyTransform(response.data, [ + snakeToCamel(), + itemResponseHandler, + ]); + } catch (err) { + throw applyTransform(err, [notify]); + } +}; + +const preRequestHandler = (item) => { + return { + ...item, + calendarId: item.calendar.id, + } +}; + +const addSla = async ({ itemInstance }) => { + const fieldsToSend = ['name', 'description', 'valid_from', 'valid_to', 'calendar_id', 'reaction_time', 'resolution_time']; //difference with top list - field calendar_id + const item = applyTransform(itemInstance, [ + preRequestHandler, + camelToSnake(), + sanitize(fieldsToSend), + ]); + try { + const response = await slaService.createSLA(item); + return applyTransform(response.data, [ + snakeToCamel() + ]); + } catch (err) { + throw applyTransform(err, [notify]); + } +}; + +const updateSla = async ({ itemInstance, itemId: id }) => { + const fieldsToSend = ['name', 'description', 'valid_from', 'valid_to', 'calendar_id', 'reaction_time', 'resolution_time']; + const item = applyTransform(itemInstance, [ + preRequestHandler, + camelToSnake(), + sanitize(fieldsToSend)]); + try { + const response = await slaService.updateSLA(id, item); + return applyTransform(response.data, [snakeToCamel()]); + } catch (err) { + throw applyTransform(err, [notify]); + } +}; + +const deleteSla = async ({ id }) => { + try { + const response = await slaService.deleteSLA(id); + return applyTransform(response.data, []); + } catch (err) { + throw applyTransform(err, [notify]); + } +}; + +const SlasAPI = { + getList: getSlasList, + get: getSla, + add: addSla, + update: updateSla, + delete: deleteSla, +} + +export default SlasAPI; diff --git a/src/modules/configuration/modules/lookups/modules/slas/components/opened-sla-general.vue b/src/modules/configuration/modules/lookups/modules/slas/components/opened-sla-general.vue new file mode 100644 index 00000000..8b58c741 --- /dev/null +++ b/src/modules/configuration/modules/lookups/modules/slas/components/opened-sla-general.vue @@ -0,0 +1,91 @@ + + + + + {{ t('reusable.generalInfo') }} + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/configuration/modules/lookups/modules/slas/components/opened-sla.vue b/src/modules/configuration/modules/lookups/modules/slas/components/opened-sla.vue new file mode 100644 index 00000000..f9834daf --- /dev/null +++ b/src/modules/configuration/modules/lookups/modules/slas/components/opened-sla.vue @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/configuration/modules/lookups/modules/slas/components/the-slas.vue b/src/modules/configuration/modules/lookups/modules/slas/components/the-slas.vue new file mode 100644 index 00000000..e66a1f3b --- /dev/null +++ b/src/modules/configuration/modules/lookups/modules/slas/components/the-slas.vue @@ -0,0 +1,208 @@ + + + + + + + + + + + + {{ t('lookups.slas.slas') }} + + deleteData(selected), + })" + > + + + + + + + + + + + + + + + + + + + {{ item.name }} + + + + {{ item.description }} + + + {{ item.calendar.name }} + + + + deleteData(item), + })" + /> + + + + + + + + + + + + + diff --git a/src/modules/configuration/modules/lookups/modules/slas/modules/conditions/api/conditions.js b/src/modules/configuration/modules/lookups/modules/slas/modules/conditions/api/conditions.js new file mode 100644 index 00000000..8ec34a3a --- /dev/null +++ b/src/modules/configuration/modules/lookups/modules/slas/modules/conditions/api/conditions.js @@ -0,0 +1,137 @@ +import { + getDefaultGetListResponse, + getDefaultGetParams, + getDefaultInstance, + getDefaultOpenAPIConfig, +} from '@webitel/ui-sdk/src/api/defaults/index.js'; +import applyTransform, { + camelToSnake, + merge, + notify, + sanitize, + snakeToCamel, + starToSearch, +} from '@webitel/ui-sdk/src/api/transformers/index.js'; +import { SLAConditionsApiFactory } from 'webitel-sdk'; + +const instance = getDefaultInstance(); +const configuration = getDefaultOpenAPIConfig(); + +const slaConditionsService = new SLAConditionsApiFactory(configuration, '', instance); + +const fieldsToSend = [ + 'name', + 'priorities', + 'sla_id', + 'reaction_time', + 'resolution_time', +]; + +const getConditionsList = async ({ parentId, ...rest }) => { + const fieldsToSend = ['page', 'size', 'q', 'sort', 'fields', 'id']; + + const { + page, + size, + fields, + sort, + id, + q, + } = applyTransform(rest, [ + merge(getDefaultGetParams()), + starToSearch('search'), + (params) => ({ ...params, q: params.search }), + sanitize(fieldsToSend), + camelToSnake(), + ]); + try { + const response = await slaConditionsService.listSLAConditions( + parentId, + page, + size, + fields, + sort, + id, + q, + ); + const { items, next } = applyTransform(response.data, [ + merge(getDefaultGetListResponse()), + ]); + return { + items: applyTransform(items, [snakeToCamel()]), + next, + }; + } catch (err) { + throw applyTransform(err, [notify]); + } +}; + +const getCondition = async ({ parentId, itemId: id }) => { + const itemResponseHandler = (item) => { + return item.slaCondition; + }; + + try { + const response = await slaConditionsService.locateSLACondition(parentId, id, fieldsToSend); + return applyTransform(response.data, [snakeToCamel(), itemResponseHandler]); + } catch (err) { + throw applyTransform(err, [notify]); + } +}; + +const preRequestHandler = (item) => { + if (!item.priorities) return item; + return { + ...item, + priorities: item.priorities?.map((priority) => priority.id), + }; +}; + +const updateCondition = async ({ itemInstance, itemId: id }) => { + const item = applyTransform(itemInstance, [ + preRequestHandler, + camelToSnake(), + sanitize(fieldsToSend), + ]); + + try { + const response = await slaConditionsService.updateSLACondition(itemInstance.slaId, id, item); + return applyTransform(response.data, [snakeToCamel()]); + } catch (err) { + throw applyTransform(err, [notify]); + } +}; + +const addCondition = async ({ itemInstance, parentId }) => { + const item = applyTransform(itemInstance, [ + preRequestHandler, + camelToSnake(), + sanitize(fieldsToSend), + ]); + + try { + const response = await slaConditionsService.createSLACondition(parentId, item); + return applyTransform(response.data, [snakeToCamel()]); + } catch (err) { + throw applyTransform(err, [notify]); + } +}; + +const deleteCondition = async ({ id, parentId }) => { + try { + const response = await slaConditionsService.deleteSLACondition(parentId, id); + return applyTransform(response.data, []); + } catch (err) { + throw applyTransform(err, [notify]); + } +}; + +const ConditionsAPI = { + getList: getConditionsList, + get: getCondition, + update: updateCondition, + delete: deleteCondition, + add: addCondition, +}; + +export default ConditionsAPI; diff --git a/src/modules/configuration/modules/lookups/modules/slas/modules/conditions/components/opened-sla-condition-popup.vue b/src/modules/configuration/modules/lookups/modules/slas/modules/conditions/components/opened-sla-condition-popup.vue new file mode 100644 index 00000000..e71ad2d8 --- /dev/null +++ b/src/modules/configuration/modules/lookups/modules/slas/modules/conditions/components/opened-sla-condition-popup.vue @@ -0,0 +1,133 @@ + + + + {{ !isNew ? t('reusable.edit') : t('reusable.add') }} + {{ t('lookups.slas.conditions', 1).toLowerCase() }} + + + + + + + + + + + + + {{ t('reusable.save') }} + + + {{ t('reusable.cancel') }} + + + + + + + + diff --git a/src/modules/configuration/modules/lookups/modules/slas/modules/conditions/components/opened-sla-conditions.vue b/src/modules/configuration/modules/lookups/modules/slas/modules/conditions/components/opened-sla-conditions.vue new file mode 100644 index 00000000..499b1c6f --- /dev/null +++ b/src/modules/configuration/modules/lookups/modules/slas/modules/conditions/components/opened-sla-conditions.vue @@ -0,0 +1,209 @@ + + + + + + + {{ t('lookups.slas.conditions', 2) }} + + + + + + + + + + + + + + + + + + + {{ item.name }} + + + + {{ item.priorities[0]?.name }} + + + + +{{ item.priorities?.length - 1 }} + + + + + + {{ name }} + + + + + + + {{ convertDurationWithMinutes(item.reactionTime / 60) }} + + + {{ convertDurationWithMinutes(item.resolutionTime / 60) }} + + + + deleteData(item), + })" + /> + + + + + + + + + + + diff --git a/src/modules/configuration/modules/lookups/modules/slas/modules/conditions/modules/filters/store/filters.js b/src/modules/configuration/modules/lookups/modules/slas/modules/conditions/modules/filters/store/filters.js new file mode 100644 index 00000000..a5e65c90 --- /dev/null +++ b/src/modules/configuration/modules/lookups/modules/slas/modules/conditions/modules/filters/store/filters.js @@ -0,0 +1,25 @@ +import FiltersStoreModule + from '@webitel/ui-sdk/src/modules/Filters/store/FiltersStoreModule'; + +const filtersList = [ + { + name: 'page', + value: 1, + defaultValue: 1, + }, + { + name: 'size', + value: 10, + defaultValue: 10, + }, + { + name: 'sort', + }, + { name: 'search' }, +]; + +const filters = new FiltersStoreModule() +.addFilter(filtersList) +.getModule(); + +export default filters; diff --git a/src/modules/configuration/modules/lookups/modules/slas/modules/conditions/store/_internals/headers.js b/src/modules/configuration/modules/lookups/modules/slas/modules/conditions/store/_internals/headers.js new file mode 100644 index 00000000..949c96ef --- /dev/null +++ b/src/modules/configuration/modules/lookups/modules/slas/modules/conditions/store/_internals/headers.js @@ -0,0 +1,32 @@ +import { SortSymbols } from '@webitel/ui-sdk/src/scripts/sortQueryAdapters'; + +export default [ + { + value: 'name', + locale: 'reusable.name', + show: true, + field: 'name', + sort: SortSymbols.NONE, + }, + { + value: 'priorities', + locale: 'vocabulary.priority', + show: true, + field: 'priorities', + sort: SortSymbols.NONE, + }, + { + value: 'reactionTime', + locale: 'lookups.slas.reactionTime', + show: true, + field: 'reaction_time', + sort: SortSymbols.NONE, + }, + { + value: 'resolutionTime', + locale: 'lookups.slas.resolutionTime', + show: true, + field: 'resolution_time', + sort: SortSymbols.NONE, + }, +]; diff --git a/src/modules/configuration/modules/lookups/modules/slas/modules/conditions/store/conditions.js b/src/modules/configuration/modules/lookups/modules/slas/modules/conditions/store/conditions.js new file mode 100644 index 00000000..d9c82f03 --- /dev/null +++ b/src/modules/configuration/modules/lookups/modules/slas/modules/conditions/store/conditions.js @@ -0,0 +1,52 @@ +import { + createApiStoreModule, + createBaseStoreModule, + createTableStoreModule, + createCardStoreModule, +} from '@webitel/ui-sdk/store'; +import ConditionsAPI from '../api/conditions.js'; +import filters from '../modules/filters/store/filters.js'; +import headers from './_internals/headers.js'; + +const resettableItemState = { + itemInstance: { + }, +}; + +const getters = { + PARENT_ID: (s, g, rootState) => rootState.configuration.lookups.slas.card.itemId, +}; + +const api = createApiStoreModule({ + state: { + api: ConditionsAPI, + }, +}); + +const table = createTableStoreModule({ + state: { + headers, + }, + getters, + modules: { + api, + filters, + }, +}); + +const card = createCardStoreModule({ + state: { _resettable: resettableItemState }, + getters, + modules: { + api, + }, +}); + +const conditions = createBaseStoreModule({ + modules: { + table, + card, + }, +}); + +export default conditions; diff --git a/src/modules/configuration/modules/lookups/modules/slas/modules/filters/store/filters.js b/src/modules/configuration/modules/lookups/modules/slas/modules/filters/store/filters.js new file mode 100644 index 00000000..a5e65c90 --- /dev/null +++ b/src/modules/configuration/modules/lookups/modules/slas/modules/filters/store/filters.js @@ -0,0 +1,25 @@ +import FiltersStoreModule + from '@webitel/ui-sdk/src/modules/Filters/store/FiltersStoreModule'; + +const filtersList = [ + { + name: 'page', + value: 1, + defaultValue: 1, + }, + { + name: 'size', + value: 10, + defaultValue: 10, + }, + { + name: 'sort', + }, + { name: 'search' }, +]; + +const filters = new FiltersStoreModule() +.addFilter(filtersList) +.getModule(); + +export default filters; diff --git a/src/modules/configuration/modules/lookups/modules/slas/store/_internals/headers.js b/src/modules/configuration/modules/lookups/modules/slas/store/_internals/headers.js new file mode 100644 index 00000000..2467ca7b --- /dev/null +++ b/src/modules/configuration/modules/lookups/modules/slas/store/_internals/headers.js @@ -0,0 +1,26 @@ +import { SortSymbols } from '@webitel/ui-sdk/src/scripts/sortQueryAdapters'; + +export default [ + { + value: 'name', + locale: 'reusable.name', + show: true, + field: 'name', + sort: SortSymbols.NONE, + }, + { + value: 'description', + locale: 'vocabulary.description', + show: true, + field: 'description', + sort: SortSymbols.NONE, + }, + { + value: 'calendar', + locale: 'objects.calendar', + show: true, + field: 'calendar', + sort: SortSymbols.NONE, + }, + +]; diff --git a/src/modules/configuration/modules/lookups/modules/slas/store/slas.js b/src/modules/configuration/modules/lookups/modules/slas/store/slas.js new file mode 100644 index 00000000..160041a4 --- /dev/null +++ b/src/modules/configuration/modules/lookups/modules/slas/store/slas.js @@ -0,0 +1,55 @@ +import { + createApiStoreModule, + createBaseStoreModule, + createCardStoreModule, + createTableStoreModule, +} from '@webitel/ui-sdk/store'; +import SlasAPI from '../api/slas.js'; +import headers from './_internals/headers'; +import filters from '../modules/filters/store/filters'; +import conditions from '../modules/conditions/store/conditions'; + +const resettableState = { + itemInstance: { + name: '', + description: '', + calendar: {}, + reactionTime: 0, + resolutionTime: 0, + validTo: 0, + validFrom: 0, + }, +}; + +const api = createApiStoreModule({ + state: { + api: SlasAPI, + }, +}); + +const table = createTableStoreModule({ + state: { + headers, + }, + modules: { + filters, + api, + }, +}); + +const card = createCardStoreModule({ + state: { _resettable: resettableState }, + modules: { + api, + conditions, + }, +}); + +const slas = createBaseStoreModule({ + modules: { + table, + card, + }, +}); + +export default slas; diff --git a/src/modules/configuration/modules/lookups/modules/sources/api/sources.js b/src/modules/configuration/modules/lookups/modules/sources/api/sources.js new file mode 100644 index 00000000..5a185b1b --- /dev/null +++ b/src/modules/configuration/modules/lookups/modules/sources/api/sources.js @@ -0,0 +1,145 @@ +import { + getDefaultGetListResponse, + getDefaultGetParams, + getDefaultInstance, + getDefaultOpenAPIConfig, +} from '@webitel/ui-sdk/src/api/defaults/index.js'; +import applyTransform, { + camelToSnake, + merge, + notify, + sanitize, + snakeToCamel, + starToSearch, +} from '@webitel/ui-sdk/src/api/transformers/index.js'; +import { SourcesApiFactory } from 'webitel-sdk'; + +const instance = getDefaultInstance(); +const configuration = getDefaultOpenAPIConfig(); + +const sourceService = new SourcesApiFactory(configuration, '', instance); + +const fieldsToSend = ['name', 'description', 'type']; + +const getSourcesList = async (params) => { + const fieldsToSend = ['page', 'size', 'q', 'sort', 'fields', 'id']; + + const listResponseHandler = (items) => { + return items.map((item) => ({ + ...item, + type: item.type.toLowerCase(), + })); + }; + + const { + page, + size, + fields, + sort, + id, + q, + type, + } = applyTransform(params, [ + merge(getDefaultGetParams()), + starToSearch('search'), + (params) => ({ ...params, q: params.search }), + sanitize(fieldsToSend), + camelToSnake(), + ]); + + try { + const response = await sourceService.listSources( + page, + size, + fields, + sort, + id, + q, + type, + ) + const { items, next } = applyTransform(response.data, [ + merge(getDefaultGetListResponse()), + ]); + return { + items: applyTransform(items, [listResponseHandler]), + next, + }; + } catch (err) { + throw applyTransform(err, [notify]); + } +}; + +const getSource = async ({ itemId: id }) => { + const itemResponseHandler = (item) => { + if(item.source.type) { + item.source.type = item.source.type.toLowerCase(); + } + return item.source; + }; + + try { + const response = await sourceService.locateSource(id); + return applyTransform(response.data, [ + snakeToCamel(), + itemResponseHandler, + ]); + } catch (err) { + throw applyTransform(err, [notify]); + } +}; + +const preRequestHandler = (item) => { + return { + ...item, + type: item.type.toUpperCase(), + } +}; + +const addSource = async ({ itemInstance }) => { + const item = applyTransform(itemInstance, [ + preRequestHandler, + sanitize(fieldsToSend), + camelToSnake(), + ]); + try { + const response = await sourceService.createSource(item); + return applyTransform(response.data, [ + snakeToCamel() + ]); + } catch (err) { + throw applyTransform(err, [notify]); + } +}; + +const updateSource = async ({ itemInstance, itemId: id }) => { + const item = applyTransform(itemInstance, [ + preRequestHandler, + camelToSnake(), + sanitize(fieldsToSend)]); + + try { + const response = await sourceService.updateSource(id, item); + return applyTransform(response.data, [snakeToCamel()]); + } catch (err) { + throw applyTransform(err, [notify]); + } +}; + +const deleteSource = async ({ id }) => { + try { + const response = await sourceService.deleteSource(id); + return applyTransform(response.data, []); + } catch (err) { + throw applyTransform(err, [notify]); + } +}; + +const SourcesAPI = { + getList: getSourcesList, + get: getSource, + add: addSource, + update: updateSource, + delete: deleteSource, +} + +export default SourcesAPI; diff --git a/src/modules/configuration/modules/lookups/modules/sources/components/opened-source-general.vue b/src/modules/configuration/modules/lookups/modules/sources/components/opened-source-general.vue new file mode 100644 index 00000000..97416ded --- /dev/null +++ b/src/modules/configuration/modules/lookups/modules/sources/components/opened-source-general.vue @@ -0,0 +1,63 @@ + + + + + {{ t('reusable.generalInfo') }} + + + + + + + + + + + + + + + diff --git a/src/modules/configuration/modules/lookups/modules/sources/components/opened-source.vue b/src/modules/configuration/modules/lookups/modules/sources/components/opened-source.vue new file mode 100644 index 00000000..64e93818 --- /dev/null +++ b/src/modules/configuration/modules/lookups/modules/sources/components/opened-source.vue @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/configuration/modules/lookups/modules/sources/components/the-sources.vue b/src/modules/configuration/modules/lookups/modules/sources/components/the-sources.vue new file mode 100644 index 00000000..d1a62846 --- /dev/null +++ b/src/modules/configuration/modules/lookups/modules/sources/components/the-sources.vue @@ -0,0 +1,208 @@ + + + + + + + + + + + + {{ t('lookups.sources.sources', 2) }} + + deleteData(selected), + })" + > + + + + + + + + + + + + + + + + + + {{ item.name }} + + + + + {{ t(`lookups.sources.types.${item.type}`) }} + + + + {{ item.description }} + + + + + deleteData(item), + })" + /> + + + + + + + + + + + + + diff --git a/src/modules/configuration/modules/lookups/modules/sources/enums/TypesSources.enum.js b/src/modules/configuration/modules/lookups/modules/sources/enums/TypesSources.enum.js new file mode 100644 index 00000000..20927759 --- /dev/null +++ b/src/modules/configuration/modules/lookups/modules/sources/enums/TypesSources.enum.js @@ -0,0 +1,10 @@ +const TypesSources = Object.freeze({ + CALL: 'call', + CHAT: 'chat', + SOCIAL_MEDIA: 'social_media', + EMAIL: 'email', + API: 'api', + MANUAL: 'manual', +}); + +export default TypesSources; diff --git a/src/modules/configuration/modules/lookups/modules/sources/modules/filters/store/filters.js b/src/modules/configuration/modules/lookups/modules/sources/modules/filters/store/filters.js new file mode 100644 index 00000000..a5e65c90 --- /dev/null +++ b/src/modules/configuration/modules/lookups/modules/sources/modules/filters/store/filters.js @@ -0,0 +1,25 @@ +import FiltersStoreModule + from '@webitel/ui-sdk/src/modules/Filters/store/FiltersStoreModule'; + +const filtersList = [ + { + name: 'page', + value: 1, + defaultValue: 1, + }, + { + name: 'size', + value: 10, + defaultValue: 10, + }, + { + name: 'sort', + }, + { name: 'search' }, +]; + +const filters = new FiltersStoreModule() +.addFilter(filtersList) +.getModule(); + +export default filters; diff --git a/src/modules/configuration/modules/lookups/modules/sources/store/_internals/headers.js b/src/modules/configuration/modules/lookups/modules/sources/store/_internals/headers.js new file mode 100644 index 00000000..b888b22b --- /dev/null +++ b/src/modules/configuration/modules/lookups/modules/sources/store/_internals/headers.js @@ -0,0 +1,25 @@ +import { SortSymbols } from '@webitel/ui-sdk/src/scripts/sortQueryAdapters'; + +export default [ + { + value: 'name', + locale: 'reusable.name', + show: true, + field: 'name', + sort: SortSymbols.NONE, + }, + { + value: 'type', + locale: 'vocabulary.type', + show: true, + field: 'type', + sort: SortSymbols.NONE, + }, + { + value: 'description', + locale: 'vocabulary.description', + show: true, + field: 'description', + sort: SortSymbols.NONE, + }, +]; diff --git a/src/modules/configuration/modules/lookups/modules/sources/store/sources.js b/src/modules/configuration/modules/lookups/modules/sources/store/sources.js new file mode 100644 index 00000000..790d650d --- /dev/null +++ b/src/modules/configuration/modules/lookups/modules/sources/store/sources.js @@ -0,0 +1,49 @@ +import { + createApiStoreModule, + createBaseStoreModule, + createCardStoreModule, + createTableStoreModule, +} from '@webitel/ui-sdk/store'; +import SourcesAPI from '../api/sources.js'; +import headers from './_internals/headers'; +import filters from '../modules/filters/store/filters'; + +const resettableState = { + itemInstance: { + name: '', + description: '', + type: '', + }, +}; + +const api = createApiStoreModule({ + state: { + api: SourcesAPI, + }, +}); + +const table = createTableStoreModule({ + state: { + headers, + }, + modules: { + filters, + api, + }, +}); + +const card = createCardStoreModule({ + state: { _resettable: resettableState }, + modules: { + api, + }, +}); + +const sources = createBaseStoreModule({ + modules: { + table, + card, + }, +}); + +export default sources; diff --git a/src/modules/configuration/modules/lookups/store/lookups.js b/src/modules/configuration/modules/lookups/store/lookups.js new file mode 100644 index 00000000..8aee8af3 --- /dev/null +++ b/src/modules/configuration/modules/lookups/store/lookups.js @@ -0,0 +1,12 @@ +import { createBaseStoreModule } from '@webitel/ui-sdk/src/store/new/index.js'; +import slas from '../modules/slas/store/slas.js'; +import sources from '../modules/sources/store/sources.js'; + +const lookups = createBaseStoreModule({ + modules: { + slas, + sources, + }, +}); + +export default lookups; diff --git a/src/modules/configuration/store/configuration.js b/src/modules/configuration/store/configuration.js new file mode 100644 index 00000000..6563ef84 --- /dev/null +++ b/src/modules/configuration/store/configuration.js @@ -0,0 +1,10 @@ +import { createBaseStoreModule } from '@webitel/ui-sdk/src/store/new/index.js'; +import lookups from '../modules/lookups/store/lookups.js'; + +const configuration = createBaseStoreModule({ + modules: { + lookups, + }, +}); + +export default configuration;
{{ item.priorities[0]?.name }}
{{ name }}