init commit
All checks were successful
build-api-image / build (push) Successful in 51s

This commit is contained in:
Antonio Lopes dos Santos
2026-08-04 16:52:02 -03:00
commit afec302c3e
11594 changed files with 1636728 additions and 0 deletions

27
frontend/.editorconfig Normal file
View File

@@ -0,0 +1,27 @@
[*]
charset=utf-8
end_of_line=lf
insert_final_newline=false
indent_style=space
indent_size=4
trim_trailing_whitespace = true
[{.eslintrc,.babelrc,.stylelintrc,jest.config,*.json,*.jsb3,*.jsb2,*.bowerrc}]
indent_style=space
indent_size=2
[*.less]
indent_style=space
indent_size=2
[*.scss]
indent_style=space
indent_size=2
[*.styl]
indent_style=space
indent_size=2
[{.analysis_options,*.yml,*.yaml}]
indent_style=space
indent_size=2

View File

@@ -0,0 +1,6 @@
VUE_APP_ENV=development
VUE_APP_ROOT_API=http://development
VUE_APP_WS_APP_KEY=1
VUE_APP_WS_HOST=127.0.0.1
VUE_APP_WS_PORT=6001
VUE_APP_DNS={"foo.com.br": "https://api-foo.com.br", "bar.com.br": "https://api-bar.com.br"}

View File

@@ -0,0 +1,6 @@
VUE_APP_ENV=production
VUE_APP_ROOT_API=http://production
VUE_APP_WS_APP_KEY=1
VUE_APP_WS_HOST=127.0.0.1
VUE_APP_WS_PORT=6001
VUE_APP_DNS={"foo.com.br": "https://api-foo.com.br", "bar.com.br": "https://api-bar.com.br"}

View File

@@ -0,0 +1,6 @@
VUE_APP_ENV=test
VUE_APP_ROOT_API=http://test
VUE_APP_WS_APP_KEY=1
VUE_APP_WS_HOST=127.0.0.1
VUE_APP_WS_PORT=6001
VUE_APP_DNS={"foo.com.br": "https://api-foo.com.br", "bar.com.br": "https://api-bar.com.br"}

24
frontend/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
.vs
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*
jsconfig.json
vue.config.js

5
frontend/babel.config.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

13841
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

76
frontend/package.json Normal file
View File

@@ -0,0 +1,76 @@
{
"name": "conectasus2frontend",
"version": "2.59.3",
"private": true,
"scripts": {
"build": "vue-cli-service build --no-clean",
"dev": "vue-cli-service build --mode development",
"lint": "vue-cli-service lint",
"serve": "vue-cli-service serve",
"watch": "vue-cli-service build --mode development --watch"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.35",
"@fortawesome/free-brands-svg-icons": "^5.15.3",
"@fortawesome/free-regular-svg-icons": "^5.15.3",
"@fortawesome/free-solid-svg-icons": "^5.15.3",
"@fortawesome/vue-fontawesome": "^2.0.2",
"axios": "^0.19.0",
"bootstrap": "^4.5.3",
"bootstrap-vue": "^2.21.2",
"chart.js": "^3.8.2",
"cleave.js": "^1.6.0",
"core-js": "^3.6.5",
"jsbarcode": "^3.11.5",
"leaflet": "^1.9.4",
"moment": "^2.24.0",
"popper.js": "^1.16.1",
"text-mask-addons": "^3.8.0",
"v-calendar": "^2.4.0",
"v-mask": "^2.0.2",
"v-money": "^0.8.1",
"validator": "^13.6.0",
"vee-validate": "^3.4.5",
"vue": "^2.6.11",
"vue-chartjs": "^4.1.2",
"vue-element-loading": "^1.1.1",
"vue-form-wizard": "^0.8.4",
"vue-router": "^3.1.2",
"vue-sticky-directive": "0.0.8",
"vue2-editor": "^2.10.3",
"vue2-leaflet": "^2.7.1",
"vuex": "^3.1.1",
"web-pki": "^2.15.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"pusher-js": "^7.0.1",
"sass": "^1.35.1",
"sass-loader": "^10.0.0",
"vue-template-compiler": "^2.6.11"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}

11
frontend/public/config.js Normal file
View File

@@ -0,0 +1,11 @@
// Placeholder de desenvolvimento.
//
// Este arquivo e copiado verbatim para o dist (nao passa pelo webpack). No
// container ele e SOBRESCRITO pelo entrypoint com o /var/www/frontend/config.js
// montado no deploy, que carrega os valores do ambiente.
//
// Vazio de proposito: com ele assim, `npm run serve` cai no fallback para as
// VUE_APP_* do .env.<modo>.local e o desenvolvimento local segue igual.
//
// O modelo a preencher no deploy esta em docker/config.js.example.
window.__APP_CONFIG__ = {}

BIN
frontend/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>ConectaSUS 2</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
<!-- Config de runtime. Precisa vir ANTES do bundle (que o vue-cli
injeta no fim do body) porque define window.__APP_CONFIG__, lido
por src/config.js. Trocado por ambiente no deploy, sem rebuild. -->
<script src="<%= BASE_URL %>config.js"></script>
</head>
<body>
<noscript>
<strong>We're sorry but ArchitectUI doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
</body>
</html>

28
frontend/src/App.vue Normal file
View File

@@ -0,0 +1,28 @@
<template>
<div id="app">
<rkm-notification />
<component :is="layout">
<transition name="fade" mode="out-in">
<router-view></router-view>
</transition>
</component>
</div>
</template>
<script>
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
export default {
computed: {
layout() {
return (this.$route.meta.layout || 'logged') + '-layout'
}
}
}
</script>
<style lang="scss">
@import "assets/styles/custom.scss";
</style>

View File

@@ -0,0 +1,199 @@
<template>
<div>
<rkm-navbar
:open="true"
class="rkm-navbar"
>
<rkm-navbar-nav-icon @click="$emit('toggle')" />
<rkm-navbar-brand class="ml-3">
<span>ConectaSUS</span> <span>v{{ packageJSON.version }}</span>
<rkm-navbar-nav-release-notes-button
:quantidade-notas-versao-novas="quantidadeNotasVersaoNovas"
@click="$refs.ModalNotaVersao.open()"
class="ml-2"
/>
</rkm-navbar-brand>
<rkm-navbar-brand class="ml-auto">
<rkm-navbar-nav-notification-button
:quantidade-notificacoes-nao-lidas="quantidadeNotificacoesNaoLidas"
@click="$refs.ModalNotificacao.open()"
/>
</rkm-navbar-brand>
<rkm-navbar-nav class="rkm-navbar-nav">
<rkm-nav-item-dropdown right>
<template slot="button-content">
<rkm-icon :icon="['far', 'user-circle']" /> {{ autenticacao.usuario.cidadao.nome }}
</template>
<rkm-dropdown-group>
<rkm-dropdown-header>Você está logado como:</rkm-dropdown-header>
<rkm-dropdown-text>
<strong>Unidade: </strong> <span>{{ autenticacao.lotacao && autenticacao.lotacao.infraestrutura.unidade.nomeFantasia }}</span>
</rkm-dropdown-text>
<rkm-dropdown-text>
<strong>Local de atendimento: </strong> <span>{{ autenticacao.lotacao && autenticacao.lotacao.infraestrutura.nome }}</span>
</rkm-dropdown-text>
<rkm-dropdown-text>
<strong>CBO: </strong> <span>{{ autenticacao.lotacao && autenticacao.lotacao.ocupacao.nome }}</span>
</rkm-dropdown-text>
<rkm-dropdown-text v-if="autenticacao.usuario.registroTipo && autenticacao.usuario.registroNumero">
<strong>{{ autenticacao.usuario.registroTipoId == enums.tipoRegistro.OUTRO
? autenticacao.usuario.registroTipoOutro
: autenticacao.usuario.registroTipo.sigla }}: </strong> <span>{{ autenticacao.usuario.registroNumero }}</span>
</rkm-dropdown-text>
<rkm-dropdown-text>
<strong>Setor: </strong> <span>{{ autenticacao.centroCusto && autenticacao.centroCusto.setor.nome }}</span>
</rkm-dropdown-text>
</rkm-dropdown-group>
<rkm-dropdown-divider />
<rkm-dropdown-item to="/lotacao">
<rkm-icon icon="sync-alt" /> Trocar lotação
</rkm-dropdown-item>
<rkm-dropdown-item to="/cadastro/usuario-perfil">
<rkm-icon :icon="['far', 'user-circle']" /> Meu perfil
</rkm-dropdown-item>
<rkm-dropdown-divider />
<rkm-dropdown-item
@click="handleLogout"
color="danger"
>
<rkm-icon icon="door-open" /> Sair do sistema
</rkm-dropdown-item>
</rkm-nav-item-dropdown>
</rkm-navbar-nav>
</rkm-navbar>
<ModalNotaVersao ref="ModalNotaVersao" />
<ModalNotificacao ref="ModalNotificacao" />
</div>
</template>
<script>
// Components.
import ModalNotaVersao from '@/components/intranet/cadastro/nota-versao/Modal'
import ModalNotificacao from '@/components/administracao/notificacao/Modal'
// Mixins.
import { repository } from '@/mixins'
// Others.
import packageJSON from '/package.json'
// Vuex.
import { mapActions, mapGetters } from 'vuex'
import tipoRegistro from '@/enums/cadastro/usuario/tipoRegistro'
export default {
components: {
ModalNotaVersao,
ModalNotificacao,
},
computed: {
...mapGetters({
autenticacao: 'getAutenticacao',
centroCusto: 'getCentroCusto',
lotacao: 'getLotacao',
quantidadeNotasVersaoNovas: 'getQuantidadeNotasVersaoNovas',
quantidadeNotificacoesNaoLidas: 'getQuantidadeNotificacoesNaoLidas',
}),
},
created() {
this.packageJSON = packageJSON
this.repositories = {
autenticacao: this.getConectaSusRepository('administracao.autenticacao')
}
},
data: () => ({
logout: null,
notification: null,
packageJSON: null,
enums: { tipoRegistro }
}),
methods: {
...mapActions([
'clear',
'setAutenticacao',
'unsetShowExitFormConfirmation',
]),
...mapActions('notification', ['setNotification']),
handleExpirationToken() {
if (this.autenticacao.expiration > 0) {
const date = new Date()
const expiration = new Date(this.autenticacao.expiration * 1000)
const diff = ((expiration.getTime() - date.getTime()))
if ((diff - (300 * 1000)) > 0) {
this.notification = setTimeout(() => {
this.showWarningConfirmation(
'Expiração da sessão',
`A sessão vai expirar em 5 minutos, deseja atualizar a sessão?`,
async () => this.handleRefreshToken(),
)
}, diff - (300 * 1000))
}
this.logout = setTimeout(() => {
this.clear()
.then(customer => {
this.unsetShowExitFormConfirmation()
this.$router.push({ name: 'login', params: { customer: customer.link }})
this.setNotification({ visible: false })
})
}, diff)
}
},
handleLogout() {
this.clearTimeouts()
this.repositories.autenticacao.logout()
.catch(this.handleError)
.finally(() => {
this.clear()
.then(customer => {
this.unsetShowExitFormConfirmation()
this.$router.push({ name: 'login', params: { customer: customer.link }})
})
})
},
handleRefreshToken() {
this.clearTimeouts()
this.repositories.autenticacao.refresh({
centro_custo_id: this.centroCusto.id,
lotacao_id: this.lotacao.id,
})
.then(({data}) => {
this.setAutenticacao(data)
this.handleExpirationToken()
})
.catch(this.handleError)
},
clearTimeouts() {
clearTimeout(this.notification)
clearTimeout(this.logout)
}
},
mixins: [ repository ],
mounted() {
this.handleExpirationToken()
},
}
</script>
<style scoped>
.rkm-navbar {
color: #ffffff !important;
}
.rkm-navbar-nav >>> .nav-link {
color: #ffffff;
}
</style>

View File

@@ -0,0 +1,66 @@
<template>
<rkm-sidebar :open="open">
<rkm-sidebar-list>
<rkm-sidebar-list-group>
<rkm-sidebar-list-group-item
:active="$route.path && $route.path.includes('dashboard')"
icon="home"
title="Dashboard"
to="/"
/>
</rkm-sidebar-list-group>
<sidebar-item :items="items" />
</rkm-sidebar-list>
<div class="footer">
<div>ConectaSUS ⋅ {{ others.moment().format('YYYY') }}</div>
<div>
<rkm-icon :icon="['far', 'clock']" /> {{ others.moment().format('DD/MM/YYYY HH:mm\\h') }}
</div>
</div>
</rkm-sidebar>
</template>
<script>
// Components.
import SidebarItem from './SidebarItem.vue'
// Others.
import moment from 'moment'
// Vuex.
import { mapGetters } from 'vuex'
export default {
components: { SidebarItem },
computed: {
...mapGetters(['getMenu']),
items() {
return this.getMenu
},
},
data: () => ({
modulos: [],
others: { moment },
}),
props: {
open: { type: Boolean },
},
}
</script>
<style scoped>
.footer {
background: rgba(0,0,0,0.1);
border-radius: 100px;
display: flex;
flex-direction: column;
height: 60px;
justify-content: space-evenly;
margin: 0.75rem;
padding: 1rem;
text-align: center;
}
</style>

View File

@@ -0,0 +1,49 @@
<template>
<div>
<rkm-sidebar-list-group
v-for="item in items"
v-bind:key="item.id"
class="sidebar-list-group"
>
<rkm-sidebar-list-group-item
:active="`/${$route.path.split('/')[1]}/${$route.path.split('/')[2]}` == item.link"
:icon="item.icone && item.icone.split('/')"
:title="item.nome"
:to="item.link"
class="sidebar-list-group-item"
/>
<rkm-sidebar-list
v-if="item.items"
class="sidebar-list"
sub-group
>
<sidebar-item :items="item.items" />
</rkm-sidebar-list>
</rkm-sidebar-list-group>
</div>
</template>
<script>
export default {
name: 'SidebarItem',
props: {
items: { type: Array },
},
}
</script>
<style lang="scss" scoped>
$primary: #007bff;
.sidebar-list {
transition: 0.02s 0.02s;
visibility: hidden;
}
.sidebar-list-group:hover > .sidebar-list-group-item {
color: darken($primary, 10%) !important;
}
.sidebar-list-group:hover > .sidebar-list {
visibility: visible;
}
</style>

View File

@@ -0,0 +1,5 @@
<template>
<div>
<slot />
</div>
</template>

View File

@@ -0,0 +1,206 @@
<template>
<rkm-application>
<template v-if="autenticado">
<Header @toggle="handleToggle" />
<Sidebar :open="open" />
<rkm-main :open="open">
<slot />
</rkm-main>
<rkm-toast
v-model="exportacaoBpa.visible"
color="success"
no-auto-hide
solid
title="BPA - Exportação"
>
O arquivo <a href="#" @click.prevent="handleExportacaoBpaDownload(exportacaoBpa.id)">{{ exportacaoBpa.arquivo }}</a> está pronto para download.
</rkm-toast>
<rkm-toast
v-model="exportacaoEsus.visible"
color="success"
no-auto-hide
solid
title="Atenção básica - Exportação"
>
O arquivo <a href="#" @click.prevent="handleExportacaoEsusDownload(exportacaoEsus.id)">{{ exportacaoEsus.arquivo }}</a> está pronto para download.
</rkm-toast>
</template>
</rkm-application>
</template>
<script>
// Components.
import Header from '@/Layout/Components/Header'
import Sidebar from '@/Layout/Components/Sidebar'
// Mixins.
import { file, repository } from '@/mixins'
// Vuex.
import { mapActions, mapGetters } from 'vuex'
// Others.
import Pusher from 'pusher-js'
import config from '@/config'
import { repositoryFactory } from '@/repositories/repositoryFactory'
export default {
components: {
Header,
Sidebar,
},
computed: {
...mapGetters({
autenticacao: 'getAutenticacao',
customer: 'getCustomer',
exportacaoBpaEmAndamento: 'getExportacaoBpaEmAndamento',
exportacaoEsusEmAndamento: 'getExportacaoEsusEmAndamento',
lotacao: 'getLotacao',
open: 'getSidebar',
}),
autenticado() {
return this.autenticacao
},
},
created() {
this.repositories = {
exportacaoBpa: this.getConectaSusRepository('bpa.exportacao'),
exportacaoEsus: this.getConectaSusRepository('atencaoBasica.exportacao'),
}
this.load()
},
data: () => ({
exportacaoBpa: {
id: null,
arquivo: null,
visible: false,
},
exportacaoEsus: {
id: null,
arquivo: null,
visible: false,
},
pusher: null,
}),
methods: {
...mapActions([
'setExportacaoBpaEmAndamento',
'setExportacaoEsusEmAndamento',
'setLoading',
'setSidebar',
'unsetLoading',
]),
init() {
this.connect()
if (this.exportacaoBpaEmAndamento) {
this.pusher.subscribe(`private-${this.customer.link}-bpa-exportacao-finalizada-usuario.${this.lotacao.usuarioId}`)
.bind('ExportacaoFinalizada', (data) => {
this.exportacaoBpa.id = data.exportacaoId
this.exportacaoBpa.arquivo = data.exportacaoArquivo
this.exportacaoBpa.visible = true
this.setExportacaoBpaEmAndamento(false)
})
}
if (this.exportacaoEsusEmAndamento) {
this.pusher.subscribe(`private-${this.customer.link}-atencao-basica-exportacao-finalizada-usuario.${this.lotacao.usuarioId}`)
.bind('ExportacaoFinalizada', (data) => {
this.exportacaoEsus.id = data.exportacaoArquivoId
this.exportacaoEsus.arquivo = data.exportacaoArquivoNome
this.exportacaoEsus.visible = true
this.setExportacaoEsusEmAndamento(false)
})
}
},
load() {
if (this.exportacaoBpaEmAndamento || this.exportacaoEsusEmAndamento) {
this.init()
}
},
handleToggle() {
this.setSidebar(!this.open)
},
handleExportacaoBpaDownload(id) {
this.setLoading()
this.repositories.exportacaoBpa.download(id)
.then(response => {
const file = response.headers['content-disposition'].split('filename=')[1]
const blob = new Blob([response.data])
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = file
link.click()
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleExportacaoEsusDownload(id) {
this.setLoading()
this.repositories.exportacaoEsus.download(id)
.then(response => {
const file = response.headers['content-disposition'].split('filename=')[1]
const blob = new Blob([response.data])
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = file
link.click()
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
connect() {
if (this.pusher === null) {
this.pusher = new Pusher(config.WS_APP_KEY, {
wsHost: config.WS_HOST,
wsPort: config.WS_PORT,
forceTLS: false,
enabledTransports: ['ws'],
authorizer: (channel) => ({
authorize: (socketId, callback) => {
const repository = repositoryFactory.get('conectaSus.websocket')
repository.login({
token: socketId + ':' + channel.name
})
.then(response => callback(null, { auth: response }))
.catch(error => console.error(error))
}
})
})
}
},
disconnect() {
if (this.pusher) {
this.pusher.disconnect()
this.pusher = null
}
},
handlePusherConnection() {
if (this.exportacaoBpaEmAndamento || this.exportacaoEsusEmAndamento) {
this.init()
} else {
this.disconnect()
}
},
},
mixins: [file, repository],
watch: {
exportacaoBpaEmAndamento() {
this.handlePusherConnection()
},
exportacaoEsusEmAndamento() {
this.handlePusherConnection()
},
},
}
</script>

View File

@@ -0,0 +1,226 @@
<template>
<div id="body">
<div id="content">
<div id="content-inner">
<div class="login-container">
<rkm-col class="login-aside">
<div class="wallpaper">
<div class="aside-content">
<div class="aside-title">ConectaSUS ⋅ Sistema de Gestão de Saúde Pública</div>
<div class="aside-city">{{customer ? customer.nome : null}}</div>
<div class="aside-actions">
<rkm-button><rkm-icon icon="headset" /> Dificuldade pra entrar? Pedir ajuda</rkm-button>
</div>
</div>
</div>
</rkm-col>
<rkm-col class="login-right">
<div class="login-header">
<img
:src="customer && customer.logotipo ? require(`@/assets/cliente/logotipo/${customer.logotipo}`) : null"
width="140"
/>
</div>
<slot />
<div class="login-footer">
<div
v-if="customer && (!customer.ativo || customer.motivoInativacao)"
class="motivo-inativacao"
>
{{customer.motivoInativacao}}
</div>
</div>
</rkm-col>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters({customer: 'getCustomer'}),
},
}
</script>
<style scoped>
.login-container {
display: flex;
align-items: center;
height: 100vh;
justify-content: space-evenly;
box-shadow: -10px 0px 70px -80px rgb(0 0 0 / 50%) inset;
background: var(--primary) url('~@/assets/images/login/medical-2.jpg');
background-blend-mode: multiply;
background-size: cover;
background-position: center center;
}
.login-container:before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.6);
z-index: 0;
}
.login-aside {
flex: 0 1 auto;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
padding: 0;
margin: 0;
width: 450px;
}
.aside-actions .btn {
background: rgb(255 255 255 / 21%);
border: none;
border-radius: 0.25rem;
padding: 0.5rem;
position: relative;
height: auto;
color: #FFF;
font-weight: 500;
text-transform: uppercase;
font-size: 0.8rem;
}
.aside-title {
font-weight: 600;
font-size: 1.2rem;
}
.aside-city {
font-weight: 100;
font-size: 4.5rem;
line-height: 1;
}
.aside-content {
text-align: center;
}
.aside-actions {
display: none;
}
.wallpaper {
color: #FFF;
font-size: 2rem;
line-height: 1.2;
text-shadow: 0.5px 0.5px #282a2c;
flex: 1;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.login-right {
align-self: start;
justify-content: space-between;
align-items: center;
display: flex;
background: #FFF;
flex-direction: column;
flex: 0 1 auto;
padding: 0;
margin: 3rem 0;
width: auto;
border-radius: 2rem;
height: calc(100% - 6rem);
width: 35vw;
}
#body >>> .login-right > * {
width: 100%;
}
.login-header {
width: 100%;
padding: 60px 10px 10px 10px;
text-align: center;
}
.login-footer {
background: rgba(255, 255, 255, 0.04);
border-top: 1px solid rgba(255, 255, 255, 0.07);
padding: 10px 5px;
text-align: center;
width: 100%;
}
.motivo-inativacao {
color: red;
font-size: 20px;
font-weight: bold;
}
.btn svg {
margin-right: 5px;
}
#body >>> .login-form h1 {
font-size: 1.8rem;
line-height: 1;
margin-bottom: 2rem;
font-weight: 400;
color: #6c757d;
}
#body >>> .login-form h1 span {
color: var(--primary);
font-weight: 200;
text-transform: initial;
letter-spacing: 0px;
}
#body >>> .login-form-actions .btn[type="submit"] {
width: 100%;
height: auto;
}
#body >>> .login-form-actions .btn[type="submit"] {
width: 100%;
height: auto;
border-radius: 100px;
padding: 0.7rem;
}
#body >>> .login-form-actions .btn[type="submit"] + .btn {
background: none;
color: var(--gray);
margin: 0.5rem 0 0 0;
border: none;
text-align: center;
display: block;
box-shadow: none;
}
#body >>> .login-form {
padding: 0 60px;
}
#body >>> h5 {
font-size: 17px;
}
#body >>> h6 {
font-size: 12px;
opacity: 0.5;
margin: 6px 0 0 0;
}
</style>

View File

@@ -0,0 +1,74 @@
<template>
<div id="body">
<div id="content">
<div id="content-inner">
<rkm-application>
<template>
<rkm-navbar
:open="true"
class="rkm-navbar"
>
<rkm-navbar-brand class="ml-3">
<span>ConectaSUS</span>
</rkm-navbar-brand>
</rkm-navbar>
<rkm-main :open="open">
<slot />
</rkm-main>
</template>
</rkm-application>
</div>
</div>
</div>
</template>
<script>
// Mixins.
import { repository } from '@/mixins'
// Others.
import packageJSON from '/package.json'
// Vuex.
import { mapActions, mapGetters } from 'vuex'
export default {
computed: {
...mapGetters({
open: 'getSidebar',
}),
},
created() {
this.packageJSON = packageJSON
},
data: () => ({
packageJSON: null,
}),
methods: {
...mapActions([
'setSidebar',
]),
handleToggle() {
this.setSidebar(!this.open)
},
},
mixins: [repository],
}
</script>
<style scoped>
.rkm-navbar {
color: #ffffff !important;
}
.rkm-navbar-nav >>> .nav-link {
color: #ffffff;
}
.rkm-main {
padding-left: 0!important;
}
</style>

View File

@@ -0,0 +1,74 @@
<template>
<div id="body">
<div id="content">
<div id="content-inner">
<rkm-application>
<template>
<rkm-navbar
:open="true"
class="rkm-navbar"
>
<rkm-navbar-brand class="ml-3">
<span>ConectaSUS</span>
</rkm-navbar-brand>
</rkm-navbar>
<rkm-main :open="open">
<slot />
</rkm-main>
</template>
</rkm-application>
</div>
</div>
</div>
</template>
<script>
// Mixins.
import { repository } from '@/mixins'
// Others.
import packageJSON from '/package.json'
// Vuex.
import { mapActions, mapGetters } from 'vuex'
export default {
computed: {
...mapGetters({
open: 'getSidebar',
}),
},
created() {
this.packageJSON = packageJSON
},
data: () => ({
packageJSON: null,
}),
methods: {
...mapActions([
'setSidebar',
]),
handleToggle() {
this.setSidebar(!this.open)
},
},
mixins: [repository],
}
</script>
<style scoped>
.rkm-navbar {
color: #ffffff !important;
}
.rkm-navbar-nav >>> .nav-link {
color: #ffffff;
}
.rkm-main {
padding-left: 0!important;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@@ -0,0 +1,449 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="727px" height="638px" viewBox="0 0 727 638" enable-background="new 0 0 727 638" xml:space="preserve"> <image id="image0" width="727" height="638" x="0" y="0"
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAtcAAAJ+CAYAAAB4nSx7AAAABGdBTUEAALGPC/xhBQAAACBjSFJN
AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAA
CXBIWXMAAA7DAAAOwwHHb6hkAABh7ElEQVR42u3dfVRU16H//4+KgICIgIpPGDQaEkMiF5cptYiJ
IY1PaatXG1NT8faKLpMmaUP1pv2m/mxuk8aQ2zw1X8XvXZKnmsjVtImQJsQkyLU0LikaNMEkaiSi
qDyJiDyI/P6YM8MMz8iBM8D7tRZrec7Zs2fPbGfmM3v22WdAQ0NDgwAAAAB02UCrGwAAAAD0FYRr
AAAAwCSEawAAAMAkhGsAAADAJIRrAAAAwCSEawAAAMAkhGsAAADAJIRrAAAAwCQeHSl04POTVrcT
AAAAsMz0myZ0qFyHwrUkTbkuxOrHBAAAAPS4L78p6nBZpoUAAAAAJiFcAwAAACYhXAMAAAAmIVwD
AAAAJiFcAwAAACYhXAMAAAAmIVwDAAAAJiFcAwAAACYhXAMAAAAmIVwDAAAAJiFcAwAAACYhXAMA
AAAmIVwDAAAAJiFcAwAAACYhXAMAAAAmIVwDAAAAJnGrcF1+oVKf7Mu1uhkA+pL6s/ry6FmrWyFJ
Kj16VKX1VrcCAPqGg4e/csvc6GF1A+zKL1TqueQdkqSAYX6advNkq5sEoNe7pMynVus3H0nRj23T
M3HDLWtJZeYTumdjjhSdoHd+P0+B3Xln9WU6nPGGtr+5V5kFtdIQX938vXlatfonimp6x5XHtfv/
/ZeSM06p9LKnAiO+o4RHVmtBmG/n6+vM/QJAF/3lvSzHv2fPjLS6OQ5uMXLtHKyn3TyZYA3AJJ4K
mjBKnkNG6eaxfta2ZMQ4TRniqSnh49TdLTn86mqt2bRXeZ7hWvSvUbrZr06HM1L18C+TdfiyU8HL
n+mlB3+pP7xzTsHRd+reuJGqzdurPzz4O+0u6nx9Hb5fADDBIwlLJUmf/D3XrUawBzQ0NDS0V+jA
5yc15bqQbmlA02D9w7kxVj8nANCrHX7raX0T9aAWXG+MPtef1e7frNYf9ktxG97Qhljb/i//vFL/
9v/KdPPPX9LmH42TJJ1JW68lzx6V54/W628/j5ZnJ+rraDkAMItzjpz93chuG8H+8psiTb9pQofK
WjpyTbAGAPPd/K/rGwOuJA0ape/OvkGSVHm50th5VNm7yyRFaVncOEfR0XE/1CJJtW/nKK++M/V1
vBwAmCVgmJ/bjWBbFq4J1gB6wuEtP9T37vihXspz2nk+R7ue+63+7cdL9b07fqjvzf+J1jyXrm8u
t3y7Mx/9l9b8eKm+d8dS3fPwfymzqOm9XNI3H72k38Qb9f1opR5+Ll1fOufJvGTbsS2fGTvOavd/
/FDfu2O9dheVKXvLg7rbuZ31ZTr8zn/pYXuddyzV8l/9l3Z/fan9Bz2o+a7i0lPGMU/jOTimw0WS
bgjXBOd5Kp4TNSVSkr7VmdJO1NeZcgBgIncL2JaEa4I1ACsd3vWEXsqu0c1336kV98/TopvqdPid
ZP3bs3vVdHz15JvrtfLNS7r57lmKu14qzdur3/zHNn3pWPXjkg5veVDL/3Ovzlw/Syvun6cVscN1
9J1k/duDzuVa981f/6BfvXWq8b7rz2r3b1drzXN7lXc5TIvun6d740aqOGev/pCwUn/437LOPeCi
dG1945KkW3TXDOOkzvOnlCNJw3zl37T8YEk6qm+KOlFfV8oBQBe5U8C2ZLWQ8oqLjU+Gv7UnGQHo
f8bc9V96598nys8+0lofJc+4J/TmR5nKeWSWYp3elrLrv6vU//sDjR4k6adxuu7+9dpa8KGyT6zU
lOul2rw3tPGtMkU/ukXPzB/luF1s0E/0b9v+qt05y/XLGYPbaM0J7cr5jp5562lFj7DtOfPOE/pD
dq08oxP0xu/m2e5bUsKCZP3bw+na/fJftCh6paYMUvtKs/Vf/5Gs7Mueil73gOKG2R+zVHstT15r
9V1rOQDoYywJ19eNH60fzo3RX97L0sEjX0lyryVUAPRtgaGj9M3BdO3+LF95+4/qZNFZfSNJqtTF
y5Lzch6LFjWGWw26QTd/T9L/XLKVk/TlgQ91RtKZZ1fre882v68zpaWSRql1tYq+d6UjWEtlOvzp
KUm+WrHS6b4leUb8QPdGpusPuY3hvi21J/6qjb/cpswLnrr535/Q7+8epY7zlNega6uva/cLAJ3X
dFaElbnSsnWu7cvtEbAB9Kj6s9r925/rD9lSYESE7ppxp2bfIn3yqzeU2UJxzyGuo85NA6dqbeO/
sWsf16Kw5rf3GtveAs8TFX2T85SJWtXUS9I4BTUb7R2l66ZIym0M962pPJisNb9M1zcargUb/qD/
iG0ScEeMUpSknLo61TS9cZ0khWn0iE7U18lyAGAWd5tubOlFZAjYAHpabc4b+kN2rXT3L/U/62bJ
dprdZzra1YoDJyoq6lrmFQ9u8URA6ZRKLkga4bzvrL75UpJGaUxb0yy+3mYLuENu0C9f+q0WhbWw
BN6IME0eIuXkHtM3tdJo+/mGtcf1Za6kkHBNGdGJ+jpTDgBM4m7BWnKDi8jYn4jyC5U6eMQ9L2MJ
oO+oLDUuhe7nK0eezPuH/nGN9V0XHiVJynw11fVCKfWXdDgzR6WdrnGUor47TtIlvbItXWecTois
zfur3syVFPod3Ty2tduXKeONv+ob+ere37cRcAfdqOi7PSXt1a6MxsvDn8n4i3ZJum7BdzWlM/V1
uBwAmMMdg7XkJpc/bzqCfV1oiK4bP9rqZgHogwJvuEXX6ai++Z+n9XDtnbrZ86yyDx6/5vr8vpeg
/4j+uf6Qna41932muLhbNGZIpb782z+UHRavd2I7X+fouxO0avd/amt2sn5yX6YW3D1JnkWfaXfG
KVUOGaUVjy5t/WTGyjz9PVOS6vTBy7/UP5uWu36Jkh69U4EarKh74xX9t2RlP/tzrflslm5WvnZn
nJJC5+k/fnRD5+rr8P0CgDncMVhLbhKupcaA/cnfcwnWALpP2E/0zIaz2vjyP5Tzzoc6ET1XG/4w
T9n/+oRxUmMnDRqlBb97UaP//Cf9aXe+Mv4nXRriqylRs7Thp7HXFiY9b9GK//uibv6fZG3dladd
rx2Vhvjq5rglWvXvP1HUiDZue7nSWNKvVqVfn20+cj7MaY2QEfP0TMoovfLiS9qe8aEOD/HVzfck
6Jf/Pk9ThnSyvs7cLwCYYPZ3I1VeUelWwVpyg8ufA0D3uaTM//yJfvORdO/zf9GDEVa3BwDQG/Wa
y58DQLe6/JlysiUpShFhXa0MAID2uc20EAAwS+lH/6UX86XKzH8o+7J03f0/UDTXqwIA9ADCNYC+
Z9AlZf9PjiqHDVfsyvV69L5bHCuDAADQnQjXAPqcwNjH9bePrG4FAKA/Ys41AAAAYBLCNQAAAGAS
wjUAAABgEsI1AAAAYBLCNQAAAGASwjUAAABgEsI1AAAAYBLCNQAAAGASwjUAAABgEsI1AAAAYBLC
NQAAAGASwjUAAABgEo+OFqy5VGF1WwEAAAC31uFw7R8QaHVbAQAAgJ53vqrDRZkWAgAAAJiEcA0A
AACYhHANAAAAmIRwDQAAAJiEcA0AAACYhHANAAAAmKTDS/EBANzL5/v+V5+VSd7jbtAPp42wujlA
j2v2Gjh/RH/5tEzV8tUtCyJ1k9UN7HNO6MPdhSqWhybc9h1F87bTIsI1upH9RcibHADAXGcP/kOf
lRHy4H4I1+g+56tUaXUbgD7sppnf40sr+qnzKvGbpHsXkKrhfnp5uD6v7IyjOlnTuCc4/Hu68/qW
jrmOnp49+A99fOqKJMl73FiNOl+okzXOt0eXfJ2rN/MvGRuX9Nnu/9WX9p/tXI45/6Tt9HPTOC+d
PHWJn7tN0Ob/9WHNf0J1lB8+VvfODGvWXxKvE3PY/79Lkq9uCZc+y78keQ3X7XFTNcr+vNu31fwn
cKaFmOkaPk8cUxAa+6Dx9cYvdl3W9L3HeC3I6T3ts/yjUmtTFJz6x/l1hM6xv89IUnD42BZKOL+X
2cs1fkY4v09NuXTUqMv2+ghy+XzqO+9jvfqExs/3ub4RNjqhD3c3PXZJn2Uc0Vm5hg1Jqj5V2Eo9
MNvZg/9oFtSqTx3Vh18777mik6cudapetMyM/+tnK5vfoDj/H8o+b/Wj682afhhdsgVrWOaaPk9G
TNUPw30lSdWnTulznVDeqSuyhT2CdZecP6K/tPKaKLl0pcmeKzr5aa4+d9l3SZ/Zg7Uk1ZTp430n
rH5UvY5zsJak4nzXEK3zR/SXJsHaVu5/m3yu2z7rG+u6pM8y/qFsl8+nU036sPfq5SPXhqbfSL8u
t3W0Y7991OGijp8/L51v8i3J+dstzHF9pO5tNip6XtkZtufe8a3WGJkoPntCchoJ7UvfYK1zXseb
/l9vYYShPaOmfUf3Tmus0/ZauqJLFyTRRdfG/h7V0i8GsFanPk+kUddH6paz/6vPyi7ps922MOg9
bhJzgE3U9JeyUS7ToezvaTW64PKF32k02z4CXlauzyW+9HTYCZ12jFgbfdAkL50tvNjslwH7e1nx
iSM6e/3UxuqMX0Md73U1Xpqy4DtOvwAZfdgHXju9OlzfNGW4vvy0TNU1Zfp49//K/mKaaB9pc+x3
VqlLNbaHPmqs0YMjRmqUVxmj193O/tzbvtW+md9aOae+QRe08H9dfvL1koo78X+d0Gc+x68BwwMc
H/Sj/Lwk8Txb5do+T5rcVpLkqykMDHTdiKmaMtw2aur4vDDCWdPR1JZ5aZi9G4Z5yluXGEDrLMd5
U74aY/9yM8JHfmoM1/ZfEbxHjHR8IW3tvczb18/1uJenglqosy/o1eFaI6bqhwsk59G0kwePaLj9
BdXiHKsT+lCSdEVnC89LI0ZI58/pLMG6R7U8Z5ef7LpHR/6v20cMGke7bYyfuR2vpc6PfKMNTiNp
n59tZVpITa1KJI1yGkVCN7imzxObz790DgaX9Nm+E7ppZpjVj6jXc5yw6xh5LtSHh6pUWabGc0Ja
/eW5cRS0cXTVCHPopEs6/bV00/Vy+tXNJsjXQyq7ourz53RWI2wj15UEql4drlv89urjo/BpPjp1
qlDFTUYabD+Nh2nM8EIVl9nm/7x56mgLNTeevMKJW2ZoPKExepyH7ecil5Hr1k/84YStrujA/3XH
iMEVnfz0f3VSkreXh5qNOrQ2aseastdk1Nih8j5VpmrjtfFZS4Uco232Mh7y9pLU6ucW71tdcW2f
JyOM5eBkC9/TpOxPy1RddlbZ58MUPYI+uWYthmYP+frKNppaVqg3dxe2UUHje5qdfXSVz5UOcvpV
v7Vfmx3vZS18RgSH2b6MlnT6jnv/66ZXn9DYjNdw3T4zTFKY7lwwVsGtFLtp5g2a4NW4HRzeell0
wYipmjLcddeoad/R7eN69Xe6XqX5/3XXbSlMdxonZEmSho9tMlc0TBFO/dX89rgmTifCSbK9dzlv
G2WinZ5773GTNMXH6ob3Ix35PPk61zFlKjhsqkY5+qylE+zQVcHh31H09c6fKx6acFtrn9++uqXJ
extBurNGKDrO+flt4fkeMVU/bPb68NCE23pnKDbLgIaGhob2Ch34/KQiJo+zuq3dpnFOKYvRo6/r
/SMCfVYLS+8BANxD3lenNP2mCR0q2z+HEFtYt1eSNHwUwRoAAADXrH+G6xYwigcAAICuYloIAAAA
0IbOTAvpWyc0AgAAABYiXAMAAAAmIVwDAAAAJunwCY0V5aVWtxUAAABwax0O1yNGsEadu3g/K0ff
j4myuhlwQp+4H/rE/dAn7uf9rBzdNoM+cSef7qdP3NL5kx0uyrQQAAAAwCSEawAAAMAkhGsAAADA
JIRrAAAAwCSEawAAAMAkhGsAANC6ol16JGGXiq1uB1S8a62mhkcYf2uVVmR1i9ASwjUAAGjRwWci
NHX2BmVY3RBIOqSdx1brSH6e7e+NiVo3O0kHrW4WmiFcAwCAZg4+E6Gf6HUdeWOF1U2BJOlWrf7V
rY2bUfHaNOsVfZpjdbvQFOEaAAA0M+1XeTriHObgZgp1am+Mxo21uh1oinANAADQyxTv2qIXZt2p
20Ksbgma6vDlzwEAAGC94l1rFfvriXojf5GCrW4MmiFcAwAA9ArFSku4Xesmv64j+UzZcVeEawAA
ALdnC9anVufpSJTVbUFbCNcAAADuLidF67RRmQRrt0e4BgAA6A32blBs+AbXfT97nVVd3AzhGgAA
tC4qUUeSrW4EFJWoI/mJVrcCHcBSfAAAAIBJCNcAAACASQjXAAAAgEkI1wAAAIBJOKERAAC4uPLB
e6rbulkNF8qtbkqfNGBYgAavWiOPu+Z2+DYffX1Jr+WUqaL6qtXN77P8vQfq/qjhuuN63y7Vw8g1
AABwQbDuXg0XylW3dXOnbkOw7n4V1Vf1Wk5Zl+shXAMAABcE6+7X2eeYYN0zzHieCdcAAACASQjX
AAAAgEkI1wAAAIBJCNcAAACASQjXAACgHes0MvNVBXTjPfg9l6aQ535sSl0Br2W2WlfAa5kaub4b
H0iPCNS2hFDtafY3WoktFY8Yqb/Gj9SSa7y3JQvGtVh34tJxSoq49keRuDRU22KteQa7E+tcAwCA
PqX8fqfEtv5Vjbn5sE7fv8nqZpmoVCuTSyXZgu9yVegHuyu69R4rawdp7tJAJe0o7fmHGzFSf42S
Xk85p9Sev/dOY+QaAAAAbSuuUq6PT5dGqvsLRq4BAMA1CXgtU56HY3Xu6ebbtn+nSfPm28JGQVrj
6PHSPyrkgX8xRvhOqir2p7oiSZqgoLRMeflJqvynKub/QpUu9/hjBaX9VHplvkp2yDYqPavEUS7g
tTQNene+6hca7dCrGjNvgqQJGpMZq5o/zVe9JNs0lxba1dvFjtaeGwYbG1eVm31KiXlNykSM1F+j
vaXT5bbRbufb1FZrc6ujw1eUeKJee6JGakleS2X8lRQfoEhPY7O8UnN2NI6urxljH8+t03vJZ5Tk
uF2gtiX4KbTJbZq210/SmoRQzTtaoJWZtiklcwPUgXb3PEauAQBAt/CYd7NqY2N1OjZNV0JjFbRU
ktZp5ANTVPenWJ2OjdXp2J+q3Cg/MDJWeiVWp2NfVo3+RT7N5ka/pfpSPw0Ks20F3Bykq5oiL6Ne
z9AS1e9wKv70T3U6/aQtQMcagbzVdvUBmWc0J7nA9ne0XpE3BjYpEKht0Z76KrugMViPqnHcZnOx
p5Yv8G+z/veqvFsoYwvWk4vLHXW9Jz/91SiXuvtU432cHqQYp9uH3uClI8kFmpNcqYKAFkbG887p
B9nVqqyt1uZkp2CtSpd2r1kaKHdBuAYAAN3iSro9OG9SbYERitffLI+CTEfQdXY191Vj/1uq+apS
A0c3Pymx/PBJedy8TtI6eQZ+qeqvZKt36WgNLDjsCOqdblef4K+keOPkxhsGSz4eTicxemp5go9K
nEazlwwdJAX4OU6IXDNmoPx82p7UkLSjUqVjfJuc3OihIFXrdad530lfVEvB3sb9N56A2fQ+Co7a
R7FLdaR8oILazcj+CvGp03tOI9ypu6tUEODV8smcFmBaCAAA6DF+o4N0teTktVfw9GFdybxZAUtL
pK/+ofK/SyEL18lv9BRdPfwLqx+ehWyjx0EnCjQnU46TAB08JdU2D6+V9ukhHVaq9NPjtGZpoN7r
UPlAbUvwUUl2gVbmyRgtt/q56l6MXAMAgGtypaTSGEWWbNMy2r9N5d+/lCJ/2oWpGJtUWxAkz4VB
qv/7W9KOf6guMFZ+k0tU+7TVz4iVPBTkeVUlxoDukgme8nM+XFur11MqpRsal79LvVgvv2aj0O1L
3V2hXB8fxfjY91xRiVyniyTe6C0VVys1wkOBqleRMVqeOGpwJ++tqQoVVQ3WXKdpIEsW+Ci0vMZp
Hre1GLkGAAAdMEE+mZmy56kr6bE690imfDLna0zmfEkndaWgA9Xs+IWKwl7VmAcyNeYByfWExo4p
P1yiMbNKVOWYQvJTeQW1MiXk6cO6kjm/yQmNfVGp0k/7aE10qPZES5XldU1OBrWVWZks2xSNUZWa
s+OMNg8dpzUJoZprlCgwThhsW4UST/hqzw2DGrdTPLQtIUB7EgJsu8orNWd3haQKZd0YqrnGfRSU
13X+oeVV66uogMYTGneUKyQ+QHsSjK8PtdXanGLBEoGtGNDQ0NDQXqEDn5/U9JsmWN1WGN7PytH3
Y6K6XhFMQ5+4H/rE/dAn7uf9rBzdNqN5n1Td1Qev7OGGfD5onmI/3d9ynyx+pSPfXGCGnSua/wTz
9bGOZ2GmhQAAAAAmIVwDAAAAJiFcAwAAACYhXAMAAAAmIVwDAAAAJiFcAwAA9DYRI/XX+JFOV2C8
VoHaljC6S1c3TFzauHb2tdzmWm7vzgjXAAAA6LjY0dqztN3rlPdbhGsAAADAJFyhEQAAtG3pHxWy
Qqr+aop8Iv2kyn+qYv4Z+WTOtwWJgjSdvn9TY9kH/sUxenclPVbnjMuSB7yWKc/DadI82+2upMeq
anSa/CP9JFWq5k/zVbLD6gfbu0xYME57xtie7carKwZqW4Kf7JdCqTxdrh/srmiy/6pys08pMc+5
NuN4eaXm7Ci1jVDfYFyuvLZam1POKdWxb7D2JPgoN/uUitTkPu1lJS1ZME5rxtj/N9TpveQzbnOZ
8u7CyDUAAGif37/IW6/qdGysqkr/Rf6ZN6s2NlanY9N0JTRWQUtlBOspqvtTrE4bxzQvzXbM4DHP
uN2f/qmB8zLlZ9RZkSt5LVxn9aPsXTy9FaMKzUku0JzsagXeYJ87XaqVyQW2/cmVKh3jq0RJSxb4
KPB0ubG/abD2V1K8n3S0oDFYj6oxyhZoc7Gnli/wlzLPaM7ROtvlzZ3qCL3BS0eMsu9VedvKSkrd
faqxjtODFGPs78sI1wAAoH2V/1TlI29Jkq6UVOpqbprKJUknVV9plAkLknJfdRp93qSqXGnwd3/s
qOZK+k9tt9txRld1UtVGnZVnSqx+hL1PbbVe311h+3feOWWVD9ZU48TAJQvGaU9CqPYk+ClUgxQS
IaVerJffmIAWTh4cpJj4AAWdsI98S0uGDpIC/Iw6QrVmzED5+bQ+4aHgaOOIdNIX1VKwt3GyZaC2
dbCOvqLvP0IAAIB+ZMmCcVrjU6U5yaWyjUgbo8WZZzQn07Y6x56E5tNCAof6S6pwbDdOJ7lWgdqW
4KOS7AKtzJMxGm71s9P9GLkGAADmOFEiRf7UaRrIOvlESnV/f6tT1QS8lqmR661+ML2Ap6duizD+
HTFSMQF1OpIpTfAZqMqqK8Z+b032dL1Z0o4CbT4tTZ5gn6JRr6yUcn0VHKC/2qdzXKyXnzGdpCNC
RzWuHpJ4o7dUXK3UCA8Fql5FRoBPHDXY6mesRzByDQAAzLHjFyoKe1VjHsjUmAdsu66kx3KSYnep
rVdQVKj2REv2ExSTJOmLasVEB2hPQoBUW6eCWltxl5MLa6u1OcV2kqNNhRJTpKT4AO2J99bmlDPa
PHSc1iSEaq5RwnHCZGaNChL8XE5oLJCX9iQYp1CWV2rO7gpJFcq6MVRzjToKyuusfsZ6xICGhoaG
9god+Pykpt80weq2wvB+Vo6+HxNldTPghD5xP/SJ+6FP3M/7WTm6bUbzPqm6qw9d0cON+XyQ2Wzf
p/tb7pPFrxRY3dx+Y+eK0Gb7vj7W8SzMtBAAAADAJIRrAAAAwCSEawAAAMAkhGsAAADAJIRrAADg
YsCwAKub0OfxHPddhGsAAOBi8Ko1hL9uNGBYgAavWmN1MzpsyYJx2rM0sOsV9ROscw0AAFx43DVX
HnfN7XpF6J0iRuqvUdLrKeeUKil19ymlWt2mXoSRawAAAMAkjFwDAAD0Nsboclaxp+YaV110XEHR
OPZVlbciA+r0XnKNpib4yX5plMrT5frB7orGeqK95SdJqtN72fWKMbbXJIRq3tECpQ8dp+WqcNwm
cWmo5gY0qatpPclnbFeL7IcI1wAAAL2Rp7diVK45yfZwO1qJmUao9fRW0IkCzbFfej651PhHoLYl
+CpRFUpSoLZFe+qr7AIl5jVWmyTXaSFLFjQeW7JgnOaqUnMc9alxGklygW36SOxo7VkaqKQdpeqP
CNcAAAC9UW21XrePQOedU9aNoZoaK6nUdizd6erqSxaM05ox9tnAV1USISnQS6HlVVqZ19E79Ndt
wVJuTpPQHDhIfp6DtSYhVI7TNGvrtUTql3O1CdcAAAB92JIF47TGp8oYbfZXUry/bf/QQaqsutKJ
mjwU5FmvIy2F8fJKzemnI9VNcUIjAABAb+TpqdsijH9HjFRMQJ2OZDYvNsFnYGOIjvDWZE/bP1NP
1kpj/JUU0aF7k1SqI+WDNbfpsnyl9aoM8OlEPX0bI9cAAAC9UW29gqJCtSdakq4qN/tUiycRJn1R
rZjoAO1JCJBq61RQaxzIO6cfBI7Wnmh7HcaJiHnV+ioqoPGERue6dpQrJD5AexJspy7aTmhsWk+T
kyb7GcI1AABAr1Sv9JQzzec1553TD/La2HaWeUZzmo12VygxxTkYO69z3fRYW/X0T0wLAQAAAExC
uAYAAABMwrQQAACA3qatqR6wFCPXAAAAgEkI1wAAAG7O35vI1hPMeJ7pKQAAADd3f9RwAnY38/ce
qPujhne5HuZcAwAAuLk7rvfVHdf7Wt0MdABfgQAAAACTEK4BAAAAkxCuAQAAAJMQrgEAAACTEK4B
AAAAkxCuAQAAAJMQrgEAAACTEK4BAAAAkxCuAQAAAJMQrgEAAACTEK4BAAAAkxCuAQAAAJMQrgEA
AACTEK4BAAAAkwxoaGhoaK/Qgc9PqqSk2Oq2AgAAAD0uKChY02+a0KGyHh2t9PsxUVY/Lhjez8qh
P9wMfeJ+6BP3Q5+4n/ezcnTbDPrEnXy6nz5xR18fO9nhskwLAQAAAExCuAYAAABMQrgGAAAATEK4
BgAAAExCuAYAAABM0sVwfUgvhEVorP1v5S6ds/oRodGBJI0NW6u3i6xuSH/X5HUSFqFVqSxtaT3X
fnnhgNXt6ccOJLm8Phr/kpRjddv6s6JdeiQ8QlONvy10hvVc+mSt0vh8d0sdXoqvRQcypNQ8FU6X
pGK9vfJ2/SZ1lrYuCbb6cUHFevtPr0iKsbohKDqmPK3QOycSxeJKbqJol1ZFb1CE4/0LlpqeqMIT
iS67cp6K0Obr43nNWOaQtszeoBvfyNNzUbKFutlrlfbJy5ofYnXb+qmiXXpk9oeK+yRPz4XYt5M0
Nj9R06xuG1x0beR6eqIecnwwBWvmvBilf11o9WOCJB1I0YNTVmi91e2AzeyJGm91G+CQs22DtOlj
p/cvuJWiXdqcvEJrGKixTtExfaEVus3+7SZkluJmZekUH/GWKf77h8r42erGLzchixT/s1eUsotf
Qt2NiXOuD+nNddJLK2+1+jFBh/TCkuN6aWWc1Q2BJJ06rnSr2wAnh7SP4ObWbF9+GLW2VMgsxc1y
Cm5Fe5Wx1ylswy2MmxSjjGN843E3XQ7XOU/Z58ZlaOaJl/Ujfi6yXM5Ty5W36Xf0hTv5ZIMi7fNI
nzpkdWv6t6Jjyps9UUpdy/ki7ohRazcRrPnJHyvub7fb5vfOPq54ph9YKvi7dyruv7c4zbM+pJ2/
zrK6WWhBl8N11GN5KjyRp8ITcdrHiVrWO5Cke77cqN/zweQ+picar5E8FZ74WC99uZyAbbVPNmiz
fufol3embFAkfeIWzmV9yKi1WzikLeG/lX6XpyP5eTqSH6dPOanRWiGL9NwbE7Vutv2ExgyNe5Lz
qtyRidNCbtVD2RuldSmc3W2Vol1ateS4XnpqkUZa3Ra0Ilg/emqj5iVn8DqxlOvIaNRK+sQ9FGtf
ujQvhsEBqxXv2qIXnOf36latfmOFXviIL6GWiko0vuzk6Uh+osYey1LcpLFWtwpNdG21ELiVc1kf
Kl1ZSo+O0IPOB6IjlL7pY1ZxcSec4GidkEmK0HGrW4GWFO1Vuu7U75nSBnTAIX363zGK+4TPdnfT
pZHrnKec1yAt1tuPbVB6Qhw/51lk5JKXnaYf5KnwxOtarxi9lJ1HsLbQudRdzV4nmjeLXxcsc6tm
Jryie5ymgeRs473LHZzL+lDpUybx2nADLc3v3fKTV/TQHSxa4C4OPrO8ya8LcBddGrkef/1xRYZF
NO5IeF2Fj/HCA1x9qHvCNji25vErguWiHsvTO09FaGyYsYP3Lrfw7ddZWh/3stXNgGSb3/uJ9Mjs
CK0zdsU9+bFtzWtY5JC2hC/XC/bNn72uI7/ifcsddSlcj1zysgqXWP0Q0Lpb9dAJPqisxuvEPUU9
lqfCx6xuBZxFPZbHrwfuJGSRnstfZHUr4HCrVufnabXVzUC7TDyhEQAAAOjfCNcAAACASQjXAAAA
gEkI1wAAAIBJWOca6EZ/O3pJyZ+W6UL1Vaub0icN8x6ohNuG6+4bfK1uCrog7duTeunzPJXX1ljd
lD4pwNNLD94UofnjJ1jdFKBfYOQa6EYE6+51ofqqkj8ts7oZ6CKCdfcqr63RS5/nWd0MoN8gXAPd
iGDd/XiOez+CdffjOQZ6DuEaAAAAMAnhGgAAADAJ4RoAAAAwCeEaAAAAMAnhGuhhSxaM056E0Ups
sj9x6TglRVx7vYlLQ7UttqUjgdoWP1JLrH7gQIdN1ot3L1b27VEtHv317a0fuxa/vn2xPoie3IOP
L0rbF96jFye2duwu/boHWwPAXIRrwAKVtYM0d2mg1c0A3FidLnoFNQ+ZE2M026tOF7tS9bS7XML5
kx/v1F3ZX/XgY8vRsnff0c+P27Z+fftibZ/Wg3cPoFsRrgErFFcp18enSyPVQF9XUuOt2U1GlO8b
NVwlF1jbHID74gqNgCWuKPFEvfZEjdSSvHNKbXbcX0nxAYr0NDbLKzVnR6kk27SSNWPs34vr9F7y
GSU5bheobQl+CnW5TalWprRwXFeVm31KiVxbAm6quKxMQSEhuk9f6c+SpCgtDK5W3rdDtNC7qrHg
tLuUPX6o7d915/Ti37Js5SfG6IOpI2U7clHvvvuBnnSUHarshWN14Mg7OjNhsSLKdmrZQdsockTZ
N9L463SdJFV+o+iPc5rfT+U5HfAaLn3ZOALtKDO8xLjNZL149y0KLrLVrYkx+mBCle76WNq+MEh5
734g3b5YC/0k+S1Wdsg5vfi3qmb39c23xu0B9AqMXANWyTyj96q8tXyBf5MDtmA9ubhcc5ILNCe5
QO/JT381yqXuPuXYv/n0IMU43T70Bi8dSS7QnORKFQQ0HxlfssBHgaft9RKs4eYqsvRJzUgtnGZs
TwtSUPEJPelcxh5m392p6Hd36sULwxUfPdkWZKdIKcb+6G+lhbdHSQc/UPS3F22h+d0mwdhw3fgg
5b27U9HvfqNv/Mba5kZPjNEH47114IhRX9kQTR/cUpsv66KXj+6TpIkhukF1Chpum4Jy36jhUnWF
S/EnP96pdyttATra/qVAQ7XQ/pi+vajrQmJs9QHoFQjXgIWSdlSqdIxvk5MbPRSkar2+u/FDOOmL
ainY2zgpMVDbEkK1JyFUa8YMlJ9P4w9QBUfto9ilOlI+UEFNpnWnXqyX35iAVk58BNzPk2UXdd3w
KEmT9WKIt46edZ0bfd8Qb8nvOmUvXKzshYv18+DBGurtL/kP0dDBI/VzY3/2+KGSPfS245tvPzAC
fI7yKgcr2F+2+ioLG8P4wRM6UNfCjY8X6aiGK3qiMYWlqFAlxv1e561m7W/ZRb1rHy0/WKJvBg+x
jaID6BWYFgJYqlTpp8dpzdJAvdeh8oHaluCjkuwCrcyTFDtae0Z14u4yz2hOpm1lkT0JTAtBL3Dw
hA7cfaNenOajG2oKdddxSdNci1ws/qz5CYnT7nKd0tFF9w3x1sUmo84t+0rZF25U/KjJOuNdrbzs
HOn2exQ9MUrBXmV693gHqgDQqzFyDVgsdXeFcn18FONj33NFJXKdLpJ4o7dUXK3UCA8Fql5FRiBO
HDW40/cnSUk7CrT5tDR5gv813R7oOV8p+4I0ffxIlZQ1D8p/vlytocFhzVcVqbisi/YpHSb489ky
yfl+poW1PC3EXtY7TBEq0ZOSniyr1g1TxiroQpEx7QNAX8bINWC5CiWe8NWeGwY1bqd4aFtCgPYk
BNh2lVdqzu4KSRXKujFUcxNCNVdSQXldp+7J5WTI2mptTunISBxgrT9nF2rh3T5692ALBw9+oBeH
3KOfL1yshcYu2wmAWbrL/y5lT12s7Km2/Y4R7oMl+mbhdY0nNHakEcezlDLK6X4qz+lAnXcrZYt0
dMotCi76wGhjiUoWjlVxK1NCniy7qOzxTU5oBNBrDWhoaGhor9CBz09q+k0TrG4rDO9n5ej7MeZd
QAFd11qfzEkusLpp/cKehNBm+3iduJ/W+iT63Z1WN+0aRDlW/Hiy65X1iOyFi5vtez8rR7fN4HXi
Tj7dT5+4o6+PdTwLM3INAEAn3Rc9VtdVFmqZ1Q0B4HYI1wAAtCtK2xde17hqR905vfg3c06WBNC3
EK4BAGhXjpa9S5gG0D5WCwEAAABMQrgGAAAATEK4BnpY4tJQU66QuGTBOMcl0a9JxEj9NX6kcdXH
jgrUtoTRtitKXtPtAfd2X/Q9+iB6suPf2bezagOAziFcA/2Gv5Lixykpwup2AL3Dn7Pf6doVHqfd
RTgH+iHCNQAAAGASVgsBLBGobQl+CpWMKyWeU6qaXEFRdXov+YySmu4vr9ScHaUutdmOS7nZp5SY
Z5t6MjfAdqzydLl+sFtKig9QpKek6FDtubFSc76wHZ+wYJz2GHUXHC3Qyswm7XPUwdUc0VOMC7R8
Ky0cP1SS/aqLxuFpdynb2G9bEi9L192+WBFlRpmJMfpg6hB9Ylzg5b7oe7Tw8juNt7ebGKMPpo6U
raaLevfdD/SN0+H7ou9RvL6wXdVRk/Xi3bc4Lnlub4+tTKGODrvOdsxoz58dbRzquBLkz49b/bwC
6AmMXAMWCL3BS0eSCzQnuUDvVXlruTF3OnX3Kc0x9m8+PUgxC/wlBWremHq9Z+xvGqwVO1prgmu1
ObkxWE89a5RNLtdXwf5KiqhQYkq5cmuvKjfbqQ5Pb8WowlY2u1qBNxjzqVWqlfb7S65U6RhfYz/Q
U4Zq4fASRb+7U9HfXtR1ITG6T7IFa/v+d3fqxQvDFR89Wd9U1yloiDFXetRwqc5bEdMkabKih0nF
zb4bRmn71OE6emSnUVdbV1qcrBfvvlH60l72G2n8Xfq1vaXBY41jn+mARmrhNEkHP1D0txelym8U
/S7BGuhPGLkGLFBw1DYiLUlJX1QrJspbS1Sh1CYjxir3kHRFJbV+mhs/UieNEW6HYH/tCa51jHxL
/grxkUJvCNWeG5zuL7CVhtRW63X7iHTeOWXdGKqpsZIym46iX1VJhKQ8q5859B8X9a59vvPBEn2z
MEjXSbpviLfkd52yF17XWLTSX38+Wab4KSG6T1L0sGp9UiTNHjJZkr+CVaZ3m4bbaUG2Kyx2KPT6
K3jwYF03dbGyp9r31al4ovSNpIvFXxjh+StlX7hR8UMmS/rK6icQgEUI14DbCNS2BB+VZBdoZZ6k
2NHaM0qSKpSYUmFbnSMhVGucpoX4Sar0HKQJLvVcdUwPuVZLFozTGp8qzUkule1EyC6sSgKY7GLx
Z8ZUDWeTdXTKjYqeWKXgmhL9/KAUcXeI7ps2REEXTujPTUrfN8RbF6s7M9XJNm2k6ej2faOsfjYA
uBumhQAWCB3VOJSceKO3VFyt1AgPBapeRUYoThw12PVGeef0g+RKFQR4OaZoVBZX6AdHpbn25fFU
oaKqgYq8MbADrZDk6anb7KuHRIxUTECdjmRKE3wGqrLqirHfW5M9rX7GAJs/X67W0OAwx5SMRl8p
+4IUPCFIKsuRlKO8muGKD/HW0bPNR5H/fLZMCr5RL07syL1WqLhuqGYbS/Rdq1/fvljbp1n9DALo
boxcAxYokJf2JBiTP8orNWd3haQKZd0YqrkJoZorqaC8zijtOlWk4GiB7SRHe2WZZzRHo7UnIVRT
jxZo5Y5yhcQHaE+Cn1HAfmJkhT4t9tca5xMaa+sVFBWqPdGSfcQ7SZK+qFZMdID2JARItXUqqLX6
GQMMBz/Qi0Pu0c8XLtZCY5f95MI/ny1T/NQh+uRj2/4ny6q1MOSyslua+nE8S3f536Vsx1SP5ic0
NvpKP/+bv7YvvEXZC2+x7TJOXGy7rSX6ZuF1jhMaz1j93AHoEQMaGhoa2it04POTmn7ThI7Uhx7w
flaOvh/D2qnupLU+mZNcYHXT+gXHFxUnvE7cT2t9Ev3uTqub1i9kL1zcbN/7WTm6bQavE3fy6X76
xB19fazjWZhpIQAAAIBJCNcAAACASQjXAAAAgEkI1wAAAIBJCNcAAEsFeHpZ3YQ+j+cY6DmEawCA
pR68KYLw140CPL304E0RXa8IQIewzjUAwFLzx0/Q/PEs9wqgb2DkGgAAADAJI9dAj3O+4mKdck8P
0mRV6Ae7K7RkwTitGWP/zmu/sqK0ZME4LVeVvgr2U6SnVHm6XD+46Ks9N9gukV5wtEArM42bxY52
7FdttTannFOq1Q8ZAIB+gpFroEf5KyneT4GnyzUnuUBzkmsUNKbxZZi6+5Sxv0CbTw9SzAJ/xzG/
MT5SToHmJFeqdEyA9oyqsZU9WqfQsJG2y6HHjm7cn1ygzcWeWu5UBwAA6F6MXAM9ykNBnnXK2l1h
bJcq/bSPljuOO49qSypvfIlWnq5QYp4kXVFJ7VWVfFFqVFGvyjDbP5cMHSQF+GlPgl/jXZbzMgcA
oKfwqQv0pAgPBdbW62SLBwO1LcFHJdkFWpknYxS683dRebpcP3CEdwAA0JOYFgL0pLxqfSVvzYu1
7wjUPPu0kAgPBapeRXm2zcRRgztdferFevmN8VVi0wMRI/XXeGPqCAAA6DaMXAM9qkKJOd76a3So
9twg2U5ovKpASco7p6wbQzU3IVRzJRWU13W++swz2jx0nNYYdUjGyY6lVj9uAAD6B8I10NPyzukH
eY2biUtDFXTWNo0jaUeBklq4SeruU04rflQoMaWi1fpcy9q5lgEAAN2DaSGAlSJGKiagTkcyu14V
AACwHiPXQA9LXBqquQH2ravKzT7V4mg1AADofQjXQA9rbeoHAADo/ZgWAgAAAJiEcA10o2HevMS6
G88xAMCd8KkEdKOE24YT/rrRMO+BSrhtuNXNAADAgTnXQDe6+wZf3X2Dr9XNAAAAPYQhNQAAAMAk
hGsAAADAJIRrAAAAwCTMuQa60d9zq7Uz45IqL121uil9kp/vQC2O89V3I72tbgoAAJIYuQa6FcG6
e1VeuqqdGZesbgYAAA6Ea6AbEay7H88xAMCdEK4BAAAAkxCuAQAAAJMQrgEAAACTEK4BAAAAk7AU
H9CD4uKD9a9hA5rtrzpRqV+kXG62/6c/H6GJ357X//cXq1tunr74mAAAsCNcA91oy8YRHSrnE+an
LRv9Wj4YPEJbIq1+JCbri48JAAAxLQQAAAAwDeEaAAAAMMmAhoaGhvYKHfj8pEpKiq1uKwAAANDj
goKCNf2mCR0q2+E519+PibL6ccHwflYO/eFm6BP3Q5+4H/rE/byflaPbZtAn7uTT/fSJO/r62MkO
l2VaCAAAAGASwjUAAABgEsI1AAAAYBLCNQAAAGASwjUAAABgEtPC9bnUtVqV2nS5vmK9vTJCY8Ns
f82PozvlPBWhFw60dvSQXghLUo7VjexnWuqTc6lrHa+RsWFr9XaR1a3sT1p6HRzSC2GN71tjV+7S
Oaub2a+0895UtEurwtp6b4PpinbpkYRdcv0EP6Qt4RGa6vT3yC4+43tMi31ic/AZp3555pDVLe2X
TAjXtg+iyHVZzY7kPHW7HpzyugpP5KnwxOuKWHc7b4g9wfjwuSe55cO2MLdcT1vdzv6k1T45pDe/
Xm28RvJUmDpRD0bzpacntPo6OJAhpRr9ceJjvaQN+g0DAz2iI+9NOds2KN3qhvYjB5+J0NTZG5TR
9EDRMX2hFXojP09HjL/nFgVb3dx+odU+UbHSEiL0E73u6JMjv7rV6ub2S10M14f0QthyKTVP7yQ0
P7YvOUYvrbR37K26d1OMns7gW1S3KtqlVdEfal72x3ppdvPD51LXKjL9TuVmb9Q8q9vaX7TZJ7fq
ocec3vymx+ul2a9oH19Cu1Wbr4PpiXpoun0jWDPnxSj960Krm9zndei9qWiXNn+5QutnW93a/uHg
M0ZQe2NFywVmTdQ4qxvZz7TZJzkpWqeNyiRQW66L4fpWPXQiz+mDyMmBDD09+07NDGncNTLmTs1L
zmBUrjuFLNLWEy/rRyEtHx655GUVblukkVa3sz9pp09cFerbT2I0nk+sbtXx18EhvblOToME6C7t
90mx3n5sgyIeiNd4qxvbT0z7VRsjn4XHWxg5RXdrq08OfvSKHlq9SPx+YD1OaATcyLnULc2+lKLn
5Txln3OdoZkd/mKE7nQu9bd6cMrrLQ/mwBp7NyiWub1uoliFX8VonHbpEcc8+LVK4xweSxCuATdx
LnWtItdN1Dv8smC5qMfsc67jtI+Tsa1XtEu/WTdR7zzGLwhuIyqxcV5v/sfa9NVyArblsrRui/R/
7P3yxkStm52kg1Y3qx/q+XA9eyI/6QEubKvqRH69WoUnEhVldXPg5FY9lL1RWpfCdDbLHNIL0RsU
kcprw30Fa/7vNiruvzMIchZzmRYSFa9Ns17Rp7x59TiPbqt53ETN++S4vpUco3Dnsj5U+pTV2mr1
owbcRrHeXnm7vn0gT4X83A00dyDDtnrIkgjXVUQ+idDTCa+rkNFs98EJjhYK1tjJ0imrmwFJ3Tly
HbJIaxJe0T1P2X8mOqQ312VpfRxvhIDDgRQ9qI26l2DtNnKecl4K0XYSXXpCHKOmVpme2LhUpX15
xNnS+tQ8grWFinftchqlLlbabzdId8/iZDoLTbtjhV74idM0kJwUrdu7Qrfx5tXjum/kWlLUYx/r
pZW3a2yYbXt9ah4nowBNfbJBkWEbXPcxImeZ8dcfV2RYROMO+gJowYf6SXjj+1bckx+zzrXVohJ1
5I0kTQ23v3+t0Bv5iZpmdbv6oQENDQ0N7RU68PlJTb9pgtVtheH9rBx9P4avou6EPnE/9In7oU/c
z/tZObptBn3iTj7dT5+4o6+PdTwLs1oIAAAAYBLCNQAAAGASwjUAAABgEsI1AAAAYJJuXS0EPatm
33FV7Tyohos1VjelTxow1Es+i6fJa+ZEq5sCAADcFCPXfQjBuns1XKxR1c6DVjcDAAC4McJ1H0Kw
7n48xwAAoC2EawAAAMAkhGsAAADAJIRrAAAAwCSEawAAAMAkhOs+zvvRxQrcOl++LR2Mn+90bIaG
bV2soXFt1+f7RPtlAAAA+ivWue4HGqq85REvKcV5b7iGRnqroara2N6vC6v2W91UAACAXo2R6/6g
oloDI++Qt/O+uDHyqCjTFavbBgAA0Icwct0flJ/XFf/xGhwnVWfYdvnOGqWrx45p4MwhuirJNi1k
hK6sStMlhWvo85Ea7GPcvuiYSh+3jWpfenynsdO1TP2+7bqQYvUDBQAAsBbhul+o0MXcagXOmiFl
7Jc0Qx7+Z1WTInnNbKl8vi4+nG/8O1xDn5+qoXH7dTHDqUj8JA2uOKbSh5lKAgAAYEe47i9Szqt+
6wj5Sqp/dLwGFhxRtfzl1Vr5+PkKnOnv2Kwf2+R44WU1zJyk4Y9WqOzZfAEAAIA51/3IftXke8vz
0RkaHFqt2rYCcfx8BUZeVtWq7SpdtV01RS2UyfhIZau267KmKnDrMg2Lt/rxAQAAWI9w3Y9Uf1Ym
hU/S4IrzutRGOe8gb6miUrZ1RGbII6SNOp/dqdJ9FRo0aYbVDw8AAMByTAvpTzI+Uu2sxRq4t+15
0tXPfiuvrZMUuHWSpArVtzRy7TJtpEI1LOMHAABAuO7rqp/dqWqn7cbVPiTb2tat/budsJySptIU
qx8dAACAe2FaCAAAAGASwjUAAABgEsI1AAAAYBLCNQAAAGASwjUAAABgEsI13I73o4sV+ATrZgMA
gN6Hpfhgvbg7NHyBdPnhj1St5ssHAgAA9BaMXAMAAAAmYeS6z5uhYVsnaZAkqUJ1+d7y0BGVlUxS
YORlVRmjxbZy43V1x05dnbVMHseOSTNtt6vft101QYvlE+4pqVZ1O3bqYoZt+sYQfasroZM02EdS
1VlHfd6P2svb7rdmVZrtkuvOV3asOquq3dKQpaM0QJLP1mXyMu5riI6o7Nl8SZLvE8vkZVyCvSE/
V2XPSkOfj7Tdp9G+CylWP88AAACE6z4uXEOfn6SB+bkqfTZf9qCtfEkpx1QXOVWD46TqDEnxIzSo
6FtdyJB8Z0mDZo5QzartuhB3h4YvXaYh+bkqXZVvC82zZkgZtis4DggfL+3YrtKMcA19PlJe8VJ1
iuvUDu9HF2vIo+G69Kw0NNJbdTu262JGYyur5TotxPvRxmPejy6Wl46p1PmKkfHzNbjimEof5pLr
AADAvTAtpE/z10CfCtUaI8DSftXk1xr/zlddgeRxS7gkyXeSv+qPNYbV+n3GSHNGpa6qsY7qEtfZ
0A35R4ygnK+6gloNDAo3jszQsK3LFLh1mXzCPTUgwF9Svq5WeGrw0vny7VD7wzU4VKrb2yREF15W
Q8gkDX80vEO1AAAA9BTCdV8W56eBVZdV38rh6me/1dXQMfLWDHn4n1VNill3bJ9isl2lq7ardF+F
48ilx7erdNV5eWxdpsDn75B3m/X4a6BPta5mNNmd8ZHKVm3XZU1V4NZlGhZvzdMLAADQFOG6L8s4
rSsaJa94+44Z8nLMg5ak/bpSMVyDHx2hgQWnzVuhI85PA9UYin0n+TcpsF8XVuWqTsM1OK6tivbr
SpG/vFpZlq/62Z0q3VehQZNYtg8AALgH5lz3afm6uHuMhi9dpsCZku2ExlqXb1SXjlUrcKZUsyr/
Gu+jBRkfqXbWMnltXSYvSfVF9pHrcJcTERvyc1WWIUmndWVBZOMJjc7tezxXA5+PVODWSY23KZnU
eFKkKlSzirnXAADAPRCu+7qMj4wAa+P7xDJdPdYkSBedt82vNlx6fLvT1n5dWOW0mZKmUuOfTdej
dt6+9Ph2lzrtLj7cUojPb7Lfud78Fm6Tr9IUq59YAACA5pgW0p/E3SHPkApdSbHvCNfQSNcTGQEA
AHDtGLnu45zXiLavUe283nRDfq7KUqxuJQAAQN9AuO7jWpueoZQ0plYAAACYjGkhAAAAgEkI1wAA
AIBJCNcwje8Ty7hqIgAA6NeYc41rFz9fgZPOq/Rx22ojrc7vBgAA6CcYuQYAAABMwsh1H+f7xDJ5
HDsmzZykQZJUdVZVD3+kaknejy7WEJXpavgoDSo6ptJjI5yufGhbtu+i/QI0xtJ9steRO0Q+M/0l
+Stw63jV7dipq7OWyePYdl1IkZpejbF+n7G/aT1GWwAAAPoCwnU/MGjmCNWs2q4LsoXtIY+Gq/pZ
21UPB4QP0RXjmKTG5fni5ytw1gwpY78Ud4eGz5RqVrlO+6iW67QQ31mNx3yfiJRHQa5Kn3W6uqJ9
GsmqNElGuHdqCwAAQG9HuO4H6velOULxpb1n5blgjLxlC7QN+cecArPraLOqLstbkm4ZLuUf6cR8
6hnyCKlQ7eOuodk7yFsKmaTArZMadxb5d7hWAAAAd0e4hsEWrAfmbreNXsfdoeELbEcGBXjq6rFO
jC7H+Wlg1WXVt3CoIT9XZYxUAwCAPooTGvuBQZNmOP7tO2uUVHC6hXnO/hroU6urhbYt71uGa4Bx
5NKxCg2aOV++Hb3DjNO6olEa0mRZvuqSag0In9S8nrg7NPz5O2yj5AAAAL0YI9f9QL1GKHDrMttG
0THXedAO+1WTP14+S5cpcKnUUFShBvuhlDRVBS2Wz9Zl8pIaT0RMOa/6rZMaT2h01JWviw/7a9jW
SAVujbS1Yd92XWhaj31/odXPEAAAgDkI1/3BsTSVPt58d/WzO11GsJtuq91j+3Vh1f7GzYztrR9r
s56PVJYhAACAXo9pIQAAAIBJCNcAAACASZgW0sddenx71ysBAABAhzByDQAAAJiEcN2HDBjq1fVK
0CaeYwAA0BbCdR/is3ga4a8bDRjqJZ/F06xuBgAAcGPMue5DvGZOlNfMiVY3AwAAoN9i5BoAAAAw
CeEaAAAAMAnhGgAAADAJ4RoAAAAwCeEaAAAAMAnhGgAAADAJ4RoAAAAwCeEaAAAAMAnhGgAAADAJ
4RoAAAAwCeEaAAAAMAnhGgAAADAJ4RoAAAAwCeEaAAAAMMmAhoaGhvYKHfj8pEpKiq1uKwAAANDj
goKCNf2mCR0q69HRSr8fE2X144Lh/awc+sPN0Cfuhz5xP/SJ+3k/K0e3zaBP3Mmn++kTd/T1sZMd
Lsu0EAAAAMAkhGsAAADAJIRrAAAAwCSEawAAAMAkhGsAAADAJKaF63Opa7Uq1Wm5vgNJGhsW0cJf
knKsftT9RM5TEXrhQNO9h/SCU3+49Bm6XYt9UrRLqxx9slZvF1ndyn6gyftTsz5xOc57Vo9or08M
zT5r0G2Kd63V1PAI42+t0lzemw5pi+NYhKY+c8jq5vYLbfZJ0S49Qp+4BRPCtS2sRa7Lct09PVGF
J/Jc/t5JkOZtihcLzHQzI6zdk9z0QLHeXrlceZs+NvrkdUWsu73VDzGYqLU+KdqlVdEfal628TrJ
vlPp0YS57lWstzMmKtf+3pS9UXlLnL7UFO3SqiXH9ZLRJ7mbjuuelbt0zupm92nt9ImkVj9r0E0O
aeex1TqSn2f7e2Oi1s1O0kHjaPGuDI37xDiW/7E2fbVcj+ziS0/3aqdP/n5ccY4+eV0P/fdybeHD
xBJdDNeH9ELYcinVFpzbVLRLm5NXaM2SYKsfc9/mCGsf66XZTQ8W6ttPYjQvxt4Ht2pmgpR3gjfE
btVGn5zL+lDpCav1oxBjR8girUl4RZsZmetGwfrRY4s00r4ZskhrErKUnmV7zpv2ycglq7X+kw+1
j18UulHbfdKpzxqY5Fat/tWtjZtR8do06xV9aoS14EWJmm9/31Kw5q9eoYy/7RXvXN2pM31yq277
mfTFSXrECl0M17fqoRN5emh6+yVztm2QGLXufiGLtPXEy41hzcWtmpmQpQe32X8qOqR9yc5hG92i
zT5pbvz1MUr/utDqVvcjxSr4UooIC5ZUrH3pWVof5/QBZrxuGoMeup9zn0id+axBdynUqb0xGje2
5aPFJ49LkyeJT5Oe1EafFO1Syn+vUPwiesQKPXNCI6PWbiPqsTy9o+XGvMYtGp/d8dAH842MuVPz
krc4/fx9SG/ys3fPOpCiBz9ZoZkEN/dBn7id4l1b9MKsO3Vbi58Xh7Tz11l66I5bO1stuqB5nxQr
LcGYb/1b6f/kJ2qa1Y3sp3okXJ/L+pBRa7dQrLdXRmhfnH0e/O+kxzip0VIhi7Q1daIejLafyJWh
8ZtirG5V/3EgSWOXHNdL2Ym8P7kL+sTtFO9aq9hfT9QbyYtaGJk+pC3hy/XFkx9rNR3WY1ruk2DN
TzbmXP9O+s/wCOZcW6QHwnWx9qWLqQfu4ECKHtRG3esYDQrWj57aKKXv5WQtK7mc/Juo0K+zNO/6
sV2vF23KeSpCY/80UbkdnLLTOEUB3aWzfYLuZhsJjT22WkdaGgXNSdLU8C0a90menmP6QQ9pp0/s
QhbpuTdW6IUtu5gHbwGPbr+Hor1K1536PW+UQAcY8+Cz+aDqTjlPRWjz9R+r8LGmz3OwQqdI6SeK
pen2Y7Y+Gb/S6lb3ba33CaxRrLSE23VqdZ6OtDQinZOkqVsmKjM/kXnWPaadPoHb6PaR63NZHyp9
yqTGs8BhnelxWv/JBr3pWHqvWG8/tkGaN4v+cRM5Ty3X086rh8B87ZwDErVyo7Tut4558OdSt+jp
2XdqJn3SfTgvx/3kpGidNmpxiyGuWGlbXtFDqxcRrHtSe33yjPMo9SFt+ckrirt7Fn1kgW4fuf72
6yytj3vZ6scJSbYz7l+3XUTGvivhdRXygWYh2xJjT9s3E15X4WOcFNT9XtE9Ya+47pq9UbnbFmlk
yCJtTT2usdERetB5v9VN7vPa6BOrm9Zf7d2g2PANrvt+9rqO/Mr2CfLCTyL0gsvBGG365GWn5eBg
ulb75FaNleuxuCc/ZrqORQY0NDQ0tFfowOcnNf2mCVa3FYb3s3L0/Rh+E3In9In7oU/cD33ift7P
ytFtM+gTd/LpfvrEHX19rONZuGeW4gMAAAD6AcI1AAAAYBLCNQAAAGASwjUAAABgku5f5xoAAPQq
Df84roa/HJQqa6xuSt/k56UBP5ymAd+ZaHVL0A0YuQYAAC4I1t2sssb2HKNPIlwDAABXBOvux3Pc
ZxGuAQAAAJMQrgEAAACTEK4BAAAAkxCuAQAAAJMQrgEAQDtmaNjWZRr+aHgLx8I19PnGY96PLlbg
EzParM370cUaFm/1YwK6B+tcAwCA9lXVSqFj5K18VTvvj58kD9U6Nquf3el6HOhnGLkGAADt86nW
1YpR8op33e07yVtXCojTgB0j1wAAoEOuHKuQ16QZkvbbdsTdIU//MtWWD3eU8X50sYboiMqezZfi
5ytwpr9xpFZ1O3bqYobr6Lb3o4vlE+5p2yg6ptLH91v9MIEuIVwDAICOSTmmuuenamjcfl3MkLxv
Ga6ruTtVH7S4lfJpKk0x/h0/X4GzZkgZzuF5hrzCq1WzaqcuWf3YAJMwLQQAAHRQvuoKJI9bwmUP
xldS2ipvO9kxcOsy2wi2v5+8XY5X6GqVv7yev6PJfqD3IlwDAIAOq372W10NnyTf+BEamH+sjRHn
cA19PlIDc7erdNV2le44q4ZmZfJ18eHtKt0tDdm6rN1VRoDegHANAAA6Yb+uFPnLa6a3rnyW30Y5
fw30qdXVQtuW9y3DNaC1ohkfqWzVMdWHjJCv1Q8P6CLmXAMAgE65tPesPGdV6mJGW6X2qyZ/vHyW
LlPgUqmhqKKFkesZGrZ1kgYZW/X7tjP3Gr0e4RoAALRjvy6sctrM+EhlTsHaefWP1v7der2sDoK+
hWkhAAAAgEkI1wAAAIBJCNcAAACASQjXAAAAgEkI1wAAAIBJCNcAAKAPmqFhWxdraJzV7UB/Q7gG
AAB9gu8TyzQs3r61XxdW7WxnLW7AfIRrAAAAwCRcRAYAALTL+9HF8gn3lCQ15J/V1fAhurLqvDy2
jtfVHY0jxL5PLJNnea7KPhuj4Quk2oLh8gr3lKrOqurhSnnZr8hYdEylj++XbfrGCF3ZJ3nN9Jdk
u1LjhRSp6RUcG/JzVfZsvqRwDX0+UoN95Ch/ZdIyeYVIClmmwEj7fY3QlVVptqs+xt2h4UtHGZdg
r1DNqjRdip+vQOM+be37qJ2L3gDtI1wDAIC2xc+XT3i1albt1CUZQVvVumJc4nzILeFSRr6kGfII
qVDt4/lS3BjJZ5Q8lavSVfnyfWKZfLYOUc2q7bqgGRq2dbyGxu03Qrm/vCYdU+mqNCl+vgIj75B3
ykeqdrmCoy1o+ypfl+InaXDFMZU+7Hx1x+3SE8vkcawxmHs5js3QsKXDdWXHdqdpIuEaGumtOpd9
QNcxLQQAALTJO8hbDfnHbCPAkqqf/Vb19n9/ViaFjpG3JMWP0KCi845yqjqry8/mS5Lqy2ud6qjQ
1Srne6hQzeNGUE45r3qfIY7Rau9HFytw6zIFbp2kQfLWwDhJhZfVEDJJwx8N79gDiB+hQUXfNgnR
+bpa4anBS+fL1+onGH0K4RoAALRpUICnrpbkt3ww4yPVVgzX4DjJd5K36vbu71zlbfB+dLF8Ar5V
6artKl2Vqzp7IM/4SGWrtuuypipwq/NJjK3UE+SthvKKZvsvPb5dpavOy2PrMgU+f4ftCwLQRYRr
AADQpkvHKjQosjF8ej863jGybDteLY9b7pCHf5nqTJxiMSjAszEUx42Rh4/r8epnd6p0X4UGTZrR
Zj3Vn5VJ4VNbWZZvvy6sylWdbF8QgK5izjUAAGhbSppqJi2Tz9Zl8pHthMZ6DXE6fl5Xt06S9m03
9YTAS3vPynNppAK3RkpVFaq3j1w7n4ioCtUY87IvHatQ4MzGExodMj5S2dj5Cly6TIFL7bc5poFO
J0U25OeqjLnXMAHhGgAAtOvS49sb51LH3aHhoZcd865tKnQlxWkz4yOXsFr97E6n4J2viw/bp5ns
14VVzvU4bTepo1GaSlNa2J3iur96VevHJEkPtzLVBegCpoUAAIBO8Z01Sio47QjL3o+Odz2REejH
GLkGAABtc1kjWrY1qp+1Lb03bOskDao6q6qHzTuREejNCNcAAKBtrU7PcF6HGoDEtBAAAADANIRr
AAAAwCSEawAA0L3i7tDwrZ28EmLcHRrOhV3QCxGuAQBA98r4SGWr0ozVRMI19PnFrVzQBej9CNcA
AACASVgtBAAAtC3uDg1fIF2pGKXBIRWqWZWmSy7L8xn7JNlGphuvfFi/b7suFNpuf/nh0xpsP7Z0
mQJnHVPp47It52fcVUN+rsqe5eIu6L0I1wAAoH0+ozQwd7tKH5cjbF9eZVzuPH6+Ap+YoUuP75fv
E5HyKMg11sE2OKaA5Oviw9LQ56dKu3fqorG8X+NyfrZ1s32VzwVp0GsRrgEAQPuqzqomxfj32CEa
4OMvn63L5OM4flnemiGPkArVPt65kWfvRxfLJ9zT2KrVVeZjoxcjXAMAgM4rOqbSx5tcQCbuDg2p
uqz6TlTj/ehi+QR8q9JV+2WbUjLV6kcGdAknNAIAgM4pvKyGkPHNV/zIOK0rGqUhj4Z3uKpBAZ5q
KK+wbcSNkYdPC4WMaSdAb8DINQAA6JyMj1Q2dr4Cly5T4FLbLvuJiBcf9tewrZEK3BopyX5Co/ON
81VXMFU+9hMa956V51KjfFWF6qusfnBA1xCuAQBA2zI+UllGk30paSpNaanwfqcTFO1cb1/97E7b
iZCGZnU3vU1Kmkqtfg6ADmJaCAAAAGASwjUAAABgEsI1AAAAYBLCNQAAAGASwjUAAHDl52V1C/o+
nuM+i3ANAABcDPjhNMJfd/Lzsj3H6JNYig8AALgY8J2JGvCdiVY3A+iVGLkGAAAATEK4BgAAAExC
uAYAAABMQrgGAAAATEK4BgAAAExCuAYAAABMQrgGAAAATEK4BgAAAExCuAYAAABMQrgGAAAATEK4
BgAAAExCuAYAAABMQrgGAAAATEK4BgAAAEwyoKGhoaG9Qgc+P6mSkmKr2woAAAD0uKCgYE2/aUKH
ynp0tNLvx0RZ/bhgeD8rh/5wM/SJ+6FP3A994n7ez8rRbTPoE3fy6X76xB19fexkh8syLQQAAAAw
CeEaAAAAMAnhGgAAADAJ4RoAAAAwCeEaAAAAMEkXw3Wx3l4ZobFhxt/KXTrXSsmcp9o+DrMc0gth
Tn3y1CHXwweSGo+FRWhs2Fq9XWR1m/u6dvpEkop2aRV90nOavA5eOGA/0OQ9zelvVSrLkXarVvvE
5lzqWqfjScqxur39QPGutZoaHmH8rVVak/elg89ENB5/5tC13Qk6xeU5b6FPXPosYZd417JG18L1
gRR9+0CeCk/Y/t6ZskGRrQSHzclWP9T+4VxqhsZn2/vkY7305XKXUHDuxHEp4XVHnxWeeFk/CrG6
1X1be32iA0kaG/2h5mXTJz2jWG9nTFSu/TWQvVF5S+xfaIL1o215Tq8P2/F5WqE1S4Ktbngf1laf
SDqQpMh1E/WOcTx303Hdw2BNNzukncdW60h+nu3vjYlaNztJB42jB5+J0E/0uuP4G1quR3YR5brX
IX3q9JxnPimt+61TgM5JUuyvJ+oNe59M3qBYvvRYomvhenqiHpreuBm1cqPmJWc0G1HI2bZBEQkr
rH6s/cLIJYlOwSxYP3pghdLT97p8CM27fqzVzexX2u6TYr39p1e0PpVA3XOC9aPHFmmkfTNkkdYk
ZCk9q+VgkLNtg7QpXqw6253a7hPboECcow9GxtypeZ8c17dWN7tPu1Wrf3Vr42ZUvDbNekWf5kjS
IX363zHadH/j8Wn3b5R+neII3+gOrn0S/N07Fbf3uE4Z2wc/ekVxT8ZrmrE97f6NivvvDPrEAubO
uT51XOmzJ2q8874DSbrny426N87qh9o/nTtxXJoyyfGh9e3XWVY3qd9z6ZOivUrXRt07vau14toV
q+BLKSKshZHpol3anMyodc9z7ZORMXdqXvIWx0j2uawPle4UttETCnVqb4zGtTY2EzJJN+q4CpnS
1mMOvrZBcoRp2xeeuO86vVeFzFKc4wsRepKJ4do2Ajdv3qzG0Qcd0gtLjuulp5xGJNCDDunNdVla
H3ery970dbe3Oq8R3a1Jn5w6rvQpk/TtU+3MyUb3OZCiBz9ZoZktfMFh1NoiTfskZJG2Zt+p9Gjb
ayTy69UqfOzWLt0FOqd41xa9MOtO3RYiSbfqtp9lad1rh1yPW93I/iAnyTGn+tM78vTcIr74uyOT
wnWx3l55ux6c8rq2Oo3w5Dy1XHmbfsfP3ZY4pBfClitv08euU3cec51LmreEgN1zWu4TJS/Xvrg2
5mSj+xxI0tglx/VSdmILAfqQ9jFq3fNa6pMDSRr7mPR7+3tXXAYnNfag4l1rbXN5kxfJ/mqY9quP
temr5Y6g95+6Uw9Z3dD+ICrRMef6to84adFddT1cF+3SqrDbbSc2Oo0knEtdq3u+3Kjf88HU8w4k
aWzYFo3PznP5stNMyCL9flOMns5gpLTbtdUns52nhbQ8Tx7my3kqQmP/NFG5rZ1AeiBDTzP1oEe1
3CfGeQkPOP0COj1R7yS8on0MDHSzYqUlRCj22GodyU90zOW1Cdb85DxH0Hvuu9IXmqixDKb1mGm/
+libtEE72/yW2cZUHnQbjy7dumiXVkUf15oTeU0+gIq1Lz1L+iRLkWEbXI5Ehn2ol7I5eavbHEgy
PpwSOzwVhxMcu1lbfTJuouZZ3b5+KOepCG2+/mMVPtb6l8+cjFe0Pi7R6qb2Gx3pE/SkYqUl3K5T
q/N0pAPfMIv//qEyfrZaz1nd7H5rrMbNytKpQkn2fFW0Vxl7JyqevNXjujRy3fp8xBaWs0pdIc3e
2PooEUzQwghP0+OpTqPURbv0m3XSvBg+zLpPO30SMkvztEG/cUwDaencBZiqQycpHtK+5BiNH2d1
Y/uJNvskWDPnxejpPzktvXcgSfcktzxPHibJSdE6bdTijvx0U7RL//lruawegm6Qk6QtTqPUxbt+
q3V7V+i2KEkK1vzVK/TCT5yWS3xtgzJ+FtfkFwf0hK6NXMs4OW6d6771qXmuc0rRo55eEqGnXfbE
NP5akL7cqb9i+BWhh7TeJ8H60baPpZWNr6N5mz5uezoPTPCK7gl7xXXX7I3K3WZ8CSo6przZd+pe
Xhs9qI0+WfKycrXW6ZdQ2+uHKTvdbO8GxYa7/vqsn72uI7+61ZiHbV99KkabPnlZ83m9dK+xE/XF
7AhNdexYoTecp+tEJSrzybWKDY+wbRt9hZ43oKGhoaG9Qgc+P6npN02wuq0wvJ+Vo+/H8LHiTugT
90OfuB/6xP28n5Wj22bQJ+7k0/30iTv6+ljHs7C561wDAAAA/RjhGgAAADAJ4RoAAAAwCeEaAAAA
MEmXVwuB+6h67z1VbN6sq+XlVjelTxoYECD/NWvkM3eu1U0BAABuipHrPoRg3b2ulperYvNmq5sB
AADcGOG6DyFYdz+eYwAA0BbCNQAAAGASwjUAAABgEsI1AAAAYBLCNQAAAGASwnVftv5VjXltXbPd
fs+lKeS5H0tL/6iQzFcV0Nrt2zsOAAAAF4Trvuzpw7oSenOTcPxjeU2W6v7+lrTjFyqK/anKW7u9
y/EfKygtTUFLrXggVt43AABAxxGu+7RNqi2YIM/1TruWfkeD9aVqdljdNgAAgL6HKzT2ceWHT2rM
zeskbZIk+X13ivTVq6qUbNM+VkiV83+hSq3TyMz5xn+IStX8ab5KZD/+D3mlrZWXn6QHMjVmYZpO
37/J9Y7Wv6ox8ybY/l3wT9UETpFema+SHes0MvNm1ecGyStStnp3/FhB9vokXUmP1bmnm9Rhb4Nz
2dbuGwAAwE0wct3XuUwNcZoS0oTfc7EamPuyTsfG6nTsfJW4jGy/pZL5L6umslI1f4ptHm6X/lEh
84Jsx2JjdfpwkCM420zQYL1q1PtjBaX9VHrFKBubJs0z5nU//VNjX6xOp5fIa+G69u8bAADAjRCu
+zynqSFtTAmpPFOigZFrNXJ9p+9ACgvSwILMxkD+dJpqKp0LnFT1I/ZAP0GD/Pzk9UCmxmRmakzm
fHkoSIOWSra51cb+eROkwNHy60w7AAAALMa0kH7APjXEb/QUXd07X5UtFXr6pzr9tBTwWqbGZNqn
hXSM3+ggXS052YkWnVRVsxMpbdM/Bu2N1emn5ZiyAgAA0Jswct0fPH1YV0Jj5Te5RLVPt120/P5Y
VeRKg7/74w5XX/n3L6XI+Y2rkqyf32RaiLOTqq+cIO/nmtY/QYP8KlV/wrbl990p/OcEAAC9Dvml
X9ik2gI/DSw93Oqye37PpRnTNDLlP/lLVT7SdF72W6r5SrbpHE3Xzt7xC1XmBsnHuP2Ym0uaTAtx
radkfpquRq513N+YtD/KT5tUlSvHdBG/oBJd7ch9AwAAuBGmhfQT5ffHNg/WO36hIvs86UdamC7i
fFxS5SOtTClpdmydRmYGqXaHJG3SudimpTfpXGwLJyZ2uH4AAAD3xMg1TOf3XKw8ClofJQcAAOir
GLmGCZzXyJZU+U9VzGfJPAAA0P8QrmGCVqZ5AAAA9DNMCwEAAABMQrgGAAAATEK4hrnWv8pyeQAA
oN8iXAMAAAAmIVwDAAAAJmG1kD7PeZm8k6rJDdJgvaqiR96S33Np8tOXuhr5L/IoSNPpwzdrzLwJ
xu0qVfOn+SrZIWnpHxWyQqr+aop8Iv1cj+nHCkpba1zuvFI1uSWOe7bVn6m6ybbLoV/NfVlFZ+Y7
7uNKeqzOPd20DkkFaTp9P6uPAACA3oeR6z7txwpKm6+BuS/rdGysTsce1qBIP5cSAyODVBsbawuz
T//UKBer0+kl8lroNHfa71/krVd1OjZWFblyHAt4ba28StOM270qTZ7QpP5Y6ZVYnY41Lnl+82Gj
/pPymPVH2VrzlkrmG/cb+7JqAmMVtNTq5w4AAKDzGLnu0yZokN9JVT/ylrG9SVW5sXKO11dz05yu
pNhkBLmyRLZxakmV/1SlUU/l37+U34rR8tOPNSjwpKocF4x5SyV752vMzc71v2qMcJ9UfWWl6t81
yp4o0dVZTg1Z/6rTqLl0Jczq5w4AAKDzGLnuy5aO1sDKEl3pUGFbsB601xhB/tM/dbXd20zQIL8O
VN2e9a9qzKwSVRij5lUFFj9vAAAA14hw3Zft+Ifq9C/yWW/fsc6YM92SCRrkV6n6E7Ytv+9O6cB/
jk2qLZgg7+d+bGz/WEGzJrR7q6b8RgdJpWdsI+RaJ89Q48DSPyok7Y8yI78DAAD0BKaF9GlvqeSV
7yjkgUyNmSfZTmisbCU026aM+D+QqTEPSFcLTnZg5Foqvz9NIzPXakzmWtlOaDwpBXWulZWPZMon
c77GZM6XdFJXGLkGAAC9FOG6r9vxCxXtaNwMeC1Tgw4bc6cfmW+MFqvF7dbqcN3epHOxLa/s4Vrf
WyqZ/1Yn62hyvwAAAG6OcN2fLP2jvENPqvp+qxsCAADQNxGu+7iA1zLlY5/DbKxPXW51owAAAPoo
wnUfV35/LGEaAACgh7BaCAAAAGASwnUfMjAgwOom9Hk8xwAAoC2E6z7Ef80awl83GhgQIP81a6xu
BgAAcGPMue5DfObOlc/cuVY3AwAAoN9i5BoAAAAwCeEaAAAAMAnhGgAAADAJ4RoAAAAwCeEaAAAA
MAnhGgAAADAJ4RoAAAAwCeEaAAAAMAnhGgAAADAJ4RoAAAAwCeEaAAAAMAnhGgAAADAJ4RoAAAAw
CeEaAAAAMAnhGgAAADAJ4RoAAAAwCeEaAAAAMAnhGgAAADAJ4RoAAAAwCeEaAAAAMAnhGgAAADAJ
4RoAAAAwCeEaAAAAMAnhGgAAADAJ4RoAAAAwCeEaAAAAMAnhGgAAADAJ4RoAAAAwCeEaAAAAMMmA
hoaGhvYKHfj8pEpKiq1uKwAAANDjgoKCNf2mCR0q69HRSr8fE2X144Lh/awc+sPN0Cfuhz5xP/SJ
+3k/K0e3zaBP3Mmn++kTd/T1sZMdLsu0EAAAAMAkhGsAAADAJIRrAAAAwCSEawAAAMAkhGsAAADA
JF0M14f0QliExtr/Vu7SuVbLJSnH6kcLAAAAdKOuhesDGVJqngpP5KnwxMd6SRv0m1TX9bDPpa7V
2LDletrqRwoAAAB0s66F6+mJemi6fSNYM+fFKP3rQsfhc6lrFZl+p3KzN2qe1Y8UAAAA6GYdvohM
+w7pzXXSS9m3OvaMXPKyCpdIKtpl9eMEAAAAul2XT2jMeco+5zpDM0+8rB+FWP2QAAAAAGt0OVxH
PWafcx2nfWERWtVkzjUAAADQX5i4FN+teih7o7QuhVVBAAAA0C+xzjUAAABgki6F65ynnNeuLtbb
j21QekKcoqx+VAAAAIAFurRayPjrjysyLKJxR8LrKnzs1muvEAAAAOjFuhSuHUvttSdkkbaesPqh
AgAAAN2LOdcAAACASQjXAAAAgEkI1wAAAIBJCNcAAACASQjXAAAAgEkI1wAAAIBJCNcAAACASQjX
AAAAgEkI1wAAAIBJCNcAAACASQjXAAAAgEkI1wAAAIBJCNcAAACASQjXAAAAgEkI1wAAAIBJCNcA
AACASQjXAAAAgEkI1wAAAIBJCNcAAACASQjXAAAAgEkI1wAAAIBJCNcAAACASQjXAAAAgEkI1wAA
AIBJCNcAAACASQjXAAAAgEkI1wAAAIBJCNcAAACASQjXAAAAgEkI1wAAAIBJCNcAAACASQjXAAAA
gEkI1wAAAIBJCNcAAACASQjXAAAAgEkI1wAAAIBJCNcAAACASQjXAAAAgEkI1wAAAIBJCNcAAACA
SQjXAAAAgEkI1wAAAIBJCNcAAACASQjXAAAAgEkI1wAAAIBJCNcAAACASQjXAAAAgEkI1wAAAIBJ
CNcAAACASQjXAAAAgEkI1wAAAIBJCNcAAACASQjXAAAAgEkI1wAAAIBJCNcAAACASQjXAAAAgEkI
1wAAAIBJCNcAAACASQjXAAAAgEkI1wAAAIBJCNcAAACASQjXAAAAgEkI1wAAAIBJCNcAAACASQjX
AAAAgEkI1wAAAIBJCNcAAACASQjXAAAAgEkI1wAAAIBJCNcAAACASQjXAAAAgEkI1wAAAIBJCNcA
AACASQY0NDQ0tFfowOcnrW4nAAAAYJnpN03oULkOhWsAAAAA7WNaCAAAAGASwjUAAABgEsI1AAAA
YBLCNQAAAGASwjUAAABgEsI1AAAAYBLCNQAAAGASwjUAAABgEsI1AAAAYBLCNQAAAGASwjUAAABg
EsI1AAAAYJL/H1yCuTneHC8kAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIyLTAxLTI0VDE4OjAyOjUw
KzAwOjAwfjofKAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMi0wMS0yNFQxODowMjo1MCswMDowMA9n
p5QAAAAASUVORK5CYII=" />
</svg>

After

Width:  |  Height:  |  Size: 34 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 45 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 34 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 971 KiB

View File

@@ -0,0 +1,71 @@
@import "bootstrap/override";
@import "pronto-atendimento/classificacao-risco";
@import "pronto-atendimento/atendimento-medico";
$labelHeight: 30px;
* {
outline: none;
}
body {
font-size: 15px;
background: #eeeeef;
}
h4, h5 {
font-weight: 400;
}
h1 {
font-weight: 200;
font-size: 2.2rem;
}
.modal > div {
width: 100%;
transition: max-width 0.5s !important;
}
.col-min {
width: 1%;
white-space: nowrap;
}
.placeholder-label:not(.btn) {
padding-top: 25px;
}
.placeholder-label.form-group-radio,
.placeholder-label.form-group-checkbox,
.placeholder-label.custom-radio,
.placeholder-label.custom-checkbox {
padding-top: $labelHeight;
}
.form-group-radio .custom-control-label,
.form-group-checkbox .custom-control-label,
.custom-radio .custom-control-label,
.custom-checkbox .custom-control-label {
min-height: 25px;
display: flex;
}
.form-group-radio-button-disabled,
.form-group-checkbox-button-disabled {
background-color: #e9ecef !important;
label {
color: #495057 !important;
border-color: #ced4da !important;
}
}
.placeholder-label.btn {
margin-top: 25px;
}
:not(.input-group):not(.btn-group) > .btn + .btn {
margin-left: 0.5rem;
}

View File

@@ -0,0 +1,444 @@
$gap: 0.5rem;
$paddingTable: 0.5rem;
$fieldHeight: calc(1.313em + 0.75rem + 2px);
.row {
margin-left: -$gap;
margin-right: -$gap;
}
.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, .col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm, .col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md, .col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg, .col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl, .col-xl-auto {
padding-left: $gap;
padding-right: $gap;
}
.custom-select {
padding: 0 1.75rem 0 0.5rem;
}
.custom-control-inline:last-child,
.custom-control-inline:only-child {
margin-right: 0;
}
.form-group {
margin-bottom: 0.75rem;
}
.form-control {
padding: 0.5rem;
}
.form-control, .custom-select, .btn, .input-group-text {
height: $fieldHeight;
}
textarea.form-control {
min-height: calc(#{$fieldHeight} * 2);
}
.list-group-item {
padding: 0.75rem;
}
.btn {
line-height: 1.25;
display: inline-flex;
justify-content: center;
align-items: center;
}
.btn .b-icon.bi, .nav-link .b-icon.bi, .dropdown-toggle .b-icon.bi, .dropdown-item .b-icon.bi, .input-group-text .b-icon.bi {
font-size: 100%;
}
label {
margin-bottom: 3px;
}
.card {
border-color: #dee2e6;
}
.card-body {
border-radius: 0.25rem;
}
.card-body, .tab-content {
padding: 0.75rem;
background: #FFF;
}
.pagination .page-link {
padding: 0.5rem 0.75rem;
height: 35px;
line-height: 1;
}
.alert {
padding: 0.5rem 1rem;
}
.dropdown-menu {
font-size: 0.9rem;
box-shadow: 3px 3px 8px -3px rgb(0 0 0 / 50%);
}
.dropdown-header, .dropdown-item {
padding: 0.5rem 1rem;
line-height: 1.2;
}
.b-dropdown-text {
padding: 0.25rem 1rem;
line-height: 1.2;
}
body .table.b-table>tfoot>tr>[aria-sort=none], body .table.b-table>thead>tr>th[aria-sort=none]{ background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='101' height='101' view-box='0 0 101 101' preserveAspectRatio='none'%3e%3cpath fill='black' opacity='.3' d='M51 1l25 23 24 22H1l25-22z'/%3e%3cpath fill='black' opacity='.3' d='M51 101l25-23 24-22H1l25 22z'/%3e%3c/svg%3e")!important; }
body .table.b-table>tfoot>tr>[aria-sort=ascending], body .table.b-table>thead>tr>th[aria-sort=ascending]{ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='101' height='101' view-box='0 0 101 101' preserveAspectRatio='none'%3e%3cpath fill='%23007bff' d='M51 1l25 23 24 22H1l25-22z'/%3e%3cpath fill='black' opacity='.3' d='M51 101l25-23 24-22H1l25 22z'/%3e%3c/svg%3e") !important; }
body .table.b-table>tfoot>tr>[aria-sort=descending], body .table.b-table>thead>tr>th[aria-sort=descending]{ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='101' height='101' view-box='0 0 101 101' preserveAspectRatio='none'%3e%3cpath fill='black' opacity='.3' d='M51 1l25 23 24 22H1l25-22z'/%3e%3cpath fill='%23007bff' d='M51 101l25-23 24-22H1l25 22z'/%3e%3c/svg%3e") !important; }
.custom-control-label::before {
top: 0.3rem;
}
.b-calendar .b-calendar-inner {
min-width: auto;
}
.b-form-btn-label-control.btn-group > .dropdown-menu {
padding: 0;
}
.b-calendar .b-calendar-grid {
border: none;
font-size: 0.9rem;
display: flex;
flex-direction: column;
}
.b-calendar output {
border: none;
background: #f8f9fa;
border-radius: 4px 4px 0 0;
border-bottom: 1px solid #d0d0d0;
line-height: 1;
padding: 0.5rem;
height: auto;
}
.b-calendar-grid-body {
flex-direction: column;
padding: 5px;
}
.b-calendar .b-calendar-grid-body .col[data-date] .btn {
font-size: 12px;
margin: 0 !important;
width: auto;
height: auto;
flex: 1;
font-weight: 500 !important;
border-radius: 2px !important;
}
.b-calendar-grid-weekdays {
padding: 0 5px;
}
.b-calendar-grid-body, .b-calendar-grid-weekdays.row.no-gutters.border-bottom {
display: flex;
border: none !important;
margin: 0;
}
.b-calendar .b-calendar-header, .b-calendar .b-calendar-nav {
margin: 0;
}
.b-calendar.b-form-date-calendar.w-100 {
display: flex;
}
.b-calendar-grid-help {
margin: 0;
padding: 0.5rem;
line-height: 1;
color: #333 !important;
display: none;
}
.b-calendar-grid-body .row {
margin: 0;
}
.b-calendar .b-calendar-grid-body .col[data-date] {
display: flex;
width: 30px;
height: 30px;
flex: 1;
}
.b-calendar-grid-caption {
text-transform: uppercase;
font-weight: 500 !important;
}
.b-calendar .btn.disabled {
cursor: crosshair;
}
.b-calendar .b-calendar-grid .btn:not(.active):hover {
box-shadow: 0px 0px 0px 1px var(--primary) inset;
color: var(--primary) !important;
background: none;
}
.b-calendar .btn-outline-primary:not(:disabled):not(.disabled):active, .b-calendar .btn-outline-primary:not(:disabled):not(.disabled).active, .b-calendar .show > .btn-outline-primary.dropdown-toggle {
color: #FFF !important;
}
.b-calendar svg {
transform: scale(0.8);
color: #444;
}
.b-calendar .b-calendar-grid {
box-shadow: none;
}
.b-calendar-grid-caption {
outline: none;
box-shadow: none;
}
.b-calendar-nav {
background: #f8f9fa;
border-bottom: 1px solid #d0d0d0;
border-radius: 0.25rem 0.25rem 0 0;
}
.b-calendar-nav .btn {
padding: 0;
margin: 0 !important;
border-radius: 0;
border: none;
box-shadow: none;
}
.b-calendar-nav .btn:hover {
background: rgba(0,0,0,0.1);
}
.b-calendar .btn.disabled {
opacity: 0.5;
}
.rkm-time-picker {
padding: 0;
margin: 0;
min-width: auto;
}
.rkm-time-picker .b-time {
width: auto;
min-width: auto;
max-width: 100%;
margin: 0;
display: flex !important;
}
.rkm-time-picker .mx-auto {
width: 100%;
}
.rkm-time-picker svg.bi {
transform: scale(0.9);
font-size: 100% !important;
}
.rkm-time-picker .b-form-spinbutton.b-time-hours + .d-flex svg {
height: 8px;
}
.rkm-date-picker .b-form-spinbutton {
border: none;
}
.rkm-time-picker .b-form-spinbutton {
border: none;
box-shadow: none;
padding: 0;
text-align: center;
}
.rkm-time-picker .b-form-spinbutton .btn {
padding: 0.75rem 1.5rem;
box-sizing: border-box;
height: auto;
}
.rkm-time-picker .b-form-spinbutton output {
width: 100%;
margin: 0;
justify-content: center;
}
.tabs {
border: 1px solid #dee2e6;
border-radius: 0.25rem;
}
.tab-content {
border-radius: 0 0 0.25rem 0.25rem;
}
.nav-tabs {
margin: 0;
padding: 0 0.5rem;
border-radius: 0.25rem 0.25rem 0 0;
}
.nav-tabs .nav-item a {
padding: 1rem 0.75rem;
}
.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link {
color: var(--primary);
background: none;
}
.tabs .card-header {
padding: 0;
background: #FFF;
border-radius: 3px 3px 0 0;
border-bottom-color: #dee2e6;
}
.nav-tabs .nav-link {
border: none;
border-radius: 0;
background: none;
margin: 0;
padding: 1rem;
font-weight: 500;
position: relative;
overflow: hidden;
color: var(--gray);
}
.nav-tabs .nav-link:after {
content: '';
position: absolute;
bottom: -3px;
left: 0.25rem;
width: calc(100% - 0.5rem);
height: 3px;
background: rgb(108 117 125 / 76%);
border-radius: 0.25rem 0.25rem 0 0;
transform: translateY(0) translateZ(0);
transition: transform 0.3s, color 0.3s;
}
.nav-tabs .nav-link.active:after, .nav-tabs .nav-link:hover:after {
transform: translateY(-3px) translateZ(0);
}
.nav-tabs .nav-link.active:after {
background: var(--primary);
}
.nav-tabs li:first-child .nav-link {
border-radius: 0.25rem 0 0 0;
}
.nav-tabs .nav-link.active {
color: var(--primary);
border-color: var(--primary);
}
.card-header-tabs {
margin: 0;
}
.tabs .nav-item.disabledTab a {
opacity: 0.55;
}
.nav-tabs .nav-item + .nav-item {
margin-left: 0;
}
.datatable-footer .form-group {
margin: 0;
}
.datatable-footer .col-form-label {
padding: 0;
display: flex;
justify-content: center;
align-items: center;
line-height: 1;
}
.table thead th {
border-bottom: none;
padding-bottom: calc(0.5rem + 1px);
box-shadow: 0px -1px 0px 0px #dee2e6 inset;
}
.table {
margin-bottom: 0.75rem;
}
.table th {
color: var(--gray-dark);
font-weight: 600;
background: #f1f1f1;
}
.table td {
height: 50px;
}
.table th[aria-sort]:hover {
background: #dee2e6;
}
.table th, .table td {
padding: $paddingTable;
vertical-align: middle;
}
.table-striped tbody tr:nth-of-type(odd) {
background: #FFF;
}
.table-striped tbody tr:nth-of-type(even) {
background: rgb(248 248 248);
}
.table tbody tr:not(.b-table-details):not(.b-table-empty-row):not(.b-table-busy-slot):hover > td {
background: rgb(0 123 255 / 10%);
border-top: 1px solid var(--primary);
border-bottom: 1px solid var(--primary);
}
.table tbody tr:not(.b-table-details thead tr):not(.b-table-details):not(.b-table-empty-row):not(.b-table-busy-slot):hover {
border: 1px solid var(--primary);
}
.b-table-has-details td {
border-bottom: 1px solid #dee2e6;
}
.pagination {
margin-bottom: 0;
}

View File

@@ -0,0 +1,20 @@
$alergiaPrimary: #6f42c1;
.btn-alergia { background-color: $alergiaPrimary !important; color: #FFFFFF !important; }
.btn-alergia:hover { background-color: darken($alergiaPrimary, 5%) !important; }
.btn-alergia:active { background-color: darken($alergiaPrimary, 10%) !important; }
$gestanteBase: #ffe6ff;
$gestantePrimary: #e600e6;
.gestante-base { background-color: $gestanteBase !important; }
.gestante-background { background-color: $gestantePrimary !important; }
.gestante-border { border-color: lighten($gestantePrimary, 45%) !important; }
.gestante-color { color: $gestantePrimary !important; }
.btn-gestante { background-color: $gestantePrimary !important; color: #FFFFFF !important; }
.btn-gestante:hover { background-color: darken($gestantePrimary, 5%) !important; }
.btn-gestante:active { background-color: darken($gestantePrimary, 10%) !important; }
:root{
--alergia-primary: #6f42c1;
--gestante-primary: #e600e6;
}

View File

@@ -0,0 +1,5 @@
.pronto-atendimento-classificacao-risco-NAO_URGENTE{ color: #0000FF !important; }
.pronto-atendimento-classificacao-risco-POUCO_URGENTE{ color: #008000 !important; }
.pronto-atendimento-classificacao-risco-URGENTE{ color: #FFFF00 !important; }
.pronto-atendimento-classificacao-risco-MUITO_URGENTE{ color: #FF7F00 !important; }
.pronto-atendimento-classificacao-risco-EMERGENCIA{ color: #FF0000 !important; }

View File

@@ -0,0 +1,301 @@
<template>
<div>
<rkm-form
class="login-form"
ref="form"
v-if="isLayoutLogin"
validate-on-submit
@success="login"
>
<h1>Boas vindas ao <span>Conecta<strong>SU</strong></span></h1>
<rkm-row class="justify-content-between">
<template v-if="!$scopedSlots.mensagem">
<rkm-col>
<h5>Por favor informe seus dados</h5>
</rkm-col>
<rkm-col cols="auto">
<h6>v{{ form.packageJSON.version }}</h6>
</rkm-col>
</template>
<template v-else>
<rkm-col>
<slot name="mensagem"></slot>
</rkm-col>
</template>
</rkm-row>
<rkm-row>
<rkm-col>
<rkm-form-input
:validations="validations.login.usuario"
name="Usuário"
v-model="model.login.usuario"
>
<rkm-input-group-prepend slot="prepend">
<rkm-input-group-text><rkm-icon :icon="['far', 'user-circle']" /></rkm-input-group-text>
</rkm-input-group-prepend>
</rkm-form-input>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col>
<rkm-form-input
:validations="validations.login.senha"
name="Senha"
:type="model.mostrarSenha ? 'text' : 'password'"
v-model="model.login.senha"
>
<rkm-input-group-prepend slot="prepend">
<rkm-input-group-text><rkm-icon icon="lock" /></rkm-input-group-text>
</rkm-input-group-prepend>
<rkm-input-group-append slot="append">
<rkm-input-group-text>
<rkm-form-checkbox v-model="model.mostrarSenha">Mostrar senha</rkm-form-checkbox>
</rkm-input-group-text>
</rkm-input-group-append>
</rkm-form-input>
</rkm-col>
</rkm-row>
<div class="login-form-actions">
<rkm-button
color="primary"
type="submit"
>
Realizar login
</rkm-button>
<rkm-button @click.stop="$router.push({ name: 'recuperar-senha', params: { customer: $route.params.customer } })">
Esqueci minha senha
</rkm-button>
</div>
</rkm-form>
<rkm-form
v-else
ref="form"
class="login-form"
>
<h1>Alterar Senha</h1>
<rkm-row class="flex-nowrap justify-content-between">
<rkm-col>
<h5>Para continuar, defina uma nova senha para seu usuário.</h5>
</rkm-col>
</rkm-row>
<rkm-row class="align-items-center">
<rkm-col>
<rkm-form-input
:validations="validations.trocaSenha.novaSenha"
label="Nova Senha"
type="password"
v-model="model.trocaSenha.novaSenha"
>
<rkm-input-group-prepend slot="prepend">
<rkm-input-group-text><rkm-icon icon="lock" /></rkm-input-group-text>
</rkm-input-group-prepend>
</rkm-form-input>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col>
<rkm-form-input
:validations="validations.trocaSenha.novaSenhaConfirmation"
label="Confirmar nova senha"
type="password"
v-model="model.trocaSenha.novaSenhaConfirmation"
>
<rkm-input-group-prepend slot="prepend">
<rkm-input-group-text><rkm-icon icon="lock" /></rkm-input-group-text>
</rkm-input-group-prepend>
</rkm-form-input>
</rkm-col>
</rkm-row>
<rkm-row class="justify-content-between">
<rkm-col cols="auto" class="order-2">
<rkm-button
@click="$refs.form.validate(trocarSenha)"
color="primary"
>
Continuar
</rkm-button>
</rkm-col>
<rkm-col cols="auto" class="order-1">
<rkm-button
@click="handleVoltarLogin()"
>
Voltar ao login
</rkm-button>
</rkm-col>
</rkm-row>
</rkm-form>
</div>
</template>
<script>
import packageJSON from '/package.json'
import { mapActions, mapGetters } from 'vuex'
import config from '@/config'
import { repository, error, stringCase } from '@/mixins'
export default {
created() {
this.form.packageJSON = packageJSON
this.repositories.cliente = this.getConectaSusRepository('intranet.cliente')
this.repositories.autenticacao = this.getConectaSusRepository('administracao.autenticacao')
},
mixins: [ repository, error, stringCase ],
data: () => ({
form: {
cliente: null,
layout: 'login',
packageJSON: null,
},
model: {
login: {
usuario: '',
senha: ''
},
trocaSenha: {
novaSenha: '',
novaSenhaConfirmation: ''
},
mostrarSenha: false,
},
repositories: {},
}),
computed: {
isLayoutLogin() {
return this.form.layout == 'login'
},
validations() {
return {
login: {
usuario: {
required: true,
min: 3
},
senha: {
required: true,
min: 8
},
},
trocaSenha: {
novaSenha: {
required: true,
senha: true
},
novaSenhaConfirmation: {
required: true,
is: this.model.trocaSenha.novaSenha
},
},
}
},
},
methods: {
...mapGetters({
autenticacao: 'getAutenticacao'
}),
...mapActions([
'clear',
'setAutenticacao',
'setCustomer',
'setLoading',
'unsetLoading',
]),
load(customer) {
this.setLoading()
this.repositories.cliente.index({link: customer})
.then(({ data }) => {
this.setCustomer(data)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleVoltarLogin() {
this.model = {
login: {
usuario: '',
senha: ''
},
trocaSenha: {
novaSenha: '',
novaSenhaConfirmation: ''
},
};
this.form.layout = 'login'
},
login() {
this.setLoading()
this.repositories.autenticacao.login(this.model.login)
.then(({ data }) => {
this.setAutenticacao(data)
this.unsetLoading()
if(data.usuario.trocaSenha) {
this.form.layout = 'troca-senha'
}else{
this.$router.push({ name: 'lotacao' })
}
})
.catch(error => {
this.unsetLoading()
this.handleError(error)
})
},
trocarSenha() {
this.setLoading()
this.repositories.autenticacao.trocarSenha(
this.autenticacao().usuario.id,
this.camelObjectKeysToSnakeRecursive(this.model.trocaSenha)
)
.then(() => {
this.unsetLoading()
this.$router.push({ name: 'lotacao' })
})
.catch(error => {
this.unsetLoading()
this.handleError(error)
})
},
},
mounted() {
const env = config.ENV
let customer = null
if (env === 'production') {
customer = window.location.hostname.split('.')[0]
} else {
customer = this.$route.params.customer
}
if(customer) {
this.clear().then(() => {
this.load(customer)
});
}
}
}
</script>

View File

@@ -0,0 +1,209 @@
<template>
<rkm-form ref="form" class="login-form">
<h1>Esqueceu sua senha?</h1>
<h5>Informe seus dados para gerar uma nova</h5>
<rkm-row>
<rkm-col>
<rkm-form-input
name="CPF"
v-model="model.cpf"
v-mask="mask.cpf"
:validations="validations.cpf"
placeholder="CPF"
>
<rkm-input-group-prepend slot="prepend">
<rkm-input-group-text><rkm-icon :icon="['far', 'user-circle']" /></rkm-input-group-text>
</rkm-input-group-prepend>
</rkm-form-input>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col>
<rkm-form-input
name="E-mail"
placeholder="E-mail"
v-model="model.email"
:validations="validations.email"
>
<rkm-input-group-prepend slot="prepend">
<rkm-input-group-text><rkm-icon :icon="['far', 'envelope']" /></rkm-input-group-text>
</rkm-input-group-prepend>
</rkm-form-input>
</rkm-col>
</rkm-row>
<div class="login-form-actions">
<rkm-button
color="primary"
type="submit"
@click="$refs.form.validate(recuperarAcesso)"
>
Recuperar acesso
</rkm-button>
<rkm-button
@click.stop="$router.push({ name: 'login', params: { customer: $route.params.customer } })"
>
Voltar ao login
</rkm-button>
</div>
</rkm-form>
</template>
<script>
import { mapActions } from "vuex"
import config from '@/config'
import { repository, error, mask, unmask } from '@/mixins'
export default {
created() {
this.repositories.cliente = this.getConectaSusRepository('intranet.cliente')
this.repositories.autenticacao = this.getConectaSusRepository('administracao.autenticacao')
},
mixins: [ repository, error, mask, unmask ],
data: () => ({
model: {
cpf: null,
email: null,
},
repositories: {},
}),
computed: {
validations() {
return {
cpf: {
required: true,
min_max_without_mask: 11,
cpf: true
},
email: {
required: true,
min: 5,
max: 180,
email: true,
},
}
},
},
methods: {
...mapActions([
'setLoading',
'unsetLoading',
'setAutenticacao',
'setCustomer',
'clear'
]),
load(customer) {
this.setLoading()
this.repositories.cliente.index({link: customer})
.then(({ data }) => {
this.setCustomer(data)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
recuperarAcesso() {
this.setLoading()
this.repositories.autenticacao.recuperarSenha({
cpf: this.unMaskCpf(this.model.cpf),
email: this.model.email
})
.then(({ data }) => {
this.showSaveNotification(data.message)
this.$router.push({ name: 'login', params: { customer: this.$route.params.customer } })
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
},
mounted() {
const env = config.ENV
let customer = null
if (env === 'production') {
customer = window.location.hostname.split('.')[0]
} else {
customer = this.$route.params.customer
}
this.clear().then(() => {
if(customer) {
this.load(customer)
}
});
},
}
</script>
<style>
button:focus {
opacity: 0.8;
}
#recuperar-senha-inner {
max-width: 450px;
}
#recuperar-senha-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
h5 {
font-size: 17px;
}
h6 {
font-size: 12px;
opacity: 0.5;
}
#metodo-recuperacao label:before, #metodo-recuperacao label:after {
display: none;
}
#metodo-recuperacao .custom-radio {
padding: 0;
margin: 0;
height: 100%;
}
#metodo-recuperacao label {
display: block;
border: 1px solid rgba(0,0,0,0.1);
padding: 2rem;
border-radius: 4px;
cursor: pointer;
background: #FFF;
transition: border-color 0.3s, box-shadow 0.3s;
}
.list-group-item {
cursor: pointer;
}
#metodo-recuperacao input:checked + label {
border-color: var(--primary);
box-shadow: 0px 0px 0 3px rgb(63 106 216 / 30%), 0px 0px 30px -10px var(--primary);
}
.list-group-item[selected] {
background: rgb(63 106 216 / 5%);
border-color: var(--primary);
z-index: 2;
}
#metodo-recuperacao label strong {
display: block;
font-size: 17px;
}
</style>

View File

@@ -0,0 +1,81 @@
<template>
<rkm-form
v-if="model"
ref="RkmForm"
>
<rkm-area
no-margin-top
title="BPA"
>
<rkm-row>
<rkm-col>
<rkm-form-select
:options="form.instrumentosRegistro"
label="Instrumento de registro padrão"
text-field="nome"
v-model="model.bpaIntrumentoRegistroPadrao"
value-field="codigo"
/>
</rkm-col>
</rkm-row>
</rkm-area>
</rkm-form>
</template>
<script>
// Vuex.
import { mapActions } from 'vuex'
// Mixins.
import { error, repository, stringCase } from '@/mixins'
export default {
created() {
this.repositories = {
configuracao: this.getConectaSusRepository('administracao.configuracao')
}
},
data: () => ({
form: null,
model: null,
cotaFormatoTrabalhoAtual: null,
}),
methods: {
...mapActions(['setLoading', 'unsetLoading']),
load() {
this.setLoading()
this.repositories.configuracao.edit()
.then(response => {
this.form = Object.assign(response.data.form)
this.model = Object.assign(response.data.model)
this.cotaFormatoTrabalhoAtual = this.model.cotaFormatoTrabalho
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
save() {
this.$refs.RkmForm.validate()
.then(valid => {
if (valid) {
this.setLoading()
this.repositories.configuracao.update(this.camelObjectKeysToSnakeRecursive(this.model))
.then(response => {
this.showSaveNotification()
this.$emit('save', response)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
}
})
},
},
mixins: [ error, repository, stringCase ],
mounted() {
this.load()
},
}
</script>

View File

@@ -0,0 +1,225 @@
<template>
<rkm-form
v-if="model"
ref="Form"
show-legend
>
<rkm-row>
<rkm-col cols="12">
<rkm-form-input-autocomplete
:repository="repositories.usuario"
:search-fields="fields"
label="Usuário"
text-field="cidadaoNome"
v-model="model.usuario"
value-field="id"
/>
</rkm-col>
</rkm-row>
<rkm-area
v-if="form.papeis.length > 0"
no-margin-top
title="Papéis"
>
<rkm-form-checkbox-group v-model="model.papeis">
<rkm-form-checkbox
v-for="papel in form.papeis"
:key="papel.id"
:value="papel.id"
>
{{papel.nome}}
</rkm-form-checkbox>
</rkm-form-checkbox-group>
</rkm-area>
<rkm-row v-if="form.modulos.length > 0">
<rkm-col>
<rkm-tabs>
<rkm-tab
v-for="modulo in form.modulos"
:key="modulo.id"
:title="modulo.nome"
>
<rkm-row>
<rkm-col cols="12">
<rkm-button
@click="checkAll(modulo.id)"
color="primary"
small
>
Marcar todos
</rkm-button>
<rkm-button
@click="uncheckAll(modulo.id)"
outline
small
>
Desmarcar todos
</rkm-button>
</rkm-col>
</rkm-row>
<rkm-row cols="3">
<rkm-col
v-for="funcionalidade in modulo.funcionalidades"
:key="funcionalidade.id"
>
<rkm-area
:title="funcionalidade.nome"
no-margin-top
>
<rkm-form-checkbox-group v-model="model.permissoes">
<rkm-form-checkbox
v-for="permissao in funcionalidade.permissoes"
:key="permissao.id"
:value="permissao.id"
>
{{permissao.nome}}
</rkm-form-checkbox>
</rkm-form-checkbox-group>
</rkm-area>
</rkm-col>
</rkm-row>
</rkm-tab>
</rkm-tabs>
</rkm-col>
</rkm-row>
</rkm-form>
</template>
<script>
// Mixins.
import {error, object, repository, stringCase} from '@/mixins'
// Vuex.
import {mapActions} from 'vuex'
export default {
computed: {
validations() {
return {
nome: {
required: true,
min: 3,
max: 100,
},
}
},
},
created() {
this.repositories = {
gestor: this.getConectaSusRepository('administracao.gestor'),
usuario: this.getConectaSusRepository('cadastro.sujeitoAtencao.usuario')
}
},
data: () => ({
fields: [
{key: 'cadastro_cidadao.nome', searchable: true},
],
form: null,
model: null,
}),
methods: {
...mapActions(['setLoading', 'unsetLoading']),
checkAll(moduloId) {
const modulo = this.form.modulos.find(modulo => modulo.id === moduloId)
if (modulo) {
modulo.funcionalidades.map(funcionalidade => funcionalidade.permissoes.map(permissao => {
if (!this.model.permissoes.includes(permissao.id)) {
this.model.permissoes.push(permissao.id)
}
}))
}
},
uncheckAll(moduloId) {
const modulo = this.form.modulos.find(modulo => modulo.id === moduloId)
if (modulo) {
modulo.funcionalidades.map(funcionalidade => funcionalidade.permissoes.map(permissao => {
const index = this.model.permissoes.findIndex(id => id === permissao.id)
if (index >= 0) {
this.model.permissoes.splice(index, 1)
}
}))
}
},
load() {
this.setLoading()
const request = this.id
? this.repositories.gestor.edit(this.id)
: this.repositories.gestor.create()
request
.then(({data}) => {
this.form = data.form
this.model = data.model
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
store() {
this.$refs.Form.validate()
.then(valid => {
if (!valid) return
this.setLoading()
const request = {
usuarioId: this.model.usuario ? this.model.usuario.id : null,
permissoes: this.model.permissoes,
papeis: this.model.papeis,
}
this.repositories.gestor.store(this.camelObjectKeysToSnakeRecursive(request))
.then(({ data }) => {
this.showSaveNotification()
this.$emit('save', data)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
})
},
update() {
this.$refs.Form.validate()
.then(valid => {
if (!valid) return
this.setLoading()
const request = {
usuarioId: this.model.usuario ? this.model.usuario.id : null,
permissoes: this.model.permissoes,
papeis: this.model.papeis,
}
this.repositories.gestor.update(this.id, this.camelObjectKeysToSnakeRecursive(request))
.then(({ data }) => {
this.showSaveNotification()
this.$emit('save', data)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
})
},
save() {
if (this.id) {
this.update()
} else {
this.store()
}
},
},
mixins: [error, object, repository, stringCase],
mounted() {
this.load()
},
props: {
id: {type: Number},
},
}
</script>

View File

@@ -0,0 +1,58 @@
<template>
<rkm-data-table
:fields="fields"
:repository="repositories.gestor"
ref="DataTable"
sort-by="nome"
>
<template v-slot:cell(actions)="{ item }">
<rkm-button
@click.stop="$router.push({name: 'administracao-gestor-edit', params: {id: item.id}})"
authorize="update-administracao-gestor"
color="primary"
small
title="Editar"
>
<rkm-icon icon="pen" />
</rkm-button>
<rkm-button
@click.stop="handleDestroy(item)"
authorize="destroy-administracao-gestor"
color="danger"
small
title="Excluir"
>
<rkm-icon icon="trash" />
</rkm-button>
</template>
</rkm-data-table>
</template>
<script>
// Mixins.
import {repository, error} from '@/mixins'
export default {
created() {
this.repositories = {
gestor: this.getConectaSusRepository('administracao.gestor')
}
},
data: () => ({
fields: [
{key: 'nome', label: 'Nome', name: 'cadastroCidadao.nome', searchable: true, sortable: true},
{class: 'col-min', key: 'actions', label: 'Ações', name: 'actions'},
],
}),
methods: {
handleDestroy({id}) {
const destroy = () => this.repositories.gestor.destroy(id)
.then(() => this.$refs.DataTable.refresh())
this.showDeleteConfirmation('Deseja realmente excluir o gestor?', destroy, this.handleError)
},
},
mixins: [repository, error],
}
</script>

View File

@@ -0,0 +1,213 @@
<template>
<rkm-form
v-if="model"
ref="RkmForm"
show-legend
>
<rkm-row>
<rkm-col cols="4">
<rkm-form-select
:options="form.tipos"
:validations="validations.tipo"
label="Tipo"
show-default-option
text-field="nome"
v-model="model.tipoId"
value-field="id"
/>
</rkm-col>
<rkm-col cols="4">
<rkm-form-multiselect
:options="form.unidades"
:select-limit="null"
@update:dropdownClosed="handleUnidadeClosed"
label="Unidades"
option-name-key="nome"
option-text-selecteds="unidade selecionado"
v-model="model.unidades"
value-field="id"
/>
</rkm-col>
<rkm-col cols="4">
<rkm-form-multiselect
:options="form.infraestruturas"
:select-limit="null"
@update:dropdownClosed="handleInfraestruturaClosed"
label="Local de atendimento"
option-name-key="nome"
option-text-selecteds="local selecionado"
v-model="model.infraestruturas"
value-field="id"
/>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col cols="4">
<rkm-form-multiselect
:options="form.usuarios"
:select-limit="null"
label="Usuários"
option-name-key="nome"
option-text-selecteds="usuário selecionado"
v-model="model.usuarios"
value-field="id"
/>
</rkm-col>
<rkm-col cols="8">
<rkm-form-input
:validations="validations.titulo"
label="Título"
v-model="model.titulo"
/>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col cols="12">
<rkm-text-editor
:validations="validations.mensagem"
label="Mensagem"
v-model="model.mensagem"
/>
</rkm-col>
</rkm-row>
</rkm-form>
</template>
<script>
// Mixins.
import {error, object, repository, stringCase} from '@/mixins'
// Vuex.
import {mapActions} from 'vuex'
export default {
computed: {
validations() {
return {
tipo: {required: true},
titulo: {
required: true,
min: 3,
max: 200,
},
mensagem: {
required: true,
min: 3,
max: 5000,
},
}
}
},
created() {
this.repositories = {
notificacao: this.getConectaSusRepository('administracao.notificacao')
}
},
data: () => ({
form: null,
model: null,
}),
methods: {
...mapActions(['setLoading', 'unsetLoading']),
load() {
this.setLoading()
if (this.id) {
this.repositories.notificacao.edit(this.id)
.then(response => {
this.form = Object.assign(response.data.form)
this.model = Object.assign(response.data.model)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
} else {
this.repositories.notificacao.create()
.then(response => {
this.form = Object.assign(response.data.form)
this.model = Object.assign(response.data.model)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
}
},
handleUnidadeClosed() {
const request = {
listar: 'infraestruturas',
unidades: this.model.unidades,
}
this.setLoading()
this.repositories.notificacao.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.infraestruturas = Object.assign(response.data.infraestruturas)
this.form.usuarios = Object.assign(response.data.usuarios)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleInfraestruturaClosed() {
const request = {
listar: 'usuarios',
infraestruturas: this.model.infraestruturas,
}
this.setLoading()
this.repositories.notificacao.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.usuarios = Object.assign(response.data.usuarios)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
save() {
this.$refs.RkmForm.validate()
.then(valid => {
if (valid === false) return
const request = {
tipoId: this.model.tipoId,
infraestruturas: this.model.infraestruturas,
usuarios: this.model.usuarios,
titulo: this.model.titulo,
mensagem: this.model.mensagem,
}
this.setLoading()
if (this.model.id) {
this.repositories.notificacao.update(this.model.id, this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.showSaveNotification()
this.$emit('saved', response)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
} else {
this.repositories.notificacao.store(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.showSaveNotification()
this.$emit('saved', response)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
}
})
},
},
mixins: [error, object, repository, stringCase],
mounted() {
this.load()
},
props: {
id: {type: Number},
},
}
</script>

View File

@@ -0,0 +1,61 @@
<template>
<rkm-data-table
:fields="fields"
:repository="repositories.notificacao"
ref="DataTable"
sort-by="dataHoraAtualizacao"
:sort-desc="true"
>
<template v-slot:cell(actions)="{item}">
<rkm-button
@click.stop="$router.push({name: 'administracao-notificacao-edit', params: {id: item.id}})"
authorize="update-administracao-notificacao"
color="primary"
small
title="Editar"
>
<rkm-icon icon="pen" />
</rkm-button>
<rkm-button
@click.stop="destroy(item)"
authorize="destroy-administracao-notificacao"
color="danger"
small
title="Excluir"
>
<rkm-icon icon="trash" />
</rkm-button>
</template>
</rkm-data-table>
</template>
<script>
// Mixins.
import {error, repository} from '@/mixins'
export default {
created() {
this.repositories = {
notificacao: this.getConectaSusRepository('administracao.notificacao')
}
},
data: () => ({
fields: [
{key: 'dataHoraAtualizacao', label: 'Data/Hora', searchable: true, sortable: true},
{key: 'tipoNome', label: 'Tipo', name: 'administracao_notificacao_tipo.nome', searchable: true, sortable: true},
{key: 'titulo', label: 'Título', name: 'administracao_notificacao.titulo', searchable: true, sortable: true},
{class: 'col-min text-center', key: 'actions', label: 'Ações'},
],
}),
methods: {
destroy({ id }) {
const destroy = () => this.repositories.notificacao.destroy(id)
.then(() => this.$refs.DataTable.refresh())
this.showDeleteConfirmation('Deseja realmente excluir a notificação?', destroy, this.handleError)
},
},
mixins: [error, repository],
}
</script>

View File

@@ -0,0 +1,135 @@
<template>
<div>
<rkm-modal
extra-large
ref="RkmModal"
title="Notificações"
>
<rkm-table
:fields="fields"
:items="items"
sort-by="dataHoraAtualizacao"
:sort-desc="true"
:sort-compare="sortCompare"
@sort-changed="handleSortChange"
>
<template v-slot:cell(actions)="{item}">
<rkm-button
@click="$refs.ModalMensagem.open(item)"
color="primary"
small
title="Mensagem"
>
<rkm-icon icon="search" />
</rkm-button>
</template>
</rkm-table>
<template v-slot:footer="{cancel}">
<rkm-button
:disabled="!!loading"
@click="cancel"
color="secondary"
>
Fechar
</rkm-button>
</template>
</rkm-modal>
<ModalMensagem ref="ModalMensagem" />
</div>
</template>
<script>
// Components.
import ModalMensagem from './components/mensagem/Modal'
// Mixins.
import {error, repository, stringCase} from '@/mixins'
// Vuex.
import {mapActions, mapGetters} from 'vuex'
export default {
components: {ModalMensagem},
computed: {
...mapGetters({
loading: 'getLoading',
lotacao: 'getLotacao',
}),
},
created() {
this.repositories = {
notificacao: this.getConectaSusRepository('administracao.notificacao'),
}
if (!this.model) {
this.model = {
sortBy: 'dataHoraAtualizacao',
sortDesc: true
};
} else {
this.model.sortBy = this.sortBy || 'dataHoraAtualizacao';
this.model.sortDesc = this.sortDesc !== undefined ? this.sortDesc : true;
}
},
data: () => ({
fields: [
{key: 'dataHoraAtualizacao',
label: 'Data/Hora',
searchable: true,
sortable: true,
sortDirection: 'desc'
},
{key: 'tipoNome', label: 'Tipo', sortable: true},
{key: 'titulo', label: 'Título', sortable: true},
{filterByFormatted: true, formatter: (value) => value ? 'Sim' : 'Não' , key: 'visualizado', label: 'Visualizada', sortable: true},
{class: 'col-min text-center', key: 'actions', label: 'Ações'},
],
items: [],
order: {
by: 'dataHoraAtualizacao',
dir: 'desc'
},
model: {
sortBy: 'dataHoraAtualizacao',
sortDesc: true
}
}),
methods: {
...mapActions(['setLoading', 'unsetLoading']),
open() {
this.load()
this.$refs.RkmModal.show()
},
load() {
const request = {
lotacaoId: this.lotacao.id,
order: this.order
}
this.setLoading()
this.repositories.notificacao.index(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.items = Object.assign(response.data)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleSortChange(ctx) {
if (ctx) {
this.order.by = ctx.sortby
this.order.dir = ctx.sortDesc ? 'desc' : 'asc'
}
this.load()
},
sortCompare(a, b, key) {
if (key === 'dataHoraAtualizacao') {
return new Date(a[key]) - new Date(b[key]);
}
return a[key] < b[key] ? -1 : a[key] > b[key] ? 1 : 0;
}
},
mixins: [error, repository, stringCase],
}
</script>

View File

@@ -0,0 +1,78 @@
<template>
<rkm-modal
:title="title"
@hide="hide"
extra-large
ref="RkmModal"
>
<rkm-row>
<rkm-col cols="12">
<span v-html="item ? item.mensagem : null" />
</rkm-col>
</rkm-row>
<template v-slot:footer="{cancel}">
<rkm-button
@click="cancel"
color="secondary"
>
Fechar
</rkm-button>
</template>
</rkm-modal>
</template>
<script>
// Mixins.
import {error, repository, stringCase} from '@/mixins'
// Vuex.
import {mapActions, mapGetters} from 'vuex'
export default {
computed: {
...mapGetters({quantidadeNotificacoesNaoLidas: 'getQuantidadeNotificacoesNaoLidas'}),
title() {
return this.item ? `${this.item.tipoNome} - ${this.item.titulo}` : null
},
},
created() {
this.repositories = {
notificacao: this.getConectaSusRepository('administracao.notificacao'),
}
},
data: () => ({
item: null,
}),
methods: {
...mapActions([
'setLoading',
'setQuantidadeNotificacoesNaoLidas',
'unsetLoading',
]),
open(item) {
this.item = item
this.$refs.RkmModal.show()
},
hide() {
if (this.item.visualizado) return
const request = {
lotacaoId: this.item.lotacaoId,
visualizado: true,
}
this.setLoading()
this.repositories.notificacao.patch(this.item.id, this.camelObjectKeysToSnakeRecursive(request))
.then(() => {
this.item.visualizado = true
this.setQuantidadeNotificacoesNaoLidas(this.quantidadeNotificacoesNaoLidas - 1)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
},
mixins: [error, repository, stringCase],
}
</script>

View File

@@ -0,0 +1,184 @@
<template>
<rkm-form
ref="Form"
show-legend
>
<rkm-row>
<rkm-col>
<rkm-form-input
label="Nome"
v-model="model.nome"
:validations="validations.nome"
/>
</rkm-col>
</rkm-row>
<rkm-row v-if="form.modulos.length > 0">
<rkm-col>
<rkm-tabs>
<rkm-tab
v-for="modulo in form.modulos"
:key="modulo.id"
:title="modulo.nome"
>
<rkm-row>
<rkm-col>
<rkm-button
@click="checkAll(modulo.id)"
color="primary"
small
>
Marcar todos
</rkm-button>
<rkm-button
@click="uncheckAll(modulo.id)"
outline
small
>
Desmarcar todos
</rkm-button>
</rkm-col>
</rkm-row>
<rkm-row cols="3">
<rkm-col
v-for="funcionalidade in modulo.funcionalidades"
:key="funcionalidade.id"
>
<rkm-area
:title="funcionalidade.nome"
no-margin-top
>
<rkm-form-checkbox-group v-model="model.permissoes">
<rkm-form-checkbox
v-for="permissao in funcionalidade.permissoes"
:key="permissao.id"
:value="permissao.id"
>
{{ permissao.nome }}
</rkm-form-checkbox>
</rkm-form-checkbox-group>
</rkm-area>
</rkm-col>
</rkm-row>
</rkm-tab>
</rkm-tabs>
</rkm-col>
</rkm-row>
</rkm-form>
</template>
<script>
// Vuex.
import { mapActions } from 'vuex'
// Mixins.
import { repository, stringCase, object, error } from '@/mixins'
export default {
computed: {
validations() {
return {
nome: {
required: true,
min: 3,
max: 100,
},
}
},
},
created() {
this.repositories = {
papel: this.getConectaSusRepository('administracao.papel')
}
this.load()
},
data: () => ({
form: {
modulos: []
},
model: {
permissoes: []
},
}),
methods: {
...mapActions(['setLoading', 'unsetLoading']),
checkAll(moduloId) {
this.form.modulos.find(m => m.id === moduloId).funcionalidades
.map(f => f.permissoes.map(permissao => {
if (!this.model.permissoes.includes(permissao.id)) {
this.model.permissoes.push(permissao.id)
}
}))
},
uncheckAll(moduloId) {
this.form.modulos.find(m => m.id === moduloId).funcionalidades
.map(f => f.permissoes.map(permissao => {
const index = this.model.permissoes.findIndex(id => id === permissao.id)
if (index >= 0) {
this.model.permissoes.splice(index, 1)
}
}))
},
load() {
this.setLoading()
const request = this.id
? this.repositories.papel.edit(this.id)
: this.repositories.papel.create()
request
.then(({ data }) => {
this.form = data.form
this.model = data.model
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
save() {
this.$refs.Form.validate()
.then(valid => {
if (!valid) return
this.setLoading()
const request = this.id
? this.repositories.papel.update(this.id, this.prepareData())
: this.repositories.papel.store(this.prepareData())
request
.then(({ data }) => {
this.showSaveNotification()
this.$emit('save', data)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
})
},
prepareData() {
return this.camelObjectKeysToSnakeRecursive(this.clone(this.model))
},
},
mixins: [repository, stringCase, object, error],
props: {
id: { type: Number }
},
}
</script>

View File

@@ -0,0 +1,71 @@
<template>
<rkm-data-table
:fields="fields"
:repository="repositories.papel"
ref="DataTable"
sort-by="nome"
>
<template v-slot:cell(actions)="{ item }">
<rkm-button
@click.stop="$router.push({ name: 'administracao-papel-edit', params: { id: item.id } })"
color="primary"
authorize="update-administracao-papel"
title="Editar"
small
>
<rkm-icon icon="pen" />
</rkm-button>
<rkm-button
@click.stop="handleDestroy(item)"
color="danger"
authorize="destroy-administracao-papel"
title="Excluir"
small
>
<rkm-icon icon="trash" />
</rkm-button>
</template>
</rkm-data-table>
</template>
<script>
// Mixins.
import { repository, error } from '@/mixins'
export default {
created() {
this.repositories = {
papel: this.getConectaSusRepository('administracao.papel')
}
},
data: () => ({
fields: [
{
name: 'papel.nome',
key: 'nome',
label: 'Nome',
searchable: true,
sortable: true
},
{
name: 'actions',
key: 'actions',
label: 'Ações',
class: 'col-min'
},
],
}),
methods: {
handleDestroy({ id }) {
const destroy = () => this.repositories.papel.destroy(id)
.then(() => this.$refs.DataTable.refresh())
this.showDeleteConfirmation('Deseja realmente excluir o papel?', destroy, this.handleError)
},
},
mixins: [repository, error],
}
</script>

View File

@@ -0,0 +1,211 @@
<template>
<rkm-form ref="Form">
<rkm-row>
<rkm-col cols="6">
<rkm-form-input
disabled
label="Unidade"
v-model="form.unidadeNomeFantasia"
/>
</rkm-col>
<rkm-col cols="6">
<rkm-form-input
disabled
label="Local de atendimento"
v-model="form.infraestruturaNome"
/>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col cols="6">
<rkm-form-input
disabled
label="Usuário"
v-model="form.usuarioNome"
/>
</rkm-col>
<rkm-col cols="6">
<rkm-form-input
disabled
label="CBO"
v-model="form.ocupacaoNome"
/>
</rkm-col>
</rkm-row>
<rkm-area
v-if="form.papeis.length > 0"
no-margin-top
title="Papéis"
>
<rkm-form-checkbox-group v-model="model.papeis">
<rkm-form-checkbox
v-for="papel in form.papeis"
:key="papel.id"
:value="papel.id"
>
{{ papel.nome }}
</rkm-form-checkbox>
</rkm-form-checkbox-group>
</rkm-area>
<rkm-area
v-if="form.modulos.length > 0"
no-margin-top
title="Permissões"
>
<rkm-row>
<rkm-col>
<rkm-tabs>
<rkm-tab
v-for="modulo in form.modulos"
:key="modulo.id"
:title="modulo.nome"
>
<rkm-row>
<rkm-col>
<rkm-button
@click="checkAll(modulo.id)"
color="primary"
small
>
Marcar todos
</rkm-button>
<rkm-button
@click="uncheckAll(modulo.id)"
outline
small
>
Desmarcar todos
</rkm-button>
</rkm-col>
</rkm-row>
<rkm-row cols="3">
<rkm-col
v-for="funcionalidade in modulo.funcionalidades"
:key="funcionalidade.id"
>
<rkm-area
:title="funcionalidade.nome"
no-margin-top
>
<rkm-form-checkbox-group v-model="model.permissoes">
<rkm-form-checkbox
v-for="permissao in funcionalidade.permissoes"
:key="permissao.id"
:value="permissao.id"
>
{{ permissao.nome }}
</rkm-form-checkbox>
</rkm-form-checkbox-group>
</rkm-area>
</rkm-col>
</rkm-row>
</rkm-tab>
</rkm-tabs>
</rkm-col>
</rkm-row>
</rkm-area>
</rkm-form>
</template>
<script>
// Vuex.
import { mapActions } from 'vuex'
// Mixins.
import { repository, stringCase, object, error } from '@/mixins'
export default {
created() {
this.repositories = {
permissao: this.getConectaSusRepository('administracao.permissao')
}
this.load()
},
data: () => ({
form: {
papeis: [],
modulos: []
},
model: {
papeis: [],
permissoes: []
},
}),
methods: {
...mapActions(['setLoading', 'unsetLoading']),
checkAll(moduloId) {
this.form.modulos.find(m => m.id === moduloId).funcionalidades
.map(f => f.permissoes.map(permissao => {
if (!this.model.permissoes.includes(permissao.id)) {
this.model.permissoes.push(permissao.id)
}
}))
},
uncheckAll(moduloId) {
this.form.modulos.find(m => m.id === moduloId).funcionalidades
.map(f => f.permissoes.map(permissao => {
const index = this.model.permissoes.findIndex(id => id === permissao.id)
if (index >= 0) {
this.model.permissoes.splice(index, 1)
}
}))
},
load() {
this.setLoading()
this.repositories.permissao.edit(this.id)
.then(({ data }) => {
this.form = data.form
this.model = data.model
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
save() {
this.$refs.Form.validate()
.then(valid => {
if (!valid) return
this.setLoading()
this.repositories.permissao.update(this.id, this.prepareData())
.then(({ data }) => {
this.showSaveNotification()
this.$emit('save', data)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
})
},
prepareData() {
return this.camelObjectKeysToSnakeRecursive(this.clone(this.model))
},
},
mixins: [repository, stringCase, object, error],
props: {
id: { type: Number, required: true }
},
}
</script>

View File

@@ -0,0 +1,73 @@
<template>
<rkm-data-table
:fields="fields"
:repository="repositories.permissao"
ref="DataTable"
sort-by="unidadeNomeFantasia"
>
<template v-slot:cell(actions)="{ item }">
<rkm-button
@click.stop="$router.push({ name: 'administracao-permissao-edit', params: { id: item.id } })"
color="primary"
authorize="update-administracao-permissao"
title="Alterar permissões"
small
>
<rkm-icon icon="key" />
</rkm-button>
</template>
</rkm-data-table>
</template>
<script>
// Mixins.
import { repository } from '@/mixins'
export default {
created() {
this.repositories = {
permissao: this.getConectaSusRepository('administracao.permissao')
}
},
data: () => ({
fields: [
{
name: 'unidade.nomeFantasia',
key: 'unidadeNomeFantasia',
label: 'Unidade',
searchable: true,
sortable: true
},
{
name: 'infraestrutura.nome',
key: 'infraestruturaNome',
label: 'Local de atendimento',
searchable: true,
sortable: true
},
{
name: 'cidadao.nome',
key: 'usuarioNome',
label: 'Usuário',
searchable: true,
sortable: true
},
{
name: 'ocupacao.nome',
key: 'ocupacaoNome',
label: 'CBO',
searchable: true,
sortable: true
},
{
name: 'actions',
key: 'actions',
label: 'Ações',
class: 'col-min'
},
],
}),
mixins: [repository],
}
</script>

View File

@@ -0,0 +1,620 @@
<template>
<div v-if="model">
<rkm-area
no-margin-top
title="Filtros"
type="fieldset"
>
<rkm-form
ref="Form"
show-legend
>
<rkm-row>
<rkm-col cols="6">
<rkm-form-date-picker
:end-validations="validations.data"
:max-date="additionals.dataAtual"
:range-inline="false"
:start-validations="validations.data"
end-label="Até"
range
start-label="Data de"
v-model="model.data"
/>
</rkm-col>
<rkm-col cols="3">
<rkm-form-time-picker
:disabled="!model.data.start && !model.data.end"
:validations="validations.hora.start"
@input="handleHoraStartInput"
label="Hora de"
v-model="model.hora.start"
/>
</rkm-col>
<rkm-col cols="3">
<rkm-form-time-picker
:disabled="!model.data.start && !model.data.end"
:validations="validations.hora.end"
@input="handleHoraEndInput"
label="Até"
v-model="model.hora.end"
/>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col cols="6">
<rkm-form-select
:options="form.usuarios"
@change="handleUsuarioChange"
default-option-text="Todos"
label="Usuário"
show-default-option
text-field="nome"
v-model="model.usuarioId"
value-field="id"
/>
</rkm-col>
<rkm-col cols="6">
<rkm-form-select
:options="form.unidades"
@change="handleUnidadeChange"
default-option-text="Todas"
label="Unidade"
show-default-option
text-field="nomeFantasia"
v-model="model.unidadeId"
value-field="id"
/>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col cols="6">
<rkm-form-select
:options="form.infraestruturas"
@change="handleInfraestruturaChange"
default-option-text="Todos"
label="Local de atendimento"
show-default-option
text-field="nome"
v-model="model.infraestruturaId"
value-field="id"
/>
</rkm-col>
<rkm-col cols="6">
<rkm-form-select
:options="form.modulos"
@change="handleModuloChange"
default-option-text="Todos"
label="Módulo"
show-default-option
text-field="nome"
v-model="model.moduloId"
value-field="id"
/>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col cols="6">
<rkm-form-select
:options="form.funcionalidades"
@change="handleFuncionalidadeChange"
default-option-text="Todas"
label="Funcionalidade"
show-default-option
text-field="nome"
v-model="model.funcionalidadeId"
value-field="id"
/>
</rkm-col>
<rkm-col cols="2">
<rkm-button
:label="false"
@click="$refs.Form.validate(search)"
color="primary"
>
Buscar
</rkm-button>
<rkm-button
:label="false"
@click="reset"
>
Limpar
</rkm-button>
</rkm-col>
</rkm-row>
</rkm-form>
</rkm-area>
<template v-if="data">
<rkm-area
no-margin-top
title="Resultado"
>
<template v-slot:trailing>
<rkm-button
@click="print"
color="info"
small
title="Imprimir"
>
<rkm-icon icon="print" />
</rkm-button>
<rkm-button
@click="exportTo"
color="info"
small
title="Exportar"
>
<rkm-icon icon="file-export" />
</rkm-button>
</template>
<rkm-table
:fields="fields"
:items="data.itens"
:sort-by="sortBy"
:sort-desc="sortDesc"
@sort-changed="onSortChanged"
hide-filters
>
<template v-slot:custom-foot>
<rkm-tr>
<rkm-th
class="text-right"
colspan="7"
>
Total: {{data.totalizadores.totalAcessos}}
</rkm-th>
</rkm-tr>
</template>
</rkm-table>
</rkm-area>
</template>
</div>
</template>
<script>
// Mixins.
import {error, file, formats, repository, stringCase} from '@/mixins'
// Others.
import moment from 'moment'
// Vuex.
import {mapActions} from 'vuex'
export default {
computed: {
validations() {
return {
data: {required: true},
hora: {
start: {
max_hour: this.model.hora.end ? this.formatTime(this.model.hora.end) : null,
},
end: {
min_hour: this.model.hora.start ? this.formatTime(this.model.hora.start) : null,
},
},
}
},
},
created() {
this.repositories = {
exportacao: this.getConectaSusRepository('exportacao.administracao'),
impressao: this.getConectaSusRepository('impressao.administracao'),
relatorioAcessoFuncionalidade: this.getConectaSusRepository('administracao.relatorioAcessoFuncionalidade'),
}
this.load()
},
data() {
return {
additionals: null,
form: null,
data: null,
fields: [
{key: 'data', label: 'Data', sortable: true},
{key: 'hora', label: 'Hora', sortable: true},
{key: 'usuarioNome', label: 'Usuário', sortable: true},
{key: 'unidadeNomeFantasia', label: 'Unidade', sortable: true},
{key: 'infraestruturaNome', label: 'Local de atendimento', sortable: true},
{key: 'moduloNome', label: 'Módulo', sortable: true},
{key: 'funcionalidadeNome', label: 'Funcionalidade', sortable: true},
],
model: null,
others: {moment},
sortBy: 'data',
sortDesc: false,
}
},
methods: {
...mapActions(['setLoading', 'unsetLoading']),
load() {
this.setLoading()
this.repositories.relatorioAcessoFuncionalidade.create()
.then((response) => {
this.additionals = response.data.additionals
this.form = response.data.form
this.model = response.data.model
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
search() {
const request = {
data: {
start: this.others.moment(this.model.data.start).format('DD/MM/YYYY'),
end: this.others.moment(this.model.data.end).format('DD/MM/YYYY'),
},
hora: {
start: this.model.hora.start,
end: this.model.hora.end,
},
usuarioId: this.model.usuarioId,
unidadeId: this.model.unidadeId,
infraestruturaId: this.model.infraestruturaId,
moduloId: this.model.moduloId,
funcionalidadeId: this.model.funcionalidadeId,
}
this.setLoading()
this.repositories.relatorioAcessoFuncionalidade.index(this.camelObjectKeysToSnakeRecursive(request))
.then(response => this.data = Object.assign(response.data))
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
reset() {
this.form.usuarios = []
this.form.unidades = []
this.form.infraestruturas = []
this.form.modulos = []
this.form.funcionalidades = []
this.model.data = {
start: null,
end: null,
}
this.model.hora = {
start: null,
end: null,
}
this.model.usuarioId = null
this.model.unidadeId = null
this.model.infraestruturaId = null
this.model.moduloId = null
this.model.funcionalidadeId = null
this.data = null
this.$nextTick().then(() => this.$refs.Form.resetValidator())
},
print() {
this.setLoading()
const request = {
data: {
start: this.others.moment(this.model.data.start).format('DD/MM/YYYY'),
end: this.others.moment(this.model.data.end).format('DD/MM/YYYY'),
},
hora: {
start: this.model.hora.start,
end: this.model.hora.end,
},
usuarioId: this.model.usuarioId,
unidadeId: this.model.unidadeId,
infraestruturaId: this.model.infraestruturaId,
moduloId: this.model.moduloId,
funcionalidadeId: this.model.funcionalidadeId,
sortBy: this.sortBy ? this.camelToSnake(this.sortBy) : null,
sortDesc: this.sortDesc,
}
this.repositories.impressao.relatorioAcessoFuncionalidade(this.camelObjectKeysToSnakeRecursive(request))
.then(({ data, headers }) => this.openFile(data, headers['content-type']))
.finally(() => this.unsetLoading())
},
exportTo() {
this.setLoading()
const request = {
data: {
start: this.others.moment(this.model.data.start).format('DD/MM/YYYY'),
end: this.others.moment(this.model.data.end).format('DD/MM/YYYY'),
},
hora: {
start: this.model.hora.start,
end: this.model.hora.end,
},
usuarioId: this.model.usuarioId,
unidadeId: this.model.unidadeId,
infraestruturaId: this.model.infraestruturaId,
moduloId: this.model.moduloId,
funcionalidadeId: this.model.funcionalidadeId,
sortBy: this.sortBy ? this.camelToSnake(this.sortBy) : null,
sortDesc: this.sortDesc,
}
this.repositories.exportacao.relatorioAcessoFuncionalidade(this.camelObjectKeysToSnakeRecursive(request))
.then(({ data, headers }) => this.openFile(data, headers['content-type']))
.finally(() => this.unsetLoading())
},
handleHoraStartInput(value) {
this.form.usuarios = []
this.form.unidades = []
this.form.infraestruturas = []
this.form.modulos = []
this.form.funcionalidades = []
this.model.usuarioId = null
this.model.unidadeId = null
this.model.infraestruturaId = null
this.model.moduloId = null
this.model.funcionalidadeId = null
this.data = null
const request = {
listar: 'usuarios',
data: {
start: this.others.moment(this.model.data.start).format('DD/MM/YYYY'),
end: this.others.moment(this.model.data.end).format('DD/MM/YYYY'),
},
hora: {
start: value,
end: this.model.hora.end,
},
}
this.setLoading()
this.repositories.relatorioAcessoFuncionalidade.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.usuarios = Object.assign(response.data.usuarios)
this.form.unidades = Object.assign(response.data.unidades)
this.form.infraestruturas = Object.assign(response.data.infraestruturas)
this.form.modulos = Object.assign(response.data.modulos)
this.form.funcionalidades = Object.assign(response.data.funcionalidades)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleHoraEndInput(value) {
this.form.usuarios = []
this.form.unidades = []
this.form.infraestruturas = []
this.form.modulos = []
this.form.funcionalidades = []
this.model.usuarioId = null
this.model.unidadeId = null
this.model.infraestruturaId = null
this.model.moduloId = null
this.model.funcionalidadeId = null
this.data = null
const request = {
listar: 'usuarios',
data: {
start: this.others.moment(this.model.data.start).format('DD/MM/YYYY'),
end: this.others.moment(this.model.data.end).format('DD/MM/YYYY'),
},
hora: {
start: this.model.hora.start,
end: value,
},
}
this.setLoading()
this.repositories.relatorioAcessoFuncionalidade.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.usuarios = Object.assign(response.data.usuarios)
this.form.unidades = Object.assign(response.data.unidades)
this.form.infraestruturas = Object.assign(response.data.infraestruturas)
this.form.modulos = Object.assign(response.data.modulos)
this.form.funcionalidades = Object.assign(response.data.funcionalidades)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleUsuarioChange(value) {
this.form.unidades = []
this.form.infraestruturas = []
this.form.modulos = []
this.form.funcionalidades = []
this.model.unidadeId = null
this.model.infraestruturaId = null
this.model.moduloId = null
this.model.funcionalidadeId = null
this.data = null
const request = {
listar: 'unidades',
data: {
start: this.others.moment(this.model.data.start).format('DD/MM/YYYY'),
end: this.others.moment(this.model.data.end).format('DD/MM/YYYY'),
},
hora: {
start: this.model.hora.start,
end: this.model.hora.end,
},
usuarioId: value,
}
this.setLoading()
this.repositories.relatorioAcessoFuncionalidade.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.unidades = Object.assign(response.data.unidades)
this.form.infraestruturas = Object.assign(response.data.infraestruturas)
this.form.modulos = Object.assign(response.data.modulos)
this.form.funcionalidades = Object.assign(response.data.funcionalidades)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleUnidadeChange(value) {
this.form.infraestruturas = []
this.form.modulos = []
this.form.funcionalidades = []
this.model.infraestruturaId = null
this.model.moduloId = null
this.model.funcionalidadeId = null
this.data = null
const request = {
listar: 'infraestruturas',
data: {
start: this.others.moment(this.model.data.start).format('DD/MM/YYYY'),
end: this.others.moment(this.model.data.end).format('DD/MM/YYYY'),
},
hora: {
start: this.model.hora.start,
end: this.model.hora.end,
},
usuarioId: this.model.usuarioId,
unidadeId: value,
}
this.setLoading()
this.repositories.relatorioAcessoFuncionalidade.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.infraestruturas = Object.assign(response.data.infraestruturas)
this.form.modulos = Object.assign(response.data.modulos)
this.form.funcionalidades = Object.assign(response.data.funcionalidades)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleInfraestruturaChange(value) {
this.form.modulos = []
this.form.funcionalidades = []
this.model.moduloId = null
this.model.funcionalidadeId = null
this.data = null
const request = {
listar: 'modulos',
data: {
start: this.others.moment(this.model.data.start).format('DD/MM/YYYY'),
end: this.others.moment(this.model.data.end).format('DD/MM/YYYY'),
},
hora: {
start: this.model.hora.start,
end: this.model.hora.end,
},
usuarioId: this.model.usuarioId,
unidadeId: this.model.unidadeId,
infraestruturaId: value,
}
this.setLoading()
this.repositories.relatorioAcessoFuncionalidade.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.modulos = Object.assign(response.data.modulos)
this.form.funcionalidades = Object.assign(response.data.funcionalidades)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleModuloChange() {
this.form.funcionalidades = []
this.model.funcionalidadeId = null
this.data = null
const request = {
listar: 'funcionalidades',
data: {
start: this.others.moment(this.model.data.start).format('DD/MM/YYYY'),
end: this.others.moment(this.model.data.end).format('DD/MM/YYYY'),
},
hora: {
start: this.model.hora.start,
end: this.model.hora.end,
},
usuarioId: this.model.usuarioId,
unidadeId: this.model.unidadeId,
infraestruturaId: this.model.infraestruturaId,
moduloId: this.model.moduloId,
}
this.setLoading()
this.repositories.relatorioAcessoFuncionalidade.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => this.form.funcionalidades = Object.assign(response.data.funcionalidades))
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleFuncionalidadeChange() {
this.data = null
},
onSortChanged(context) {
this.sortBy = context.sortBy
this.sortDesc = context.sortDesc
},
},
mixins: [error, file, formats, repository, stringCase],
watch: {
'model.data': {
deep: true,
handler(value) {
this.form.usuarios = []
this.form.unidades = []
this.form.infraestruturas = []
this.form.modulos = []
this.form.funcionalidades = []
this.model.hora = {
start: null,
end: null,
}
this.model.usuarioId = null
this.model.unidadeId = null
this.model.infraestruturaId = null
this.model.moduloId = null
this.model.funcionalidadeId = null
this.data = null
if (value.start === null || value.end === null) {
return
}
const request = {
listar: 'usuarios',
data: {
start: this.others.moment(this.model.data.start).format('DD/MM/YYYY'),
end: this.others.moment(this.model.data.end).format('DD/MM/YYYY'),
},
}
this.setLoading()
this.repositories.relatorioAcessoFuncionalidade.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.usuarios = Object.assign(response.data.usuarios)
this.form.unidades = Object.assign(response.data.unidades)
this.form.infraestruturas = Object.assign(response.data.infraestruturas)
this.form.modulos = Object.assign(response.data.modulos)
this.form.funcionalidades = Object.assign(response.data.funcionalidades)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
},
},
}
</script>

View File

@@ -0,0 +1,464 @@
<template>
<div v-if="model">
<rkm-area
no-margin-top
title="Filtros"
type="fieldset"
>
<rkm-form
ref="Form"
show-legend
>
<rkm-row>
<rkm-col cols="6">
<rkm-form-date-picker
:end-validations="validations.data"
:max-date="additionals.dataAtual"
:range-inline="false"
:start-validations="validations.data"
end-label="Até"
range
start-label="Data de"
v-model="model.data"
/>
</rkm-col>
<rkm-col cols="3">
<rkm-form-time-picker
:disabled="!model.data.start && !model.data.end"
:validations="validations.hora.start"
@input="handleHoraStartInput"
label="Hora de"
v-model="model.hora.start"
/>
</rkm-col>
<rkm-col cols="3">
<rkm-form-time-picker
:disabled="!model.data.start && !model.data.end"
:validations="validations.hora.end"
@input="handleHoraEndInput"
label="Até"
v-model="model.hora.end"
/>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col cols="6">
<rkm-form-select
:options="form.usuarios"
@change="handleUsuarioChange"
default-option-text="Todos"
label="Usuário"
show-default-option
text-field="nome"
v-model="model.usuarioId"
value-field="id"
/>
</rkm-col>
<rkm-col cols="3">
<rkm-form-select
:options="form.status"
@change="handleStatusChange"
default-option-text="Todos"
label="Status"
show-default-option
text-field="nome"
v-model="model.statusId"
value-field="id"
/>
</rkm-col>
<rkm-col cols="2">
<rkm-button
:label="false"
@click="$refs.Form.validate(search)"
color="primary"
>
Buscar
</rkm-button>
<rkm-button
:label="false"
@click="reset"
>
Limpar
</rkm-button>
</rkm-col>
</rkm-row>
</rkm-form>
</rkm-area>
<template v-if="data">
<rkm-area
no-margin-top
title="Resultado"
>
<template v-slot:trailing>
<rkm-button
@click="print"
color="info"
small
title="Imprimir"
>
<rkm-icon icon="print" />
</rkm-button>
<rkm-button
@click="exportTo"
color="info"
small
title="Exportar"
>
<rkm-icon icon="file-export" />
</rkm-button>
</template>
<rkm-table
:fields="fields"
:items="data.itens"
:sort-by="sortBy"
:sort-desc="sortDesc"
@sort-changed="onSortChanged"
hide-filters
/>
</rkm-area>
<rkm-area
no-margin-top
title="Totais"
type="fieldset"
>
<rkm-row>
<rkm-col>
<rkm-form-input
:value="data.totalizadores.totalAcessos"
disabled
label="Total de acessos"
/>
</rkm-col>
<rkm-col>
<rkm-form-input
:value="data.totalizadores.totalAcessoRealizadoComSucesso"
disabled
label="Total de acessos realizados com sucesso"
/>
</rkm-col>
<rkm-col>
<rkm-form-input
:value="data.totalizadores.totalUsuarioEOuSenhaIncorretos"
disabled
label="Total de usuário e/ou senha incorretos"
/>
</rkm-col>
<rkm-col>
<rkm-form-input
:value="data.totalizadores.totalUsuarioBloqueado"
disabled
label="Total de usuários bloqueados"
/>
</rkm-col>
<rkm-col>
<rkm-form-input
:value="data.totalizadores.totalUsuarioAcesso"
disabled
label="Total de usuários que acessaram o sistema"
/>
</rkm-col>
</rkm-row>
</rkm-area>
</template>
</div>
</template>
<script>
// Mixins.
import {error, file, formats, repository, stringCase} from '@/mixins'
// Others.
import moment from 'moment'
// Vuex.
import {mapActions} from 'vuex'
export default {
computed: {
validations() {
return {
data: {required: true},
hora: {
start: {
max_hour: this.model.hora.end ? this.formatTime(this.model.hora.end) : null,
},
end: {
min_hour: this.model.hora.start ? this.formatTime(this.model.hora.start) : null,
},
},
}
},
},
created() {
this.repositories = {
exportacao: this.getConectaSusRepository('exportacao.administracao'),
impressao: this.getConectaSusRepository('impressao.administracao'),
relatorioAcessoSistema: this.getConectaSusRepository('administracao.relatorioAcessoSistema'),
}
this.load()
},
data() {
return {
additionals: null,
form: null,
data: null,
fields: [
{formatter: value => this.formatDate(value), key: 'data', label: 'Data', sortable: true},
{key: 'hora', label: 'Hora', sortable: true},
{key: 'usuarioNome', label: 'Usuário', sortable: true},
{key: 'statusNome', label: 'Status', sortable: true},
],
model: null,
others: {moment},
sortBy: 'data',
sortDesc: false,
}
},
methods: {
...mapActions(['setLoading', 'unsetLoading']),
load() {
this.setLoading()
this.repositories.relatorioAcessoSistema.create()
.then((response) => {
this.additionals = response.data.additionals
this.form = response.data.form
this.model = response.data.model
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
search() {
const request = {
data: {
start: this.others.moment(this.model.data.start).format('DD/MM/YYYY'),
end: this.others.moment(this.model.data.end).format('DD/MM/YYYY'),
},
hora: {
start: this.model.hora.start,
end: this.model.hora.end,
},
usuarioId: this.model.usuarioId,
statusId: this.model.statusId,
}
this.setLoading()
this.repositories.relatorioAcessoSistema.index(this.camelObjectKeysToSnakeRecursive(request))
.then(response => this.data = Object.assign(response.data))
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
reset() {
this.form.usuarios = []
this.form.status = []
this.model.data = {
start: null,
end: null,
}
this.model.hora = {
start: null,
end: null,
}
this.model.usuarioId = null
this.model.statusId = null
this.data = null
this.$nextTick().then(() => this.$refs.Form.resetValidator())
},
print() {
this.setLoading()
const request = {
data: {
start: this.others.moment(this.model.data.start).format('DD/MM/YYYY'),
end: this.others.moment(this.model.data.end).format('DD/MM/YYYY'),
},
hora: {
start: this.model.hora.start,
end: this.model.hora.end,
},
usuarioId: this.model.usuarioId,
statusId: this.model.statusId,
sortBy: this.sortBy ? this.camelToSnake(this.sortBy) : null,
sortDesc: this.sortDesc,
}
this.repositories.impressao.relatorioAcessoSistema(this.camelObjectKeysToSnakeRecursive(request))
.then(({ data, headers }) => this.openFile(data, headers['content-type']))
.finally(() => this.unsetLoading())
},
exportTo() {
this.setLoading()
const request = {
data: {
start: this.others.moment(this.model.data.start).format('DD/MM/YYYY'),
end: this.others.moment(this.model.data.end).format('DD/MM/YYYY'),
},
hora: {
start: this.model.hora.start,
end: this.model.hora.end,
},
usuarioId: this.model.usuarioId,
statusId: this.model.statusId,
sortBy: this.sortBy ? this.camelToSnake(this.sortBy) : null,
sortDesc: this.sortDesc,
}
this.repositories.exportacao.relatorioAcessoSistema(this.camelObjectKeysToSnakeRecursive(request))
.then(({ data, headers }) => this.openFile(data, headers['content-type']))
.finally(() => this.unsetLoading())
},
handleHoraStartInput(value) {
this.form.usuarios = []
this.form.status = []
this.model.usuarioId = null
this.model.statusId = null
this.data = null
const request = {
listar: 'usuarios',
data: {
start: this.others.moment(this.model.data.start).format('DD/MM/YYYY'),
end: this.others.moment(this.model.data.end).format('DD/MM/YYYY'),
},
hora: {
start: value,
end: this.model.hora.end,
},
}
this.setLoading()
this.repositories.relatorioAcessoSistema.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.usuarios = Object.assign(response.data.usuarios)
this.form.status = Object.assign(response.data.status)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleHoraEndInput(value) {
this.form.usuarios = []
this.form.status = []
this.model.usuarioId = null
this.model.statusId = null
this.data = null
const request = {
listar: 'usuarios',
data: {
start: this.others.moment(this.model.data.start).format('DD/MM/YYYY'),
end: this.others.moment(this.model.data.end).format('DD/MM/YYYY'),
},
hora: {
start: this.model.hora.start,
end: value,
},
}
this.setLoading()
this.repositories.relatorioAcessoSistema.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.usuarios = Object.assign(response.data.usuarios)
this.form.status = Object.assign(response.data.status)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleUsuarioChange(value) {
this.form.status = []
this.model.statusId = null
this.data = null
const request = {
listar: 'status',
data: {
start: this.others.moment(this.model.data.start).format('DD/MM/YYYY'),
end: this.others.moment(this.model.data.end).format('DD/MM/YYYY'),
},
hora: {
start: this.model.hora.start,
end: this.model.hora.end,
usuarioId: value,
},
}
this.setLoading()
this.repositories.relatorioAcessoSistema.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => this.form.status = Object.assign(response.data.status))
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleStatusChange() {
this.data = null
},
onSortChanged(context) {
this.sortBy = context.sortBy
this.sortDesc = context.sortDesc
},
},
mixins: [error, file, formats, repository, stringCase],
watch: {
'model.data': {
deep: true,
handler(value) {
this.form.usuarios = []
this.form.status = []
this.model.hora = {
start: null,
end: null,
}
this.model.usuarioId = null
this.model.statusId = null
this.data = null
if (value.start === null || value.end === null) {
return
}
const request = {
listar: 'usuarios',
data: {
start: this.others.moment(this.model.data.start).format('DD/MM/YYYY'),
end: this.others.moment(this.model.data.end).format('DD/MM/YYYY'),
},
}
this.setLoading()
this.repositories.relatorioAcessoSistema.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.usuarios = Object.assign(response.data.usuarios)
this.form.status = Object.assign(response.data.status)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
},
},
}
</script>

View File

@@ -0,0 +1,631 @@
<template>
<div>
<rkm-area
no-margin-top
title="Filtros"
type="fieldset"
>
<rkm-form
v-if="model"
ref="RkmForm"
>
<rkm-row>
<rkm-col cols="6">
<rkm-form-date-picker
:end-validations="validations.periodo"
:max-date="additionals.dataAtual"
:range-inline="false"
:start-validations="validations.periodo"
end-label="Até"
range
start-label="Período de"
v-model="model.periodo"
/>
</rkm-col>
<rkm-col cols="6">
<rkm-form-select
:options="form.unidades"
@change="handleUnidadeChange"
default-option-text="Todas"
label="Unidade"
show-default-option
text-field="nomeFantasia"
v-model="model.unidadeId"
value-field='id'
/>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col cols="6">
<rkm-form-select
:options="form.infraestruturas"
@change="handleInfraestruturaChange"
default-option-text="Todos"
label="Local de atendimento"
show-default-option
text-field="nome"
v-model="model.infraestruturaId"
value-field='id'
/>
</rkm-col>
<rkm-col cols="6">
<rkm-form-select
:options="form.lotacoes"
@change="handleLotacaoChange"
default-option-text="Todos"
label="CBO"
show-default-option
text-field="nome"
v-model="model.ocupacaoId"
value-field='id'
/>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col cols="3">
<rkm-form-select
:options="form.papeis"
@change="handlePapelChange"
default-option-text="Todos"
label="Papel"
show-default-option
text-field="nome"
v-model="model.papelId"
value-field='id'
/>
</rkm-col>
<rkm-col cols="3">
<rkm-form-select
:options="form.acoes"
@change="handleAcaoChange"
default-option-text="Todas"
label="Tipo de ação"
show-default-option
text-field="nome"
v-model="model.acaoId"
value-field='id'
/>
</rkm-col>
<rkm-col cols="6">
<rkm-form-input-autocomplete
:disabled="!model.periodo.start || !model.periodo.end"
:repository="repositories.relatorioHistoricoPapelLotacao.usuario"
:request-query-string-params="requestQueryStringParamsUsuario"
:search-fields="[{ key: 'cadastro_cidadao.nome', searchable: true }]"
label="Usuário"
v-model="model.usuario"
/>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col cols="6">
<rkm-form-input-autocomplete
:disabled="!model.periodo.start || !model.periodo.end"
:repository="repositories.relatorioHistoricoPapelLotacao.usuarioResponsavel"
:request-query-string-params="requestQueryStringParamsUsuarioResponsavel"
:search-fields="[{ key: 'cadastro_cidadao.nome', searchable: true }]"
label="Permitido por"
v-model="model.usuarioResponsavel"
/>
</rkm-col>
<rkm-col cols="6">
<rkm-button
:label="false"
@click="handleBuscarClick"
color="primary"
>
Buscar
</rkm-button>
<rkm-button
:label="false"
@click="handleLimparClick"
>
Limpar
</rkm-button>
</rkm-col>
</rkm-row>
</rkm-form>
</rkm-area>
<rkm-area
no-margin-top
title="Resultado"
>
<template v-slot:trailing>
<rkm-dropdown
class="mr-2"
color="primary"
cols="1"
menu-class="auto-scroll"
no-caret
small
title="Colunas"
v-rkm-tooltip.hover
>
<template v-slot:button-content>
<rkm-icon icon="columns" />
</template>
<rkm-form-checkbox
:indeterminate="indeterminate"
@change="handleTodosClick"
class="mx-2"
v-model="selectedAll"
>
Todas
</rkm-form-checkbox>
<rkm-form-checkbox-group
:options="optionalColumns"
class="mx-2"
stacked
text-field="label"
v-model="selectedColumns"
value-field="key"
/>
</rkm-dropdown>
<rkm-button
:disabled="itens.length === 0"
@click="handleImprimirClick"
color="info"
small
title="Imprimir"
>
<rkm-icon icon="print" />
</rkm-button>
</template>
<rkm-table
:fields="fields"
:items="itens"
/>
</rkm-area>
</div>
</template>
<script>
// Mixins.
import {error, formats, object, repository, stringCase} from '@/mixins'
// Others.
import moment from 'moment'
// Vuex.
import {mapActions} from 'vuex'
export default {
computed: {
fields() {
return this.columns.filter(column => (column.fixed || false) || this.selectedColumns.includes(column.key))
},
optionalColumns() {
return this.columns.filter(column => !(column.fixed || false))
},
validations() {
return {
periodo: { required: true },
}
},
requestQueryStringParamsUsuario() {
const request = this.clone(this.model)
request.periodo.start = this.others.moment(this.model.periodo.start).format('DD/MM/YYYY')
request.periodo.end = this.others.moment(this.model.periodo.end).format('DD/MM/YYYY')
return this.camelObjectKeysToSnakeRecursive(request)
},
requestQueryStringParamsUsuarioResponsavel() {
const request = this.clone(this.model)
request.periodo.start = this.others.moment(this.model.periodo.start).format('DD/MM/YYYY')
request.periodo.end = this.others.moment(this.model.periodo.end).format('DD/MM/YYYY')
return this.camelObjectKeysToSnakeRecursive(request)
},
},
created() {
this.repositories = {
impressao: this.getConectaSusRepository('impressao.administracao'),
relatorioHistoricoPapelLotacao: this.getConectaSusRepository('administracao.relatorioHistoricoPapelLotacao'),
}
},
data() {
return {
additionals: null,
columns: [
{
class: 'col-min text-center',
filterByFormatted: true,
fixed: true,
formatter: value => this.formatDateTime(value),
key: 'historicoDataHora',
label: 'Data/Hora',
sortable: true,
},
{key: 'unidadeNomeFantasia', label: 'Unidade', fixed: true, sortable: true },
{key: 'infraestruturaNome', label: 'Local de atendimento', fixed: true, sortable: true },
{key: 'ocupacaoNome', label: 'CBO', fixed: true, sortable: true },
{key: 'papelNome', label: 'Papel', fixed: true, sortable: true },
{key: 'acaoNome', label: 'Tipo de ação', fixed: true, sortable: true },
{key: 'usuarioNome', label: 'Usuário', fixed: true, sortable: true },
{key: 'usuarioResponsavelNome', label: 'Permitido por', sortable: true },
],
form: null,
indeterminate: true,
itens: [],
model: null,
others: {moment},
selectedAll: false,
selectedColumns: [],
}
},
methods: {
...mapActions(['setLoading', 'unsetLoading']),
load() {
this.setLoading()
this.repositories.relatorioHistoricoPapelLotacao.create()
.then(response => {
this.additionals = Object.assign(response.data.additionals)
this.form = Object.assign(response.data.form)
this.model = Object.assign(response.data.model)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleUnidadeChange() {
this.form.infraestruturas = []
this.form.lotacoes = []
this.form.papeis = []
this.form.acoes = []
this.model.infraestruturaId = null
this.model.lotacaoId = null
this.model.ocupacaoId = null
this.model.papelId = null
this.model.acaoId = null
this.model.usuario = {
id: null,
nome: null,
}
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
this.getInfraestruturas()
},
handleInfraestruturaChange() {
this.form.lotacoes = []
this.form.papeis = []
this.form.acoes = []
this.model.lotacaoId = null
this.model.ocupacaoId = null
this.model.papelId = null
this.model.acaoId = null
this.model.usuario = {
id: null,
nome: null,
}
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
this.getLotacoes()
},
handleLotacaoChange() {
this.form.papeis = []
this.form.acoes = []
this.model.papelId = null
this.model.acaoId = null
this.model.usuario = {
id: null,
nome: null,
}
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
this.getPapeis()
},
handlePapelChange() {
this.form.acoes = []
this.model.acaoId = null
this.model.usuario = {
id: null,
nome: null,
}
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
this.getAcoes()
},
handleAcaoChange() {
this.model.usuario = {
id: null,
nome: null,
}
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
},
handleBuscarClick() {
this.$refs.RkmForm.validate()
.then(valid => {
if (valid === false) return
this.itens = []
const request = this.clone(this.model)
request.periodo.start = this.others.moment(this.model.periodo.start).format('DD/MM/YYYY')
request.periodo.end = this.others.moment(this.model.periodo.end).format('DD/MM/YYYY')
this.setLoading()
this.repositories.relatorioHistoricoPapelLotacao.index(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.itens = response.data
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
})
},
handleLimparClick() {
this.form.unidades = []
this.form.infraestruturas = []
this.form.lotacoes = []
this.form.papeis = []
this.form.acoes = []
this.model.periodo = {
start: null,
end: null,
}
this.model.unidadeId = null
this.model.infraestruturaId = null
this.model.lotacaoId = null
this.model.ocupacaoId = null
this.model.papelId = null
this.model.acaoId = null
this.model.usuario = {
id: null,
nome: null,
}
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
this.$nextTick().then(() => this.$refs.RkmForm.resetValidator())
},
handleTodosClick(selected) {
this.selectedColumns = selected ? this.optionalColumns.map(c => c.key) : []
},
handleImprimirClick() {
this.setLoading()
const request = {
...this.request(),
selectedColumns: this.fields.map(field => ({
class: field.class,
key: this.camelToSnake(field.key),
label: field.label,
})),
}
this.repositories.impressao.relatorioHistoricoPapelLotacao(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
const content = new Blob([response.data], {type: response.headers['content-type']})
const url = URL.createObjectURL(content)
const link = document.createElement('a')
link.href = url
link.target = '_blank'
link.click()
link.remove()
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
request() {
const model = this.clone(this.model)
model.periodo.start = this.others.moment(model.periodo.start).format('DD/MM/YYYY')
model.periodo.end = this.others.moment(model.periodo.end).format('DD/MM/YYYY')
return model
},
getUnidades() {
this.setLoading()
const request = {
listar: 'unidades',
...this.request(),
}
this.repositories.relatorioHistoricoPapelLotacao.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.unidades = Object.assign(response.data.unidades)
this.form.infraestruturas = Object.assign(response.data.infraestruturas)
this.form.lotacoes = Object.assign(response.data.lotacoes)
this.form.papeis = Object.assign(response.data.papeis)
this.form.acoes = Object.assign(response.data.acoes)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
getInfraestruturas() {
this.setLoading()
const request = {
listar: 'infraestruturas',
...this.request(),
}
this.repositories.relatorioHistoricoPapelLotacao.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.infraestruturas = Object.assign(response.data.infraestruturas)
this.form.lotacoes = Object.assign(response.data.lotacoes)
this.form.papeis = Object.assign(response.data.papeis)
this.form.acoes = Object.assign(response.data.acoes)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
getLotacoes() {
this.setLoading()
const request = {
listar: 'lotacoes',
...this.request(),
}
this.repositories.relatorioHistoricoPapelLotacao.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.lotacoes = Object.assign(response.data.lotacoes)
this.form.papeis = Object.assign(response.data.papeis)
this.form.acoes = Object.assign(response.data.acoes)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
getPapeis() {
this.setLoading()
const request = {
listar: 'papeis',
...this.request(),
}
this.repositories.relatorioHistoricoPapelLotacao.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.papeis = Object.assign(response.data.papeis)
this.form.acoes = Object.assign(response.data.acoes)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
getAcoes() {
this.setLoading()
const request = {
listar: 'acoes',
...this.request(),
}
this.repositories.relatorioHistoricoPapelLotacao.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.acoes = Object.assign(response.data.acoes)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
},
mounted() {
this.load()
},
mixins: [error, formats, object, repository, stringCase],
watch: {
'model.periodo': {
deep: true,
handler(value) {
this.form.unidades = []
this.form.infraestruturas = []
this.form.lotacoes = []
this.form.papeis = []
this.form.acoes = []
this.model.unidadeId = null
this.model.infraestruturaId = null
this.model.lotacaoId = null
this.model.ocupacaoId = null
this.model.papelId = null
this.model.acaoId = null
this.model.usuario = {
id: null,
nome: null,
}
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
if (value.start && value.end) {
this.getUnidades()
}
},
},
'model.usuario': {
deep: true,
handler() {
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
},
},
'model.usuarioResponsavel': {
deep: true,
handler() {
this.itens = []
},
},
selectedColumns(value) {
this.indeterminate = value.length > 0 && value.length != this.optionalColumns.length
this.selectedAll = value.length > 0 && value.length == this.optionalColumns.length
},
},
}
</script>
<style scoped>
.dropdown >>> .auto-scroll {
max-height: 400px;
overflow-y: auto;
width: 300px;
}
</style>

View File

@@ -0,0 +1,764 @@
<template>
<div>
<rkm-area
no-margin-top
title="Filtros"
type="fieldset"
>
<rkm-form
v-if="model"
ref="RkmForm"
>
<rkm-row>
<rkm-col cols="6">
<rkm-form-date-picker
:end-validations="validations.periodo"
:max-date="additionals.dataAtual"
:range-inline="false"
:start-validations="validations.periodo"
end-label="Até"
range
start-label="Período de"
v-model="model.periodo"
/>
</rkm-col>
<rkm-col cols="6">
<rkm-form-select
:options="form.unidades"
@change="handleUnidadeChange"
default-option-text="Todas"
label="Unidade"
show-default-option
text-field="nomeFantasia"
v-model="model.unidadeId"
value-field='id'
/>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col cols="6">
<rkm-form-select
:options="form.infraestruturas"
@change="handleInfraestruturaChange"
default-option-text="Todos"
label="Local de atendimento"
show-default-option
text-field="nome"
v-model="model.infraestruturaId"
value-field='id'
/>
</rkm-col>
<rkm-col cols="6">
<rkm-form-select
:options="form.lotacoes"
@change="handleLotacaoChange"
default-option-text="Todos"
label="CBO"
show-default-option
text-field="nome"
v-model="model.lotacaoId"
value-field='id'
/>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col cols="6">
<rkm-form-select
:options="form.modulos"
@change="handleModuloChange"
default-option-text="Todos"
label="Módulo"
show-default-option
text-field="nome"
v-model="model.moduloId"
value-field='id'
/>
</rkm-col>
<rkm-col cols="6">
<rkm-form-select
:options="form.funcionalidades"
@change="handleFuncionalidadeChange"
default-option-text="Todas"
label="Funcionalidade"
show-default-option
text-field="nome"
v-model="model.funcionalidadeId"
value-field='id'
/>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col cols="3">
<rkm-form-select
:options="form.crudAcoes"
@change="handleCrudAcaoChange"
default-option-text="Todas"
label="Permissão para"
show-default-option
text-field="nome"
v-model="model.crudAcaoId"
value-field='id'
/>
</rkm-col>
<rkm-col cols="3">
<rkm-form-select
:options="form.acoes"
@change="handleAcaoChange"
default-option-text="Todas"
label="Tipo de ação"
show-default-option
text-field="nome"
v-model="model.acaoId"
value-field='id'
/>
</rkm-col>
<rkm-col cols="6">
<rkm-form-input-autocomplete
:disabled="!model.periodo.start || !model.periodo.end"
:repository="repositories.relatorioHistoricoPermissaoLotacao.usuario"
:request-query-string-params="requestQueryStringParamsUsuario"
:search-fields="[{ key: 'cadastro_cidadao.nome', searchable: true }]"
label="Usuário"
v-model="model.usuario"
/>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col cols="6">
<rkm-form-input-autocomplete
:disabled="!model.periodo.start || !model.periodo.end"
:repository="repositories.relatorioHistoricoPermissaoLotacao.usuarioResponsavel"
:request-query-string-params="requestQueryStringParamsUsuarioResponsavel"
:search-fields="[{ key: 'cadastro_cidadao.nome', searchable: true }]"
label="Permitido por"
v-model="model.usuarioResponsavel"
/>
</rkm-col>
<rkm-col cols="6">
<rkm-button
:label="false"
@click="handleBuscarClick"
color="primary"
>
Buscar
</rkm-button>
<rkm-button
:label="false"
@click="handleLimparClick"
>
Limpar
</rkm-button>
</rkm-col>
</rkm-row>
</rkm-form>
</rkm-area>
<rkm-area
no-margin-top
title="Resultado"
>
<template v-slot:trailing>
<rkm-dropdown
class="mr-2"
color="primary"
cols="1"
menu-class="auto-scroll"
no-caret
small
title="Colunas"
v-rkm-tooltip.hover
>
<template v-slot:button-content>
<rkm-icon icon="columns" />
</template>
<rkm-form-checkbox
:indeterminate="indeterminate"
@change="handleTodosClick"
class="mx-2"
v-model="selectedAll"
>
Todas
</rkm-form-checkbox>
<rkm-form-checkbox-group
:options="optionalColumns"
class="mx-2"
stacked
text-field="label"
v-model="selectedColumns"
value-field="key"
/>
</rkm-dropdown>
<rkm-button
:disabled="itens.length === 0"
@click="handleImprimirClick"
color="info"
small
title="Imprimir"
>
<rkm-icon icon="print" />
</rkm-button>
</template>
<rkm-table
:fields="fields"
:items="itens"
/>
</rkm-area>
</div>
</template>
<script>
// Mixins.
import {error, formats, object, repository, stringCase} from '@/mixins'
// Others.
import moment from 'moment'
// Vuex.
import {mapActions} from 'vuex'
export default {
computed: {
fields() {
return this.columns.filter(column => (column.fixed || false) || this.selectedColumns.includes(column.key))
},
optionalColumns() {
return this.columns.filter(column => !(column.fixed || false))
},
validations() {
return {
periodo: { required: true },
}
},
requestQueryStringParamsUsuario() {
const request = this.clone(this.model)
request.periodo.start = this.others.moment(this.model.periodo.start).format('DD/MM/YYYY')
request.periodo.end = this.others.moment(this.model.periodo.end).format('DD/MM/YYYY')
return this.camelObjectKeysToSnakeRecursive(request)
},
requestQueryStringParamsUsuarioResponsavel() {
const request = this.clone(this.model)
request.periodo.start = this.others.moment(this.model.periodo.start).format('DD/MM/YYYY')
request.periodo.end = this.others.moment(this.model.periodo.end).format('DD/MM/YYYY')
return this.camelObjectKeysToSnakeRecursive(request)
},
},
created() {
this.repositories = {
impressao: this.getConectaSusRepository('impressao.administracao'),
relatorioHistoricoPermissaoLotacao: this.getConectaSusRepository('administracao.relatorioHistoricoPermissaoLotacao'),
}
},
data() {
return {
additionals: null,
columns: [
{
class: 'col-min text-center',
filterByFormatted: true,
fixed: true,
formatter: value => this.formatDateTime(value),
key: 'historicoDataHora',
label: 'Data/Hora',
sortable: true,
},
{key: 'unidadeNomeFantasia', label: 'Unidade', sortable: true },
{key: 'infraestruturaNome', label: 'Local de atendimento', sortable: true },
{key: 'ocupacaoNome', label: 'CBO', sortable: true },
{key: 'moduloNome', label: 'Módulo', fixed: true, sortable: true },
{key: 'funcionalidadeNome', label: 'Funcionalidade', fixed: true, sortable: true },
{key: 'crudAcaoNome', label: 'Permissão para', fixed: true, sortable: true },
{key: 'acaoNome', label: 'Tipo de ação', fixed: true, sortable: true },
{key: 'usuarioNome', label: 'Usuário', fixed: true, sortable: true },
{key: 'usuarioResponsavelNome', label: 'Permitido por', sortable: true },
],
form: null,
indeterminate: true,
itens: [],
model: null,
others: {moment},
selectedAll: false,
selectedColumns: [
'unidadeNomeFantasia',
'infraestruturaNome',
'ocupacaoNome'
],
}
},
methods: {
...mapActions(['setLoading', 'unsetLoading']),
load() {
this.setLoading()
this.repositories.relatorioHistoricoPermissaoLotacao.create()
.then(response => {
this.additionals = Object.assign(response.data.additionals)
this.form = Object.assign(response.data.form)
this.model = Object.assign(response.data.model)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleUnidadeChange() {
this.form.infraestruturas = []
this.form.lotacoes = []
this.form.modulos = []
this.form.funcionalidades = []
this.form.crudAcoes = []
this.form.acoes = []
this.model.infraestruturaId = null
this.model.lotacaoId = null
this.model.moduloId = null
this.model.funcionalidadeId = null
this.model.crudAcaoId = null
this.model.acaoId = null
this.model.usuario = {
id: null,
nome: null,
}
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
this.getInfraestruturas()
},
handleInfraestruturaChange() {
this.form.lotacoes = []
this.form.modulos = []
this.form.funcionalidades = []
this.form.crudAcoes = []
this.form.acoes = []
this.model.lotacaoId = null
this.model.moduloId = null
this.model.funcionalidadeId = null
this.model.crudAcaoId = null
this.model.acaoId = null
this.model.usuario = {
id: null,
nome: null,
}
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
this.getLotacoes()
},
handleLotacaoChange() {
this.form.modulos = []
this.form.funcionalidades = []
this.form.crudAcoes = []
this.form.acoes = []
this.model.moduloId = null
this.model.funcionalidadeId = null
this.model.crudAcaoId = null
this.model.acaoId = null
this.model.usuario = {
id: null,
nome: null,
}
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
this.getModulos()
},
handleModuloChange() {
this.form.funcionalidades = []
this.form.crudAcoes = []
this.form.acoes = []
this.model.funcionalidadeId = null
this.model.crudAcaoId = null
this.model.acaoId = null
this.model.usuario = {
id: null,
nome: null,
}
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
this.getFuncionalidades()
},
handleFuncionalidadeChange() {
this.form.crudAcoes = []
this.form.acoes = []
this.model.crudAcaoId = null
this.model.acaoId = null
this.model.usuario = {
id: null,
nome: null,
}
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
this.getCrudAcoes()
},
handleCrudAcaoChange() {
this.form.acoes = []
this.model.acaoId = null
this.model.usuario = {
id: null,
nome: null,
}
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
this.getAcoes()
},
handleAcaoChange() {
this.model.usuario = {
id: null,
nome: null,
}
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
},
handleBuscarClick() {
this.$refs.RkmForm.validate()
.then(valid => {
if (valid === false) return
this.itens = []
const request = this.clone(this.model)
request.periodo.start = this.others.moment(this.model.periodo.start).format('DD/MM/YYYY')
request.periodo.end = this.others.moment(this.model.periodo.end).format('DD/MM/YYYY')
this.setLoading()
this.repositories.relatorioHistoricoPermissaoLotacao.index(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.itens = response.data
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
})
},
handleLimparClick() {
this.form.unidades = []
this.form.infraestruturas = []
this.form.lotacoes = []
this.form.modulos = []
this.form.funcionalidades = []
this.form.crudAcoes = []
this.form.acoes = []
this.model.periodo = {
start: null,
end: null,
}
this.model.unidadeId = null
this.model.infraestruturaId = null
this.model.lotacaoId = null
this.model.moduloId = null
this.model.funcionalidadeId = null
this.model.crudAcaoId = null
this.model.acaoId = null
this.model.usuario = {
id: null,
nome: null,
}
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
this.$nextTick().then(() => this.$refs.RkmForm.resetValidator())
},
handleTodosClick(selected) {
this.selectedColumns = selected ? this.optionalColumns.map(c => c.key) : []
},
handleImprimirClick() {
this.setLoading()
const request = {
...this.request(),
selectedColumns: this.fields.map(field => ({
class: field.class,
key: this.camelToSnake(field.key),
label: field.label,
})),
}
this.repositories.impressao.relatorioHistoricoPermissaoLotacao(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
const content = new Blob([response.data], {type: response.headers['content-type']})
const url = URL.createObjectURL(content)
const link = document.createElement('a')
link.href = url
link.target = '_blank'
link.click()
link.remove()
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
request() {
const model = this.clone(this.model)
model.periodo.start = this.others.moment(model.periodo.start).format('DD/MM/YYYY')
model.periodo.end = this.others.moment(model.periodo.end).format('DD/MM/YYYY')
return model
},
getUnidades() {
this.setLoading()
const request = {
listar: 'unidades',
...this.request(),
}
this.repositories.relatorioHistoricoPermissaoLotacao.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.unidades = Object.assign(response.data.unidades)
this.form.infraestruturas = Object.assign(response.data.infraestruturas)
this.form.lotacoes = Object.assign(response.data.lotacoes)
this.form.modulos = Object.assign(response.data.modulos)
this.form.funcionalidades = Object.assign(response.data.funcionalidades)
this.form.crudAcoes = Object.assign(response.data.crudAcoes)
this.form.acoes = Object.assign(response.data.acoes)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
getInfraestruturas() {
this.setLoading()
const request = {
listar: 'infraestruturas',
...this.request(),
}
this.repositories.relatorioHistoricoPermissaoLotacao.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.infraestruturas = Object.assign(response.data.infraestruturas)
this.form.lotacoes = Object.assign(response.data.lotacoes)
this.form.modulos = Object.assign(response.data.modulos)
this.form.funcionalidades = Object.assign(response.data.funcionalidades)
this.form.crudAcoes = Object.assign(response.data.crudAcoes)
this.form.acoes = Object.assign(response.data.acoes)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
getLotacoes() {
this.setLoading()
const request = {
listar: 'lotacoes',
...this.request(),
}
this.repositories.relatorioHistoricoPermissaoLotacao.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.lotacoes = Object.assign(response.data.lotacoes)
this.form.modulos = Object.assign(response.data.modulos)
this.form.funcionalidades = Object.assign(response.data.funcionalidades)
this.form.crudAcoes = Object.assign(response.data.crudAcoes)
this.form.acoes = Object.assign(response.data.acoes)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
getModulos() {
this.setLoading()
const request = {
listar: 'modulos',
...this.request(),
}
this.repositories.relatorioHistoricoPermissaoLotacao.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.modulos = Object.assign(response.data.modulos)
this.form.funcionalidades = Object.assign(response.data.funcionalidades)
this.form.crudAcoes = Object.assign(response.data.crudAcoes)
this.form.acoes = Object.assign(response.data.acoes)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
getFuncionalidades() {
this.setLoading()
const request = {
listar: 'funcionalidades',
...this.request(),
}
this.repositories.relatorioHistoricoPermissaoLotacao.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.funcionalidades = Object.assign(response.data.funcionalidades)
this.form.crudAcoes = Object.assign(response.data.crudAcoes)
this.form.acoes = Object.assign(response.data.acoes)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
getCrudAcoes() {
this.setLoading()
const request = {
listar: 'crud-acoes',
...this.request(),
}
this.repositories.relatorioHistoricoPermissaoLotacao.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.crudAcoes = Object.assign(response.data.crudAcoes)
this.form.acoes = Object.assign(response.data.acoes)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
getAcoes() {
this.setLoading()
const request = {
listar: 'acoes',
...this.request(),
}
this.repositories.relatorioHistoricoPermissaoLotacao.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.acoes = Object.assign(response.data.acoes)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
},
mounted() {
this.load()
},
mixins: [error, formats, object, repository, stringCase],
watch: {
'model.periodo': {
deep: true,
handler(value) {
this.form.unidades = []
this.form.infraestruturas = []
this.form.lotacoes = []
this.form.modulos = []
this.form.funcionalidades = []
this.form.crudAcoes = []
this.form.acoes = []
this.model.unidadeId = null
this.model.infraestruturaId = null
this.model.lotacaoId = null
this.model.moduloId = null
this.model.funcionalidadeId = null
this.model.crudAcaoId = null
this.model.acaoId = null
this.model.usuario = {
id: null,
nome: null,
}
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
if (value.start && value.end) {
this.getUnidades()
}
},
},
'model.usuario': {
deep: true,
handler() {
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
},
},
'model.usuarioResponsavel': {
deep: true,
handler() {
this.itens = []
},
},
selectedColumns(value) {
this.indeterminate = value.length > 0 && value.length != this.optionalColumns.length
this.selectedAll = value.length > 0 && value.length == this.optionalColumns.length
},
},
}
</script>
<style scoped>
.dropdown >>> .auto-scroll {
max-height: 400px;
overflow-y: auto;
width: 300px;
}
</style>

View File

@@ -0,0 +1,559 @@
<template>
<div>
<rkm-area
no-margin-top
title="Filtros"
type="fieldset"
>
<rkm-form
v-if="model"
ref="RkmForm"
>
<rkm-row>
<rkm-col cols="6">
<rkm-form-date-picker
:end-validations="validations.periodo"
:max-date="additionals.dataAtual"
:range-inline="false"
:start-validations="validations.periodo"
end-label="Até"
range
start-label="Período de"
v-model="model.periodo"
/>
</rkm-col>
<rkm-col cols="6">
<rkm-form-select
:options="form.papeis"
@change="handlePapelChange"
default-option-text="Todos"
label="Papel"
show-default-option
text-field="nome"
v-model="model.papelId"
value-field='id'
/>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col cols="6">
<rkm-form-select
:options="form.modulos"
@change="handleModuloChange"
default-option-text="Todos"
label="Módulo"
show-default-option
text-field="nome"
v-model="model.moduloId"
value-field='id'
/>
</rkm-col>
<rkm-col cols="6">
<rkm-form-select
:options="form.funcionalidades"
@change="handleFuncionalidadeChange"
default-option-text="Todas"
label="Funcionalidade"
show-default-option
text-field="nome"
v-model="model.funcionalidadeId"
value-field='id'
/>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col cols="3">
<rkm-form-select
:options="form.crudAcoes"
@change="handleCrudAcaoChange"
default-option-text="Todas"
label="Permissão para"
show-default-option
text-field="nome"
v-model="model.crudAcaoId"
value-field='id'
/>
</rkm-col>
<rkm-col cols="3">
<rkm-form-select
:options="form.acoes"
@change="handleAcaoChange"
default-option-text="Todas"
label="Tipo de ação"
show-default-option
text-field="nome"
v-model="model.acaoId"
value-field='id'
/>
</rkm-col>
<rkm-col>
<rkm-form-input-autocomplete
:disabled="!model.periodo.start || !model.periodo.end"
:repository="repositories.relatorioHistoricoPermissaoPapel.usuarioResponsavel"
:request-query-string-params="requestQueryStringParamsUsuarioResponsavel"
:search-fields="[{ key: 'cadastro_cidadao.nome', searchable: true }]"
label="Permitido por"
v-model="model.usuarioResponsavel"
/>
</rkm-col>
<rkm-col cols="auto">
<rkm-button
:label="false"
@click="handleBuscarClick"
color="primary"
>
Buscar
</rkm-button>
<rkm-button
:label="false"
@click="handleLimparClick"
>
Limpar
</rkm-button>
</rkm-col>
</rkm-row>
</rkm-form>
</rkm-area>
<rkm-area
no-margin-top
title="Resultado"
>
<template v-slot:trailing>
<rkm-dropdown
class="mr-2"
color="primary"
cols="1"
menu-class="auto-scroll"
no-caret
small
title="Colunas"
v-rkm-tooltip.hover
>
<template v-slot:button-content>
<rkm-icon icon="columns" />
</template>
<rkm-form-checkbox
:indeterminate="indeterminate"
@change="handleTodosClick"
class="mx-2"
v-model="selectedAll"
>
Todas
</rkm-form-checkbox>
<rkm-form-checkbox-group
:options="optionalColumns"
class="mx-2"
stacked
text-field="label"
v-model="selectedColumns"
value-field="key"
/>
</rkm-dropdown>
<rkm-button
:disabled="itens.length === 0"
@click="handleImprimirClick"
color="info"
small
title="Imprimir"
>
<rkm-icon icon="print" />
</rkm-button>
</template>
<rkm-table
:fields="fields"
:items="itens"
/>
</rkm-area>
</div>
</template>
<script>
// Mixins.
import {error, formats, object, repository, stringCase} from '@/mixins'
// Others.
import moment from 'moment'
// Vuex.
import {mapActions} from 'vuex'
export default {
computed: {
fields() {
return this.columns.filter(column => (column.fixed || false) || this.selectedColumns.includes(column.key))
},
optionalColumns() {
return this.columns.filter(column => !(column.fixed || false))
},
validations() {
return {
periodo: { required: true },
}
},
requestQueryStringParamsUsuarioResponsavel() {
const request = this.clone(this.model)
request.periodo.start = this.others.moment(this.model.periodo.start).format('DD/MM/YYYY')
request.periodo.end = this.others.moment(this.model.periodo.end).format('DD/MM/YYYY')
return this.camelObjectKeysToSnakeRecursive(request)
},
},
created() {
this.repositories = {
impressao: this.getConectaSusRepository('impressao.administracao'),
relatorioHistoricoPermissaoPapel: this.getConectaSusRepository('administracao.relatorioHistoricoPermissaoPapel'),
}
},
data() {
return {
additionals: null,
columns: [
{
class: 'col-min text-center',
filterByFormatted: true,
fixed: true,
formatter: value => this.formatDateTime(value),
key: 'historicoDataHora',
label: 'Data/Hora',
sortable: true,
},
{key: 'papelNome', label: 'Papel', fixed: true, sortable: true },
{key: 'moduloNome', label: 'Módulo', fixed: true, sortable: true },
{key: 'funcionalidadeNome', label: 'Funcionalidade', fixed: true, sortable: true },
{key: 'crudAcaoNome', label: 'Permissão para', fixed: true, sortable: true },
{key: 'acaoNome', label: 'Tipo de ação', fixed: true, sortable: true },
{key: 'usuarioResponsavelNome', label: 'Permitido por', sortable: true },
],
form: null,
indeterminate: true,
itens: [],
model: null,
others: {moment},
selectedAll: false,
selectedColumns: [],
}
},
methods: {
...mapActions(['setLoading', 'unsetLoading']),
load() {
this.setLoading()
this.repositories.relatorioHistoricoPermissaoPapel.create()
.then(response => {
this.additionals = Object.assign(response.data.additionals)
this.form = Object.assign(response.data.form)
this.model = Object.assign(response.data.model)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handlePapelChange() {
this.form.modulos = []
this.form.funcionalidades = []
this.form.crudAcoes = []
this.form.acoes = []
this.model.moduloId = null
this.model.funcionalidadeId = null
this.model.crudAcaoId = null
this.model.acaoId = null
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
this.getModulos()
},
handleModuloChange() {
this.form.funcionalidades = []
this.form.crudAcoes = []
this.form.acoes = []
this.model.funcionalidadeId = null
this.model.crudAcaoId = null
this.model.acaoId = null
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
this.getFuncionalidades()
},
handleFuncionalidadeChange() {
this.form.crudAcoes = []
this.form.acoes = []
this.model.crudAcaoId = null
this.model.acaoId = null
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
this.getCrudAcoes()
},
handleCrudAcaoChange() {
this.form.acoes = []
this.model.acaoId = null
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
this.getAcoes()
},
handleAcaoChange() {
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
},
handleBuscarClick() {
this.$refs.RkmForm.validate()
.then(valid => {
if (valid === false) return
this.itens = []
const request = this.clone(this.model)
request.periodo.start = this.others.moment(this.model.periodo.start).format('DD/MM/YYYY')
request.periodo.end = this.others.moment(this.model.periodo.end).format('DD/MM/YYYY')
this.setLoading()
this.repositories.relatorioHistoricoPermissaoPapel.index(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.itens = response.data
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
})
},
handleLimparClick() {
this.form.papeis = []
this.form.modulos = []
this.form.funcionalidades = []
this.form.crudAcoes = []
this.form.acoes = []
this.model.periodo = {
start: null,
end: null,
}
this.model.papelId = null
this.model.moduloId = null
this.model.funcionalidadeId = null
this.model.crudAcaoId = null
this.model.acaoId = null
this.model.usuarioResponsavel = {
id: null,
nome: null
}
this.itens = []
this.$nextTick().then(() => this.$refs.RkmForm.resetValidator())
},
handleTodosClick(selected) {
this.selectedColumns = selected ? this.optionalColumns.map(c => c.key) : []
},
handleImprimirClick() {
this.setLoading()
const request = {
...this.request(),
selectedColumns: this.fields.map(field => ({
class: field.class,
key: this.camelToSnake(field.key),
label: field.label,
})),
}
this.repositories.impressao.relatorioHistoricoPermissaoPapel(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
const content = new Blob([response.data], {type: response.headers['content-type']})
const url = URL.createObjectURL(content)
const link = document.createElement('a')
link.href = url
link.target = '_blank'
link.click()
link.remove()
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
request() {
const model = this.clone(this.model)
model.periodo.start = this.others.moment(model.periodo.start).format('DD/MM/YYYY')
model.periodo.end = this.others.moment(model.periodo.end).format('DD/MM/YYYY')
return model
},
getPapeis() {
this.setLoading()
const request = {
listar: 'papeis',
...this.request(),
}
this.repositories.relatorioHistoricoPermissaoPapel.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.papeis = Object.assign(response.data.papeis)
this.form.modulos = Object.assign(response.data.modulos)
this.form.funcionalidades = Object.assign(response.data.funcionalidades)
this.form.crudAcoes = Object.assign(response.data.crudAcoes)
this.form.acoes = Object.assign(response.data.acoes)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
getModulos() {
this.setLoading()
const request = {
listar: 'modulos',
...this.request(),
}
this.repositories.relatorioHistoricoPermissaoPapel.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.modulos = Object.assign(response.data.modulos)
this.form.funcionalidades = Object.assign(response.data.funcionalidades)
this.form.crudAcoes = Object.assign(response.data.crudAcoes)
this.form.acoes = Object.assign(response.data.acoes)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
getFuncionalidades() {
this.setLoading()
const request = {
listar: 'funcionalidades',
...this.request(),
}
this.repositories.relatorioHistoricoPermissaoPapel.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.funcionalidades = Object.assign(response.data.funcionalidades)
this.form.crudAcoes = Object.assign(response.data.crudAcoes)
this.form.acoes = Object.assign(response.data.acoes)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
getCrudAcoes() {
this.setLoading()
const request = {
listar: 'crud-acoes',
...this.request(),
}
this.repositories.relatorioHistoricoPermissaoPapel.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.crudAcoes = Object.assign(response.data.crudAcoes)
this.form.acoes = Object.assign(response.data.acoes)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
getAcoes() {
this.setLoading()
const request = {
listar: 'acoes',
...this.request(),
}
this.repositories.relatorioHistoricoPermissaoPapel.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.acoes = Object.assign(response.data.acoes)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
},
mounted() {
this.load()
},
mixins: [error, formats, object, repository, stringCase],
watch: {
'model.periodo': {
deep: true,
handler(value) {
this.form.papeis = []
this.form.modulos = []
this.form.funcionalidades = []
this.form.crudAcoes = []
this.form.acoes = []
this.model.papelId = null
this.model.moduloId = null
this.model.funcionalidadeId = null
this.model.crudAcaoId = null
this.model.acaoId = null
this.model.usuarioResponsavel = {
id: null,
nome: null,
}
this.itens = []
if (value.start && value.end) {
this.getPapeis()
}
},
},
'model.usuarioResponsavel': {
deep: true,
handler() {
this.itens = []
},
},
selectedColumns(value) {
this.indeterminate = value.length > 0 && value.length != this.optionalColumns.length
this.selectedAll = value.length > 0 && value.length == this.optionalColumns.length
},
},
}
</script>
<style scoped>
.dropdown >>> .auto-scroll {
max-height: 400px;
overflow-y: auto;
width: 300px;
}
</style>

View File

@@ -0,0 +1,334 @@
<template>
<div v-if="model">
<rkm-area
no-margin-top
title="Filtros"
type="fieldset"
>
<rkm-form
ref="Form"
show-legend
>
<rkm-row>
<rkm-col cols="6">
<rkm-form-multiselect
:options="form.unidades"
:select-limit="null"
@update:dropdownClosed="handleUnidadeClosed"
label="Unidades"
option-name-key="nomeFantasia"
option-text-selecteds="unidade selecionado"
v-model="model.unidades"
value-field="id"
/>
</rkm-col>
<rkm-col cols="6">
<rkm-form-multiselect
:options="form.infraestruturas"
:select-limit="null"
@update:dropdownClosed="handleInfraestruturaClosed"
label="Locais de atendimento"
option-name-key="nome"
option-text-selecteds="local selecionado"
v-model="model.infraestruturas"
value-field="id"
/>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col cols="6">
<rkm-form-multiselect
:options="form.papeis"
:select-limit="null"
@update:dropdownClosed="handlePapelClosed"
label="Papéis"
option-name-key="nome"
option-text-selecteds="papel selecionado"
v-model="model.papeis"
value-field="id"
/>
</rkm-col>
<rkm-col cols="6">
<rkm-form-multiselect
:options="form.usuarios"
:select-limit="null"
@update:dropdownClosed="handleUsuarioClosed"
label="Usuários"
option-name-key="nome"
option-text-selecteds="usuário selecionado"
v-model="model.usuarios"
value-field="id"
/>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col cols="6">
<rkm-form-multiselect
:options="form.ocupacoes"
:select-limit="null"
@update:dropdownClosed="handleOcupacaoClosed"
label="CBO's"
option-name-key="nome"
option-text-selecteds="cbo selecionado"
v-model="model.ocupacoes"
value-field="id"
/>
</rkm-col>
<rkm-col cols="6">
<rkm-button
:label="false"
@click="$refs.Form.validate(search)"
color="primary"
>
Buscar
</rkm-button>
<rkm-button
:label="false"
@click="reset"
>
Limpar
</rkm-button>
</rkm-col>
</rkm-row>
</rkm-form>
</rkm-area>
<template v-if="data">
<rkm-area
no-margin-top
title="Resultado"
>
<template v-slot:trailing>
<rkm-button
@click="print"
color="info"
small
title="Imprimir"
>
<rkm-icon icon="print" />
</rkm-button>
<rkm-button
@click="exportTo"
color="info"
small
title="Exportar"
>
<rkm-icon icon="file-export" />
</rkm-button>
</template>
<rkm-table
:fields="fields"
:items="data.itens"
:sort-by="sortBy"
:sort-desc="sortDesc"
@sort-changed="onSortChanged"
/>
</rkm-area>
</template>
</div>
</template>
<script>
// Mixins.
import {error, file, repository, stringCase} from '@/mixins'
// Others.
import moment from 'moment'
// Vuex.
import {mapActions} from 'vuex'
export default {
created() {
this.repositories = {
exportacao: this.getConectaSusRepository('exportacao.administracao'),
impressao: this.getConectaSusRepository('impressao.administracao'),
relatorioPapelLotacao: this.getConectaSusRepository('administracao.relatorioPapelLotacao'),
}
this.load()
},
data() {
return {
data: null,
form: null,
fields: [
{key: 'unidadeNomeFantasia', label: 'Unidade', sortable: true},
{key: 'infraestruturaNome', label: 'Local de atendimento', sortable: true},
{key: 'papelNome', label: 'Papel', sortable: true},
{key: 'usuarioNome', label: 'Usuário', sortable: true},
{key: 'ocupacaoNome', label: 'CBO', sortable: true},
],
model: null,
others: {moment},
sortBy: 'unidadeNomeFantasia',
sortDesc: false,
}
},
methods: {
...mapActions(['setLoading', 'unsetLoading']),
load() {
this.setLoading()
this.repositories.relatorioPapelLotacao.create()
.then((response) => {
this.form = response.data.form
this.model = response.data.model
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
search() {
const request = {
unidades: this.model.unidades,
infraestruturas: this.model.infraestruturas,
papeis: this.model.papeis,
usuarios: this.model.usuarios,
ocupacoes: this.model.ocupacoes,
}
this.setLoading()
this.repositories.relatorioPapelLotacao.index(this.camelObjectKeysToSnakeRecursive(request))
.then(response => this.data = Object.assign(response.data))
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
reset() {
this.data = null
this.load()
this.$nextTick().then(() => this.$refs.Form.resetValidator())
},
print() {
this.setLoading()
const request = {
unidades: this.model.unidades,
infraestruturas: this.model.infraestruturas,
papeis: this.model.papeis,
usuarios: this.model.usuarios,
ocupacoes: this.model.ocupacoes,
sortBy: this.sortBy ? this.camelToSnake(this.sortBy) : null,
sortDesc: this.sortDesc,
}
this.repositories.impressao.relatorioPapelLotacao(this.camelObjectKeysToSnakeRecursive(request))
.then(({ data, headers }) => this.openFile(data, headers['content-type']))
.finally(() => this.unsetLoading())
},
exportTo() {
this.setLoading()
const request = {
unidades: this.model.unidades,
infraestruturas: this.model.infraestruturas,
papeis: this.model.papeis,
usuarios: this.model.usuarios,
ocupacoes: this.model.ocupacoes,
sortBy: this.sortBy ? this.camelToSnake(this.sortBy) : null,
sortDesc: this.sortDesc,
}
this.repositories.exportacao.relatorioPapelLotacao(this.camelObjectKeysToSnakeRecursive(request))
.then(({ data, headers }) => this.openFile(data, headers['content-type']))
.finally(() => this.unsetLoading())
},
handleUnidadeClosed() {
this.data = null
const request = {
listar: 'infraestruturas',
unidades: this.model.unidades,
}
this.setLoading()
this.repositories.relatorioPapelLotacao.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.papeis = response.data.papeis
this.form.usuarios = response.data.usuarios
this.form.ocupacoes = response.data.ocupacoes
this.form.infraestruturas = response.data.infraestruturas
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleInfraestruturaClosed() {
this.data = null
const request = {
listar: 'papeis',
unidades: this.model.unidades,
infraestruturas: this.model.infraestruturas,
}
this.setLoading()
this.repositories.relatorioPapelLotacao.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.papeis = response.data.papeis
this.form.usuarios = response.data.usuarios
this.form.ocupacoes = response.data.ocupacoes
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handlePapelClosed() {
this.data = null
const request = {
listar: 'usuarios',
unidades: this.model.unidades,
infraestruturas: this.model.infraestruturas,
papeis: this.model.papeis,
}
this.setLoading()
this.repositories.relatorioPapelLotacao.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.usuarios = response.data.usuarios
this.form.ocupacoes = response.data.ocupacoes
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleUsuarioClosed() {
this.data = null
const request = {
listar: 'ocupacoes',
unidades: this.model.unidades,
infraestruturas: this.model.infraestruturas,
papeis: this.model.papeis,
usuarios: this.model.usuarios,
}
this.setLoading()
this.repositories.relatorioPapelLotacao.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => this.form.ocupacoes = response.data.ocupacoes)
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleOcupacaoClosed() {
this.data = null
},
onSortChanged(context) {
this.sortBy = context.sortBy
this.sortDesc = context.sortDesc
},
},
mixins: [error, file, repository, stringCase],
}
</script>

View File

@@ -0,0 +1,375 @@
<template>
<div v-if="model">
<rkm-area
no-margin-top
title="Filtros"
type="fieldset"
>
<rkm-form
ref="Form"
show-legend
>
<rkm-row>
<rkm-col cols="6">
<rkm-form-multiselect
:options="form.unidades"
:select-limit="null"
@update:dropdownClosed="handleUnidadeClosed"
label="Unidades"
option-name-key="nomeFantasia"
option-text-selecteds="unidade selecionado"
v-model="model.unidades"
value-field="id"
/>
</rkm-col>
<rkm-col cols="6">
<rkm-form-multiselect
:options="form.infraestruturas"
:select-limit="null"
@update:dropdownClosed="handleInfraestruturaClosed"
label="Locais de atendimento"
option-name-key="nome"
option-text-selecteds="local selecionado"
v-model="model.infraestruturas"
value-field="id"
/>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col cols="6">
<rkm-form-multiselect
:options="form.usuarios"
:select-limit="null"
@update:dropdownClosed="handleUsuarioClosed"
label="Usuários"
option-name-key="nome"
option-text-selecteds="usuário selecionado"
v-model="model.usuarios"
value-field="id"
/>
</rkm-col>
<rkm-col cols="6">
<rkm-form-multiselect
:options="form.ocupacoes"
:select-limit="null"
@update:dropdownClosed="handleOcupacaoClosed"
label="CBO's"
option-name-key="nome"
option-text-selecteds="cbo selecionado"
v-model="model.ocupacoes"
value-field="id"
/>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col cols="3">
<rkm-form-multiselect
:options="form.modulos"
:select-limit="null"
@update:dropdownClosed="handleModuloClosed"
label="Módulos"
option-name-key="nome"
option-text-selecteds="módulo selecionado"
v-model="model.modulos"
value-field="id"
/>
</rkm-col>
<rkm-col cols="3">
<rkm-form-multiselect
:options="form.funcionalidades"
:select-limit="null"
@update:dropdownClosed="handleFuncionalidadeClosed"
label="Funcionalidades"
option-name-key="nome"
option-text-selecteds="funcionalidade selecionada"
v-model="model.funcionalidades"
value-field="id"
/>
</rkm-col>
<rkm-col cols="6">
<rkm-button
:label="false"
@click="$refs.Form.validate(search)"
color="primary"
>
Buscar
</rkm-button>
<rkm-button
:label="false"
@click="reset"
>
Limpar
</rkm-button>
</rkm-col>
</rkm-row>
</rkm-form>
</rkm-area>
<template v-if="data">
<rkm-area
no-margin-top
title="Resultado"
>
<template v-slot:trailing>
<rkm-button
@click="print"
color="info"
small
title="Imprimir"
>
<rkm-icon icon="print" />
</rkm-button>
<rkm-button
@click="exportTo"
color="info"
small
title="Exportar"
>
<rkm-icon icon="file-export" />
</rkm-button>
</template>
<rkm-table
:fields="fields"
:items="data.itens"
:sort-by="sortBy"
:sort-desc="sortDesc"
@sort-changed="onSortChanged"
/>
</rkm-area>
</template>
</div>
</template>
<script>
// Mixins.
import {error, file, repository, stringCase} from '@/mixins'
// Others.
import moment from 'moment'
// Vuex.
import {mapActions} from 'vuex'
export default {
created() {
this.repositories = {
exportacao: this.getConectaSusRepository('exportacao.administracao'),
impressao: this.getConectaSusRepository('impressao.administracao'),
relatorioPermissaoLotacao: this.getConectaSusRepository('administracao.relatorioPermissaoLotacao'),
}
this.load()
},
data() {
return {
data: null,
form: null,
fields: [
{key: 'unidadeNomeFantasia', label: 'Unidade', sortable: true},
{key: 'infraestruturaNome', label: 'Local de atendimento', sortable: true},
{key: 'usuarioNome', label: 'Usuário', sortable: true},
{key: 'ocupacaoNome', label: 'CBO', sortable: true},
{key: 'moduloNome', label: 'Módulo', sortable: true},
{key: 'funcionalidadeNome', label: 'Funcionalidade', sortable: true},
],
model: null,
others: {moment},
sortBy: 'unidadeNomeFantasia',
sortDesc: false,
}
},
methods: {
...mapActions(['setLoading', 'unsetLoading']),
load() {
this.setLoading()
this.repositories.relatorioPermissaoLotacao.create()
.then((response) => {
this.form = response.data.form
this.model = response.data.model
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
search() {
const request = {
unidades: this.model.unidades,
infraestruturas: this.model.infraestruturas,
usuarios: this.model.usuarios,
ocupacoes: this.model.ocupacoes,
modulos: this.model.modulos,
funcionalidades: this.model.funcionalidades,
}
this.setLoading()
this.repositories.relatorioPermissaoLotacao.index(this.camelObjectKeysToSnakeRecursive(request))
.then(response => this.data = Object.assign(response.data))
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
reset() {
this.data = null
this.load()
this.$nextTick().then(() => this.$refs.Form.resetValidator())
},
print() {
this.setLoading()
const request = {
unidades: this.model.unidades,
infraestruturas: this.model.infraestruturas,
usuarios: this.model.usuarios,
ocupacoes: this.model.ocupacoes,
modulos: this.model.modulos,
funcionalidades: this.model.funcionalidades,
sortBy: this.sortBy ? this.camelToSnake(this.sortBy) : null,
sortDesc: this.sortDesc,
}
this.repositories.impressao.relatorioPermissaoLotacao(this.camelObjectKeysToSnakeRecursive(request))
.then(({ data, headers }) => this.openFile(data, headers['content-type']))
.finally(() => this.unsetLoading())
},
exportTo() {
this.setLoading()
const request = {
unidades: this.model.unidades,
infraestruturas: this.model.infraestruturas,
usuarios: this.model.usuarios,
ocupacoes: this.model.ocupacoes,
modulos: this.model.modulos,
funcionalidades: this.model.funcionalidades,
sortBy: this.sortBy ? this.camelToSnake(this.sortBy) : null,
sortDesc: this.sortDesc,
}
this.repositories.exportacao.relatorioPermissaoLotacao(this.camelObjectKeysToSnakeRecursive(request))
.then(({ data, headers }) => this.openFile(data, headers['content-type']))
.finally(() => this.unsetLoading())
},
handleUnidadeClosed() {
this.data = null
const request = {
listar: 'infraestruturas',
unidades: this.model.unidades,
}
this.setLoading()
this.repositories.relatorioPermissaoLotacao.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.infraestruturas = response.data.infraestruturas
this.form.usuarios = response.data.usuarios
this.form.ocupacoes = response.data.ocupacoes
this.form.modulos = response.data.modulos
this.form.funcionalidades = response.data.funcionalidades
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleInfraestruturaClosed() {
this.data = null
const request = {
listar: 'modulos',
unidades: this.model.unidades,
infraestruturas: this.model.infraestruturas,
}
this.setLoading()
this.repositories.relatorioPermissaoLotacao.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.usuarios = response.data.usuarios
this.form.ocupacoes = response.data.ocupacoes
this.form.modulos = response.data.modulos
this.form.funcionalidades = response.data.funcionalidades
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleUsuarioClosed() {
this.data = null
const request = {
listar: 'ocupacoes',
unidades: this.model.unidades,
infraestruturas: this.model.infraestruturas,
usuarios: this.model.usuarios,
}
this.setLoading()
this.repositories.relatorioPermissaoLotacao.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.ocupacoes = response.data.ocupacoes
this.form.modulos = response.data.modulos
this.form.funcionalidades = response.data.funcionalidades
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleOcupacaoClosed() {
this.data = null
const request = {
listar: 'modulos',
unidades: this.model.unidades,
infraestruturas: this.model.infraestruturas,
usuarios: this.model.usuarios,
ocupacoes: this.model.ocupacoes,
}
this.setLoading()
this.repositories.relatorioPermissaoLotacao.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.modulos = response.data.modulos
this.form.funcionalidades = response.data.funcionalidades
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleModuloClosed() {
this.data = null
const request = {
listar: 'funcionalidades',
usuarios: this.model.usuarios,
unidades: this.model.unidades,
infraestruturas: this.model.infraestruturas,
modulos: this.model.modulos,
}
this.setLoading()
this.repositories.relatorioPermissaoLotacao.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => this.form.funcionalidades = response.data.funcionalidades)
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleFuncionalidadeClosed() {
this.data = null
},
onSortChanged(context) {
this.sortBy = context.sortBy
this.sortDesc = context.sortDesc
},
},
mixins: [error, file, repository, stringCase],
}
</script>

View File

@@ -0,0 +1,254 @@
<template>
<div v-if="model">
<rkm-area
no-margin-top
title="Filtros"
type="fieldset"
>
<rkm-form
ref="Form"
show-legend
>
<rkm-row>
<rkm-col cols="6">
<rkm-form-multiselect
:options="form.papeis"
:select-limit="null"
@update:dropdownClosed="handlePapelClosed"
label="Papéis"
option-name-key="nome"
option-text-selecteds="papel selecionado"
v-model="model.papeis"
value-field="id"
/>
</rkm-col>
<rkm-col cols="6">
<rkm-form-multiselect
:options="form.modulos"
:select-limit="null"
@update:dropdownClosed="handleModuloClosed"
label="Módulos"
option-name-key="nome"
option-text-selecteds="módulo selecionado"
v-model="model.modulos"
value-field="id"
/>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col cols="6">
<rkm-form-multiselect
:options="form.funcionalidades"
:select-limit="null"
@update:dropdownClosed="handleFuncionalidadeClosed"
label="Funcionalidades"
option-name-key="nome"
option-text-selecteds="funcionalidade selecionada"
v-model="model.funcionalidades"
value-field="id"
/>
</rkm-col>
<rkm-col cols="6">
<rkm-button
:label="false"
@click="$refs.Form.validate(search)"
color="primary"
>
Buscar
</rkm-button>
<rkm-button
:label="false"
@click="reset"
>
Limpar
</rkm-button>
</rkm-col>
</rkm-row>
</rkm-form>
</rkm-area>
<template v-if="data">
<rkm-area
no-margin-top
title="Resultado"
>
<template v-slot:trailing>
<rkm-button
@click="print"
color="info"
small
title="Imprimir"
>
<rkm-icon icon="print" />
</rkm-button>
<rkm-button
@click="exportTo"
color="info"
small
title="Exportar"
>
<rkm-icon icon="file-export" />
</rkm-button>
</template>
<rkm-table
:fields="fields"
:items="data.itens"
:sort-by="sortBy"
:sort-desc="sortDesc"
@sort-changed="onSortChanged"
/>
</rkm-area>
</template>
</div>
</template>
<script>
// Mixins.
import {error, file, repository, stringCase} from '@/mixins'
// Others.
import moment from 'moment'
// Vuex.
import {mapActions} from 'vuex'
export default {
created() {
this.repositories = {
exportacao: this.getConectaSusRepository('exportacao.administracao'),
impressao: this.getConectaSusRepository('impressao.administracao'),
relatorioPermissaoPapel: this.getConectaSusRepository('administracao.relatorioPermissaoPapel'),
}
this.load()
},
data() {
return {
data: null,
form: null,
fields: [
{key: 'papelNome', label: 'Papel', sortable: true},
{key: 'moduloNome', label: 'Módulo', sortable: true},
{key: 'funcionalidadeNome', label: 'Funcionalidade', sortable: true},
],
model: null,
others: {moment},
sortBy: 'papelNome',
sortDesc: false,
}
},
methods: {
...mapActions(['setLoading', 'unsetLoading']),
load() {
this.setLoading()
this.repositories.relatorioPermissaoPapel.create()
.then((response) => {
this.form = response.data.form
this.model = response.data.model
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
search() {
const request = {
papeis: this.model.papeis,
modulos: this.model.modulos,
funcionalidades: this.model.funcionalidades,
}
this.setLoading()
this.repositories.relatorioPermissaoPapel.index(this.camelObjectKeysToSnakeRecursive(request))
.then(response => this.data = Object.assign(response.data))
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
reset() {
this.data = null
this.load()
this.$nextTick().then(() => this.$refs.Form.resetValidator())
},
print() {
this.setLoading()
const request = {
papeis: this.model.papeis,
modulos: this.model.modulos,
funcionalidades: this.model.funcionalidades,
sortBy: this.sortBy ? this.camelToSnake(this.sortBy) : null,
sortDesc: this.sortDesc,
}
this.repositories.impressao.relatorioPermissaoPapel(this.camelObjectKeysToSnakeRecursive(request))
.then(({ data, headers }) => this.openFile(data, headers['content-type']))
.finally(() => this.unsetLoading())
},
exportTo() {
this.setLoading()
const request = {
papeis: this.model.papeis,
modulos: this.model.modulos,
funcionalidades: this.model.funcionalidades,
sortBy: this.sortBy ? this.camelToSnake(this.sortBy) : null,
sortDesc: this.sortDesc,
}
this.repositories.exportacao.relatorioPermissaoPapel(this.camelObjectKeysToSnakeRecursive(request))
.then(({ data, headers }) => this.openFile(data, headers['content-type']))
.finally(() => this.unsetLoading())
},
handlePapelClosed() {
this.data = null
const request = {
listar: 'modulos',
papeis: this.model.papeis,
}
this.setLoading()
this.repositories.relatorioPermissaoPapel.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => {
this.form.modulos = response.data.modulos
this.form.funcionalidades = response.data.funcionalidades
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleModuloClosed() {
this.data = null
const request = {
listar: 'funcionalidades',
papeis: this.model.papeis,
modulos: this.model.modulos,
}
this.setLoading()
this.repositories.relatorioPermissaoPapel.list(this.camelObjectKeysToSnakeRecursive(request))
.then(response => this.form.funcionalidades = response.data.funcionalidades)
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleFuncionalidadeClosed() {
this.data = null
},
onSortChanged(context) {
this.sortBy = context.sortBy
this.sortDesc = context.sortDesc
},
},
mixins: [error, file, repository, stringCase],
}
</script>

View File

@@ -0,0 +1,113 @@
<template>
<div>
<rkm-data-table
:fields="fields"
:repository="repositories.baixa"
sort-by="descricao"
search-by="descricao"
>
<template v-slot:cell(isAtivo)="{ item }">
{{ item.isAtivo ? 'ATIVO' : 'INATIVO' }}
</template>
<template v-slot:cell(actions)="{ item }">
<rkm-button
@click.stop="$refs.ModalMaterial.open(item)"
authorize="store-almoxarifado-baixa"
color="primary"
small
title="Cadastrar baixa"
>
<rkm-icon icon="plus" />
</rkm-button>
<rkm-button
@click.stop="$refs.ModalHistorico.open(item)"
authorize="reverse-almoxarifado-baixa"
color="primary"
small
title="Histórico de baixa"
>
<rkm-icon icon="history" />
</rkm-button>
</template>
</rkm-data-table>
<ModalMaterial ref="ModalMaterial" />
<ModalHistorico ref="ModalHistorico" />
</div>
</template>
<script>
// Components.
import ModalMaterial from './components/material/Modal'
import ModalHistorico from './components/historico/Modal'
// Mixins.
import { repository, formats } from '@/mixins'
export default {
components: { ModalMaterial, ModalHistorico },
created() {
this.repositories = {
baixa: this.getConectaSusRepository('almoxarifado.baixa')
}
},
computed: {
fields() {
return [
{
name: 'cadastroMaterial.codigoMaterial',
key: 'codigoMaterial',
label: 'Código do material',
class: 'col-min',
searchable: true,
sortable: true
},
{
name: 'descricao',
key: 'descricao',
label: 'Descrição',
searchable: true,
sortable: true
},
{
name: 'cadastroGrupo.nome',
key: 'grupo',
label: 'Grupo',
searchable: true,
sortable: true
},
{
name: 'cadastroSubgrupo.nome',
key: 'subgrupo',
label: 'Subgrupo',
searchable: true,
sortable: true
},
{
name: 'cadastroMaterial.isAtivo',
key: 'isAtivo',
label: 'Status',
class: 'col-min',
},
{
key: 'actions',
label: 'Ações',
class: 'col-min'
},
{
name: 'almoxarifadoEntradaItem.codigoBarras',
key: 'codigoBarras',
label: 'Código de Barras',
searchable: true,
visible: false,
},
]
}
},
mixins: [ repository, formats ],
}
</script>

View File

@@ -0,0 +1,178 @@
<template>
<div>
<rkm-data-table
:fields="fields"
:repository="repositories.baixa.history"
:request-params="requestParams"
:tbody-tr-class="rowClass"
class="table-baixas"
sort-by="data"
>
<template v-slot:cell(actions)="{ item }">
<rkm-button
@click.stop="$refs.ModalDetalhes.open(item)"
color="outline-primary"
small
title="Exibir detalhes"
>
<rkm-icon icon="align-justify" />
</rkm-button>
<rkm-button
:disabled="!!item.estornado"
@click.stop="print(item)"
color="primary"
small
title="Imprimir"
>
<rkm-icon icon="print" />
</rkm-button>
<rkm-button
:disabled="!!item.estornado"
:title="!!item.estornado ? 'Estornado' : 'Estornar'"
@click="handleEstorno(item)"
color="danger"
small
>
<rkm-icon icon="undo-alt" />
</rkm-button>
</template>
<template #afterPagination>
<div class="mt-2">
<rkm-row>
<rkm-col>
<p>A linha em <span class="red">vermelho</span> representa uma baixa que foi estornada.</p>
</rkm-col>
</rkm-row>
</div>
</template>
</rkm-data-table>
<ModalDetalhes ref="ModalDetalhes" />
</div>
</template>
<script>
// Components.
import ModalDetalhes from './components/detalhe/Modal'
import moment from 'moment'
import { mapActions } from 'vuex'
// Mixins.
import { repository, error, file } from '@/mixins'
export default {
components: { ModalDetalhes },
computed: {
requestParams() {
return { material_id: this.material.id }
},
fields() {
return [
{
name: 'almoxarifadoSaida.createdAt',
key: 'data',
label: 'Data',
searchable: true,
sortable: true,
},
{
name: 'almoxarifadoMotivoBaixa.descricao',
key: 'motivoDescricao',
label: 'Motivo',
searchable: true,
sortable: true,
},
{
name: 'cadastroCidadao.nome',
key: 'usuarioSolicitanteNome',
label: 'Usuário solicitante',
searchable: true,
sortable: true,
},
{
name: 'quantidade',
key: 'quantidade',
label: 'Quantidade',
},
{
key: 'actions',
label: 'Ações',
class: 'col-min'
},
]
},
},
created() {
this.repositories = {
baixa: this.getConectaSusRepository('almoxarifado.baixa'),
balancete: this.getConectaSusRepository('almoxarifado.balancete'),
impressao: this.getConectaSusRepository('impressao.almoxarifado'),
}
},
methods: {
...mapActions(['setLoading', 'unsetLoading']),
print({ saidaItemId }) {
this.setLoading()
return this.repositories.impressao.baixa(saidaItemId)
.then(response => {
this.openFile(response.data, response.headers['content-type'])
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleEstorno(item) {
const data = moment(item.data, 'DD/MM/YYYY').format('YYYY-MM-DD')
this.setLoading()
this.repositories.balancete.checkDate(data)
.then(({ data }) => {
if (!data.mesFechado) {
this.showDeleteConfirmation(
'Deseja realmente estornar a baixa?',
() => this.repositories.baixa.reverse(item.saidaItemId)
.then(() => item.estornado = true),
this.handleError
)
return
}
this.showValidationNotification(
'A data da baixa corresponde a um mês já finalizado. Estorno não permitido.'
)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
rowClass(item, type) {
if (type === 'row' && item && !!item.estornado) {
return 'table-danger'
}
}
},
mixins: [ repository, error, file ],
props: {
material: { type: Object, required: true }
}
}
</script>
<style scoped>
.table-baixas >>> .red { color: red; }
</style>

View File

@@ -0,0 +1,40 @@
<template>
<rkm-modal
:buttons="['cancel']"
:loading="loading"
:title="`Histórico de baixa (${material.descricao})`"
large
ref="Modal"
>
<List
:material="material"
ref="List"
/>
</rkm-modal>
</template>
<script>
// Components.
import List from './List'
// Vuex.
import { mapGetters } from 'vuex'
export default {
components: { List },
computed: {
...mapGetters({ loading: 'getLoading' }),
},
data: () => ({
material: {},
}),
methods: {
open(material) {
this.material = material
this.$refs.Modal.show()
},
}
}
</script>

View File

@@ -0,0 +1,89 @@
<template>
<rkm-modal
:buttons="['cancel']"
:loading="loading"
large
ref="Modal"
title="Detalhes"
>
<rkm-table
:fields="fields"
:items="items"
hide-filters
hide-pagination
thead-class="d-none"
/>
</rkm-modal>
</template>
<script>
// Vuex.
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters({ loading: 'getLoading' }),
fields() {
return [
{
key: "field",
class: "font-weight-bold col-min",
},
{
key: "value",
},
]
}
},
data: () => ({
items: [],
}),
methods: {
open(item) {
this.items = [
{
field: 'Data',
value: item.data,
},
{
field: 'Motivo',
value: item.motivoDescricao,
},
{
field: 'Justificativa',
value: item.justificativa || '-',
},
{
field: 'Usuário solicitante',
value: item.usuarioSolicitanteNome || '-',
},
{
field: 'Usuário que realizou a baixa',
value: item.usuarioBaixaNome,
},
{
field: 'Quantidade',
value: item.quantidade,
},
]
if (item.usuarioEstornoNome) {
this.items.push({
field: 'Usuário que realizou o estorno',
value: item.usuarioEstornoNome,
})
this.items.push({
field: 'Data do estorno',
value: item.dataEstorno,
})
}
this.$refs.Modal.show()
},
}
}
</script>

View File

@@ -0,0 +1,377 @@
<template>
<div>
<template v-if="loaded">
<rkm-form
ref="Form"
show-legend
>
<rkm-row>
<rkm-col>
<rkm-form-select
:options="form.motivos"
:validations="validations.motivo"
label="Motivo"
show-default-option
textField="descricao"
v-model="model.motivoId"
valueField="id"
/>
</rkm-col>
<rkm-col
v-if="isConsumoInterno"
cols="6"
>
<rkm-form-input-autocomplete
:repository="repositories.usuario"
:request-query-string-params="autocompleteQueryStringParams.usuarioSolicitante"
:search-fields="autocompleteFields.usuarioSolicitante"
:validations="validations.usuarioSolicitante"
label="Usuário solicitante"
uppercase
text-field="cidadaoNome"
v-model="model.usuarioSolicitante"
/>
</rkm-col>
</rkm-row>
<rkm-row v-if="precisaJustificar">
<rkm-col>
<rkm-form-input
:validations="validations.justificativa"
label="Justificativa"
uppercase
v-model="model.justificativa"
/>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col cols="6">
<rkm-form-input
:value="model.estoque"
disabled
label="Estoque"
>
<rkm-input-group-append slot="append">
<rkm-button
@click.stop="loadEstoque"
color="primary"
title="Atualizar estoque"
v-rkm-tooltip.hover
>
<rkm-icon icon="sync-alt" />
</rkm-button>
</rkm-input-group-append>
</rkm-form-input>
</rkm-col>
<rkm-col cols="6">
<rkm-form-input
:disabled="!temEstoque"
:validations="validations.quantidade"
label="Quantidade"
v-model="model.quantidade"
type="number"
>
<rkm-input-group-append
v-if="form.possuiEntradaComLote"
slot="append"
>
<rkm-button
:disabled="!quantidadeValida"
@click.stop="$refs.ModalLote.open(model.entradaItens)"
title="Visualizar lotes"
v-rkm-tooltip.hover
>
<rkm-icon icon="box-open" />
</rkm-button>
</rkm-input-group-append>
</rkm-form-input>
</rkm-col>
</rkm-row>
</rkm-form>
<ModalLote
:model="model"
ref="ModalLote"
/>
</template>
</div>
</template>
<script>
// Components.
import ModalLote from '@/components/almoxarifado/components/lote/Modal'
// Vuex.
import { mapActions } from 'vuex'
import { mapGetters } from 'vuex'
// Mixins.
import { repository, error, stringCase, object, data, file } from '@/mixins'
// Enums
import motivoBaixaTipo from '@/enums/almoxarifado/motivoBaixaTipo'
export default {
components: { ModalLote },
computed: {
...mapGetters({ lotacao: 'getLotacao' }),
autocompleteFields() {
return {
usuarioSolicitante: [{ key: 'cadastro_cidadao.nome', searchable: true }],
}
},
autocompleteQueryStringParams() {
return {
usuarioSolicitante: {
infraestrutura_id: this.lotacao.infraestrutura.id,
},
}
},
validations() {
return {
motivo: {
required: true,
},
justificativa: {
required: this.precisaJustificar,
max: 100,
},
usuarioSolicitante: {
required: this.isConsumoInterno
},
quantidade: {
required: true,
min_value: 1,
max_value: this.model.estoque,
},
}
},
precisaJustificar() {
if (this.model && this.model.motivoId) {
return !!this.form.motivos.find(m => m.id === this.model.motivoId).justificar
}
return false
},
isConsumoInterno() {
if (this.model && this.model.motivoId) {
return this.form.motivos
.find(m => m.id === this.model.motivoId).tipoId === this.enums.motivoBaixaTipo.CONSUMO_INTERNO
}
return false
},
temEstoque() {
return this.model && this.model.estoque > 0
},
quantidadeValida() {
if (!this.model.quantidade) {
return false
}
const quantidade = parseInt(this.model.quantidade)
return quantidade > 0 && quantidade <= this.model.estoque
},
},
created() {
this.repositories = {
usuario: this.getConectaSusRepository('cadastro.sujeitoAtencao.usuario'),
baixa: this.getConectaSusRepository('almoxarifado.baixa'),
material: this.getConectaSusRepository('cadastro.material'),
impressao: this.getConectaSusRepository('impressao.almoxarifado'),
}
this.load()
},
data: () => ({
form: null,
loaded: false,
model: null,
enums: { motivoBaixaTipo },
}),
methods: {
...mapActions(['setLoading', 'unsetLoading']),
load() {
this.setLoading()
this.repositories.baixa.create(this.material.id)
.then(({ data }) => {
this.form = data.form
this.model = data.model
this.loaded = true
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
save(imprimir = false) {
this.$refs.Form.validate()
.then(valid => {
if (!valid) return
this.setLoading()
this.repositories.baixa.store(this.prepareData())
.then(({ data }) => {
this.showSaveNotification()
if (imprimir) {
this.print(data.id).then(() => this.$emit('save', data))
}
else {
this.$emit('save', data)
}
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
})
},
prepareData() {
const data = this.clone(this.model)
delete data.estoque
data.materialId = this.material.id
data.quantidade = parseInt(data.quantidade)
this.createIdKeysToObject(data, ['usuarioSolicitante'])
data.entradaItens = data.entradaItens
.filter(i => i.quantidadeSaida > 0)
.map(item => ({
entradaItemId: item.entradaItemId,
quantidadeSaida: isNaN(parseInt(item.quantidadeSaida)) ? 0 : parseInt(item.quantidadeSaida),
}))
return this.camelObjectKeysToSnakeRecursive(data)
},
print(saidaId) {
this.setLoading()
return this.repositories.impressao.baixa(saidaId)
.then(response => {
this.openFile(response.data, response.headers['content-type'])
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
loadEstoque() {
this.form.possuiEntradaComLote = false
this.model.estoque = 0
this.model.entradaItens = []
this.model.escolheuEntradaItemManualmente = false
this.model.quantidade = null
this.setLoading()
this.repositories.material.getEstoque(this.material.id)
.then(({ data }) => {
this.form.possuiEntradaComLote = data.possuiEntradaComLote
this.model.estoque = data.total
this.model.entradaItens = data.porEntrada.map(estoqueEntrada => {
estoqueEntrada.quantidadeSaida = 0
return estoqueEntrada
})
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
selectEntradaItens() {
this.model.escolheuEntradaItemManualmente = false
this.model.entradaItens.map(e => e.quantidadeSaida = 0)
let quantidade = parseInt(this.model.quantidade)
let i = 0
while (quantidade > 0) {
const entradaItem = this.model.entradaItens[i]
const estoque = parseInt(entradaItem.estoque)
if (quantidade > estoque) {
entradaItem.quantidadeSaida = estoque
quantidade -= estoque
}
else {
entradaItem.quantidadeSaida = quantidade
quantidade = 0
}
i++
}
}
},
mixins: [repository, error, stringCase, object, data, file],
props: {
material: { type: Object, required: true },
},
watch: {
'precisaJustificar': function (precisa) {
if (!precisa) {
this.model.justificativa = null
}
},
'isConsumoInterno': function (isConsumoInterno) {
if (isConsumoInterno) {
this.model.usuarioSolicitante = {
id: this.lotacao.usuario.id,
cidadaoNome: this.lotacao.usuario.cidadao.nome,
}
}
else {
this.model.usuarioSolicitante = null
}
},
'model.quantidade': function () {
if (this.form.possuiEntradaComLote && this.quantidadeValida) {
this.selectEntradaItens()
}
},
'temEstoque': function (temEstoque) {
this.$emit('has-estoque', temEstoque)
}
}
}
</script>

View File

@@ -0,0 +1,71 @@
<template>
<rkm-modal
:loading="loading"
:title="`Cadastro de baixa (${material.descricao})`"
large
ref="Modal"
>
<Form
:material="material"
@has-estoque="disableSave = !$event"
@save="handleExit"
ref="Form"
/>
<template #footer>
<rkm-button
:disabled="!!loading"
@click="handleExit"
>
Fechar
</rkm-button>
<rkm-button
:disabled="!!loading || disableSave"
@click="$refs.Form.save()"
color="primary"
>
Salvar
</rkm-button>
<rkm-button
:disabled="!!loading || disableSave"
@click="$refs.Form.save(true)"
color="primary"
>
Salvar e imprimir
</rkm-button>
</template>
</rkm-modal>
</template>
<script>
// Components.
import Form from './Form'
// Vuex.
import { mapGetters } from 'vuex'
export default {
components: { Form },
computed: {
...mapGetters({ loading: 'getLoading' }),
},
data: () => ({
material: {},
disableSave: true,
}),
methods: {
open(material) {
this.material = material
this.$refs.Modal.show()
},
handleExit() {
this.$refs.Modal.hide()
},
}
}
</script>

View File

@@ -0,0 +1,513 @@
<template>
<div v-if="loaded">
<rkm-alert v-if="additionals.dataUltimoBalanceteFechado">
<strong>Último balancete fechado:</strong> {{ additionals.dataUltimoBalanceteFechado }}.
</rkm-alert>
<rkm-area
type="fieldset"
title="Filtros"
>
<rkm-form ref="Form">
<rkm-row>
<rkm-col cols="6">
<rkm-form-date-picker
:end-validations="validations.periodo"
:max-date="additionals.dataAtual"
:range-inline="false"
:start-validations="validations.periodo"
end-label="Até"
range
start-label="Período de"
v-model="model.periodo"
/>
</rkm-col>
<rkm-col cols="6">
<rkm-form-select
:options="form.grupos"
default-option-text="Todos"
label="Grupo"
show-default-option
text-field="nome"
v-model="model.grupoId"
value-field="id"
/>
</rkm-col>
</rkm-row>
<rkm-row>
<rkm-col cols="6">
<rkm-form-select
:disabled="!model.grupoId"
:options="subgrupos"
default-option-text="Todos"
label="Subgrupo"
show-default-option
text-field="nome"
v-model="model.subgrupoId"
value-field="id"
/>
</rkm-col>
<rkm-col>
<rkm-form-select
:options="form.origensProgramaticas"
default-option-text="Todas"
label="Origem programática"
show-default-option
text-field="nome"
v-model="model.origemProgramaticaId"
value-field="id"
/>
</rkm-col>
<rkm-col
class="text-nowrap"
cols="auto"
>
<rkm-button
:label="false"
@click="$refs.Form.validate(search)"
color="primary"
>
Buscar
</rkm-button>
<rkm-button
:label="false"
@click="clear"
>
Limpar
</rkm-button>
</rkm-col>
</rkm-row>
</rkm-form>
</rkm-area>
<rkm-area
no-margin-top
title="Resultado"
>
<template v-slot:trailing>
<rkm-dropdown
class="mr-2 text-nowrap"
color="primary"
cols="1"
menu-class="auto-scroll"
no-caret
small
title="Colunas"
v-rkm-tooltip.hover
>
<template v-slot:button-content>
<rkm-icon icon="columns" />
</template>
<rkm-form-checkbox
:indeterminate="indeterminate"
:value="true"
@change="handleSelectedColumns"
class="mx-2"
v-model="selectedAll"
>
Todas
</rkm-form-checkbox>
<rkm-form-checkbox-group
:options="columns"
class="mx-2"
stacked
v-model="selectedCols"
/>
</rkm-dropdown>
<rkm-button
:disabled="itens.length === 0 || !(showSaldoFisico || showSaldoFinanceiro)"
@click="print()"
color="info"
small
title="Imprimir"
>
<rkm-icon icon="print" />
</rkm-button>
</template>
<rkm-table
:fields="fields"
:foot-clone="itens.length > 0 && (showSaldoFisico || showSaldoFinanceiro)"
:items="itens"
hide-filters
hide-pagination
no-footer-sorting
>
<template
v-if="showSaldoFisico || showSaldoFinanceiro"
#thead-top
>
<rkm-tr>
<rkm-th
:colspan="showMaterial
? (showSubgrupo ? 4 : 3)
: (showSubgrupo ? 2 : 1)"
>
</rkm-th>
<rkm-th
:colspan="showSaldoFisico && showSaldoFinanceiro ? 2 : 1"
class="text-center text-nowrap"
>
Saldo anterior
</rkm-th>
<rkm-th
:colspan="showSaldoFisico && showSaldoFinanceiro ? 2 : 1"
class="text-center"
>
Entrada
</rkm-th>
<rkm-th
:colspan="showSaldoFisico && showSaldoFinanceiro ? 2 : 1"
class="text-center"
>
Saída
</rkm-th>
<rkm-th
:colspan="showSaldoFisico && showSaldoFinanceiro ? 2 : 1"
class="text-center text-nowrap"
>
Saldo atual
</rkm-th>
</rkm-tr>
</template>
<template #foot(materialCodigo)>
<span></span>
</template>
<template #foot(materialDescricao)>
<span></span>
</template>
<template #foot(grupoNome)>
<span></span>
</template>
<template #foot(subgrupoNome)>
<span></span>
</template>
<template #foot(saldoAnteriorFisico)>
<strong>{{ resultado.totalizadores.saldoAnteriorFisico }}</strong>
</template>
<template #foot(saldoAnteriorFinanceiro)>
<strong>{{ resultado.totalizadores.saldoAnteriorFinanceiro }}</strong>
</template>
<template #foot(saldoEntradaFisico)>
<strong>{{ resultado.totalizadores.saldoEntradaFisico }}</strong>
</template>
<template #foot(saldoEntradaFinanceiro)>
<strong>{{ resultado.totalizadores.saldoEntradaFinanceiro }}</strong>
</template>
<template #foot(saldoSaidaFisico)>
<strong>{{ resultado.totalizadores.saldoSaidaFisico }}</strong>
</template>
<template #foot(saldoSaidaFinanceiro)>
<strong>{{ resultado.totalizadores.saldoSaidaFinanceiro }}</strong>
</template>
<template #foot(saldoAtualFisico)>
<strong>{{ resultado.totalizadores.saldoAtualFisico }}</strong>
</template>
<template #foot(saldoAtualFinanceiro)>
<strong>{{ resultado.totalizadores.saldoAtualFinanceiro }}</strong>
</template>
</rkm-table>
</rkm-area>
</div>
</template>
<script>
import { repository, error } from '@/mixins'
import { mapActions } from 'vuex'
import moment from 'moment'
export default {
computed: {
validations() {
return {
periodo: { required: true },
}
},
fields() {
const codigoMaterial = { key: 'materialCodigo', label: 'Código', sortable: true, class: 'col-min text-center' }
const descricaoMaterial = { key: 'materialDescricao', label: 'Material', sortable: true }
const nomeGrupo = { key: 'grupoNome', label: 'Grupo', sortable: true }
const nomeSubgrupo = { key: 'subgrupoNome', label: 'Subgrupo', sortable: true }
const saldoAnteriorFisico = { key: 'saldoAnteriorFisico', label: 'Físico', class: 'col-min text-center' }
const saldoAnteriorFinanceiro = { key: 'saldoAnteriorFinanceiro', label: 'Financeiro', class: 'col-min text-center' }
const entradaFisico = { key: 'saldoEntradaFisico', label: 'Físico', class: 'col-min text-center' }
const entradaFinanceiro = { key: 'saldoEntradaFinanceiro', label: 'Financeiro', class: 'col-min text-center' }
const saidaFisico = { key: 'saldoSaidaFisico', label: 'Físico', class: 'col-min text-center' }
const saidaFinanceiro = { key: 'saldoSaidaFinanceiro', label: 'Financeiro', class: 'col-min text-center' }
const saldoAtualFisico = { key: 'saldoAtualFisico', label: 'Físico', class: 'col-min text-center' }
const saldoAtualFinanceiro = { key: 'saldoAtualFinanceiro', label: 'Financeiro', class: 'col-min text-center' }
let fields = []
if (this.showMaterial) {
fields.push(codigoMaterial)
fields.push(descricaoMaterial)
}
fields.push(nomeGrupo)
if (this.showSubgrupo) {
fields.push(nomeSubgrupo)
}
if (this.showSaldoFisico && this.showSaldoFinanceiro) {
fields = fields.concat([
saldoAnteriorFisico,
saldoAnteriorFinanceiro,
entradaFisico,
entradaFinanceiro,
saidaFisico,
saidaFinanceiro,
saldoAtualFisico,
saldoAtualFinanceiro,
])
}
else if (this.showSaldoFisico) {
fields = fields.concat([
saldoAnteriorFisico,
entradaFisico,
saidaFisico,
saldoAtualFisico,
])
}
else if (this.showSaldoFinanceiro) {
fields = fields.concat([
saldoAnteriorFinanceiro,
entradaFinanceiro,
saidaFinanceiro,
saldoAtualFinanceiro,
])
}
return fields
},
subgrupos() {
if (this.model.grupoId) {
return this.form.grupos.find(g => g.id === this.model.grupoId).subgrupos
}
return []
},
showMaterial() {
return !!this.selectedCols.find(c => c === 'material')
},
showSubgrupo() {
return !!this.selectedCols.find(c => c === 'subgrupo')
},
showSaldoFisico() {
return !!this.selectedCols.find(c => c === 'saldoFisico')
},
showSaldoFinanceiro() {
return !!this.selectedCols.find(c => c === 'saldoFinanceiro')
},
itens() {
if (this.showMaterial) {
return this.resultado.porMaterial
}
else if (this.showSubgrupo) {
return this.resultado.porSubgrupo
}
return this.resultado.porGrupo
}
},
created() {
this.repositories = {
balancete: this.getConectaSusRepository('almoxarifado.balancete'),
impressao: this.getConectaSusRepository('impressao.almoxarifado'),
}
this.load()
},
data: () => ({
loaded: false,
additionals: {},
form: {},
model: {},
selectedAll: true,
indeterminate: false,
resultado: {
porMaterial: [],
porSubgrupo: [],
porGrupo: [],
totalizadores: {},
},
columns: [
{ value: 'material', text: 'Material' },
{ value: 'subgrupo', text: 'Sugrupo' },
{ value: 'saldoFisico', text: 'Saldo físico' },
{ value: 'saldoFinanceiro', text: 'Saldo financeiro' },
],
selectedCols: [
'material',
'subgrupo',
'saldoFisico',
'saldoFinanceiro',
],
}),
methods: {
...mapActions(['setLoading', 'unsetLoading']),
load() {
this.setLoading()
this.repositories.balancete.list()
.then(({ data }) => {
this.additionals = data.additionals
this.form = data.form
this.model = data.model
this.$emit('additionals-load', this.additionals)
this.loaded = true
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
search() {
this.setLoading()
this.repositories.balancete.index(this.getParams())
.then(({ data }) => this.resultado = data)
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
clear() {
this.model.periodo.start = null
this.model.periodo.end = null
this.model.grupoId = null
this.model.subgrupoId = null
this.model.origemProgramaticaId = null
this.$nextTick().then(() => this.$refs.Form.resetValidator())
},
fecharBalancete() {
this.setLoading()
this.repositories.balancete.store()
.then(() => {
this.showSaveNotification();
this.$emit('save')
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
handleSelectedColumns(selected) {
if (selected) {
this.columns
.filter(c => !this.selectedCols.includes(c.value))
.forEach(c => this.selectedCols.push(c.value))
return
}
this.selectedCols = []
},
print() {
this.setLoading()
const params = this.getParams()
params.selectedCols = this.selectedCols
this.repositories.impressao.balancete(params)
.then(response => {
const content = new Blob([response.data], { type: response.headers['content-type'] })
const url = URL.createObjectURL(content)
const link = document.createElement('a')
link.href = url
link.target = '_blank'
link.click()
link.remove()
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
getParams() {
return {
inicio: moment(this.model.periodo.start).format('DD/MM/YYYY'),
fim: moment(this.model.periodo.end).format('DD/MM/YYYY'),
grupo_id: this.model.grupoId,
subgrupo_id: this.model.subgrupoId,
origem_programatica_id: this.model.origemProgramaticaId,
}
},
},
mixins: [repository, error],
watch: {
model: {
deep: true,
handler() {
this.resultado = {
porMaterial: [],
porSubgrupo: [],
porGrupo: [],
totalizadores: {},
}
}
},
'model.grupoId': function () {
this.model.subgrupoId = null
},
'selectedCols': function (newValue) {
this.selectedAll = newValue.length > 0 && newValue.length == this.columns.length
this.indeterminate = newValue.length > 0 && newValue.length != this.columns.length
},
}
}
</script>

View File

@@ -0,0 +1,170 @@
<template>
<rkm-modal
:loading="loading"
large
ref="Modal"
title="Lotes"
>
<rkm-form ref="Form">
<rkm-table
:fields="fields"
:items="items"
hide-filters
>
<template v-slot:cell(fornecedor.nomeFantasia)="{ value }">
{{ value || '-' }}
</template>
<template v-slot:cell(lote)="{ value }">
{{ value || '-' }}
</template>
<template v-slot:cell(validadeLote)="{ value }">
{{ value || '-' }}
</template>
<template v-slot:cell(quantidadeSaida)="{ item }">
<rkm-form-input
:validations="getQuantidadeValidation(item)"
class="saida my-auto"
name="Saída"
type="number"
v-model="item.quantidadeSaida"
/>
</template>
<template #afterTable>
<div class="mt-2">
<rkm-row>
<rkm-col class="text-right">
<h6 class="font-weight-bold mb-0">Quantidade de saídas: {{ quantidadeSaidas }}</h6>
</rkm-col>
</rkm-row>
</div>
</template>
</rkm-table>
</rkm-form>
<template v-slot:footer>
<rkm-button
:disabled="!!loading"
@click="$refs.Modal.hide()"
>
Cancelar
</rkm-button>
<rkm-button
:disabled="!!loading"
@click="save"
color="primary"
>
Salvar
</rkm-button>
</template>
</rkm-modal>
</template>
<script>
// Vuex.
import { mapGetters } from 'vuex'
// Mixins.
import { object } from '@/mixins'
export default {
computed: {
...mapGetters({ loading: 'getLoading' }),
fields() {
return [
{ key: 'fornecedor.nomeFantasia', label: 'Fornecedor' },
{ key: 'lote', label: 'Lote', class: 'col-min text-center' },
{ key: 'validadeLote', label: 'Validade', class: 'col-min text-center' },
{ key: 'estoque', label: 'Estoque', class: 'col-min text-center' },
{ key: 'quantidadeSaida', label: 'Saída', class: ' col-min text-center' },
]
},
quantidadeSaidas() {
let quantidadeSaidas = 0
this.items.forEach(item => {
const quantidadeSaida = parseInt(item.quantidadeSaida)
quantidadeSaidas += isNaN(quantidadeSaida) ? 0 : quantidadeSaida
})
return quantidadeSaidas
}
},
data: () => ({
onSave: null,
items: []
}),
methods: {
open(entradaItens, onSave = null) {
this.items = this.clone(entradaItens)
this.onSave = onSave
this.$refs.Modal.show()
},
save() {
this.$refs.Form.validate().then(valid => {
if (!valid) return
const items = this.items.map(item => {
const quantidadeSaida = parseInt(item.quantidadeSaida)
item.quantidadeSaida = isNaN(quantidadeSaida) ? null : quantidadeSaida
return item
})
if (this.model) {
if (this.quantidadeSaidas != parseInt(this.model[this.quantidadeKey])) {
this.showValidationNotification(`A soma das saídas deve ser igual a quantidade total informada.
Quantidade total informada: ${this.model[this.quantidadeKey]}.`)
return;
}
this.model.entradaItens = this.clone(items)
this.model.escolheuEntradaItemManualmente = true
}
if (this.onSave !== null) {
this.onSave(items)
}
this.$refs.Modal.hide()
})
},
getQuantidadeValidation(item) {
return {
min_value: 0,
max_value: item.estoque
}
},
},
mixins: [ object ],
props: {
model: { type: Object, default: null },
quantidadeKey: { type: String, default: 'quantidade' },
}
}
</script>
<style scoped>
.saida {
width: 220px;
}
</style>

View File

@@ -0,0 +1,179 @@
<template>
<div v-if="isLoaded">
<BoxPaciente
ref="BoxPaciente"
:data="{ form, model }"
/>
<rkm-tabs
v-model="tabIndex"
>
<rkm-tab
title="Dispensação"
active
>
<TabDispensacao
ref="TabDispensacao"
:form="form"
:model="model"
@save="handleSave"
/>
</rkm-tab>
<rkm-tab
title="Histórico"
>
<TabHistorico
ref="TabHistorico"
:form="form"
/>
</rkm-tab>
</rkm-tabs>
</div>
</template>
<script>
import BoxPaciente from '@/components/almoxarifado/paciente/Index'
import { repository, object, error, stringCase, data, request, printAlmoxarifado } from '@/mixins'
import TabDispensacao from './tabs/Dispensacao'
import TabHistorico from './tabs/Historico'
import { mapActions } from 'vuex'
import moment from 'moment'
export default {
components: {
BoxPaciente,
TabDispensacao,
TabHistorico
},
created() {
this.repositories = {
dispensacaoMateriais: this.getConectaSusRepository('almoxarifado.dispensacaoMateriais'),
}
this.load()
},
data: () => ({
tabIndex: 0,
isLoaded: false,
form: null,
model: null
}),
methods: {
...mapActions(['setLoading', 'unsetLoading', 'unsetShowExitFormConfirmation']),
load() {
if(this.pacienteId) {
this.setLoading()
this.repositories.dispensacaoMateriais.create(this.pacienteId)
.then(({ data }) => {
this.setShowDetails(data.form.historico)
this.form = this.clone(data.form)
this.model = this.clone(data.model)
this.isLoaded = true
this.$emit('paciente', this.form.paciente)
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
}
},
setShowDetails(dispensacoes) {
dispensacoes.forEach(dispensacao => {
dispensacao._showDetails = true
})
},
save() {
this.$refs.TabDispensacao.save()
},
hasModelChanged() {
return this.form.historico.some(h => h.items.some(i => !i.estornoSalvo && !!i.estornado)) || this.model.items.length > 0
},
handleSave() {
if(!this.hasModelChanged()) {
this.showAlert('Não é possível finalizar, pois nenhuma ação foi realizada.', 'Atenção')
return
}
this.setLoading()
this.repositories.dispensacaoMateriais.store(this.prepareData())
.then(({ data }) => {
if(data.id) {
this.showConfirmation("Deseja imprimir o recido do material?",
async () => {
this.$nextTick()
.then(() => this.handlePrint(data))
.then(() => this.afterSave())
}
)
} else {
this.$nextTick().then(() => this.afterSave())
}
})
.catch(this.handleError)
.finally(() => this.unsetLoading())
},
prepareData() {
let estornados = []
this.form.historico.forEach(dispensacao => dispensacao.items.forEach(item => {
if(!item.estornoSalvo && !!item.estornado) {
estornados.push(item.id)
}
}))
return {
paciente_id: this.pacienteId,
usuario_id: this.model.medicoSolicitante ? this.model.medicoSolicitante.id : null,
data_dispensacao: this.model.dataDispensacao ? moment(this.model.dataDispensacao, 'DD/MM/YYYY').format('YYYY-MM-DD') : null,
observacao: this.model.observacao,
items: this.model.items.map(item => {
return {
material_id: item.material.id,
quantidade: item.quantidade,
dispensado_em: moment(item.dispensadoEm, 'DD/MM/YYYY').format('YYYY-MM-DD'),
observacao: item.observacao
}
}),
estornados: estornados
}
},
afterSave() {
this.$nextTick().then(() => {
this.showSaveNotification(
null,
async () => {
this.unsetShowExitFormConfirmation()
this.$router.push({ name: 'almoxarifado-dispensacao-materiais-index' })
})
})
},
handlePrint(data) {
let printData = {
medicoNome: this.model.medicoSolicitante.cidadaoNome,
materiais: this.model.items,
pacienteNome: this.form.paciente.cidadao.nome,
pacienteNomeSocial: this.form.paciente.cidadao.nomeSocial,
prontuario: this.form.paciente.prontuario,
dataNascimento: this.form.paciente.cidadao.dataNascimento.split("-").reverse().join("/"),
dataAtual: this.form.dataAtual,
dataEntrega: data.dispensadoEm,
horaAtendimento: data.horaAtendimento,
logotipo: this.form.cliente.logotipo,
nomePrefeitura: this.form.cliente.nome,
ramoAtividade: this.form.cliente.sistema.ramoAtividade.nome,
sistemaNome: this.form.cliente.sistema.nome,
sistemaVersao: this.form.cliente.sistema.versao,
usuario: this.form.usuario,
unidade: this.form.unidade,
telefone: this.form.telefone,
}
this.print().dispensacao(printData)
},
},
mounted() {
this.tabRefs = [
this.$refs.TabDispensacao,
this.$refs.TabHistorico,
]
},
mixins: [ repository, object, error, stringCase, data, request, printAlmoxarifado ],
props: {
pacienteId: { type: Number, required: true }
}
}
</script>

Some files were not shown because too many files have changed in this diff Show More