Skip to content

Instantly share code, notes, and snippets.

@iErik
Created June 4, 2018 20:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iErik/2863f6b1952c75a85ff1404b4d1f733e to your computer and use it in GitHub Desktop.
Save iErik/2863f6b1952c75a85ff1404b4d1f733e to your computer and use it in GitHub Desktop.
<template>
<c-card
:title="accountName ? `Exclusivo de ${accountName}` : ''">
<c-toggle
class="input"
v-if="!data.id"
:labels="{ checked: 'Ativa', unchecked: 'Inativa'}"
:value="data.status"
height="30"
width="80"
sync
@input="v => data.status = v"
slot="action" />
<c-form
autocomplete="new-password"
id="perk"
class="new-perk-form"
@submit.prevent>
<c-title size="3">Informações</c-title>
<div class="half -left">
<!-- name required -->
<c-input
required
name="nome"
v-model="data.name"
v-validate="'required'"
label="Nome do parceiro"
:feedback-show="errors.has('nome')"
:feedback-message="errors.first('nome')" />
<!-- colors -->
<c-input
type="color"
name="cor"
required
v-model="data.colors.primary"
v-validate="'required'"
:feedback-show="errors.has('cor')"
:feedback-message="errors.first('cor')"
label="Cor" />
<!-- logo -->
<div class="inline">
<c-input
type="file"
file-accept="image/*"
:file-label="computedFileLabel"
v-model="data.logo"
@change.native="e => previewLogo(e, 'logoSrc')"
label="Logo (5:4, mín. de 100:80)"
/>
<c-button
:disabled="!logoSrc"
:popover-label="`<img style='height: 100%; width: 100px; object-fit: contain' src='${logoSrc}' />`"
alternative
type="button"
size="lg"
icon="file-preview">
Preview
</c-button>
</div>
<!-- logo -->
<div class="inline">
<c-input
type="file"
file-accept="image/png"
:file-label="computedNegativeFileLabel"
v-model="data.logo_negative"
@change.native="e => previewLogo(e, 'negativeLogoSrc')"
label="Logo negativo (5:4, mín. de 100:80, PNG transparente)"
/>
<c-button
:disabled="!negativeLogoSrc"
:popover-label="`
<div style='background-color: ${data.colors.primary}'>
<img
style='
background-color: transparent;
height: 100%; width: 100px;
object-fit: contain;'
src='${negativeLogoSrc}'
/>
</div>
`"
alternative
type="button"
size="lg"
icon="file-preview">
Preview
</c-button>
</div>
<c-input
name="cnpj"
v-model="data.cnpj"
:value="data.cnpj"
@input="cnpj => data.cnpj = cnpj.replace(/\D/g, '')"
:mask="['##.###.###/####-##']"
label="CNPJ"
/>
</div>
<div class="half">
<!-- is_main -->
<c-toggle
form-label="Principal"
:labels="{ checked: 'É principal', unchecked: 'Não é principal'}"
:value="!!data.is_main"
@input="v => data.is_main = +v"
width="140" />
<!-- description required -->
<c-input
text-area
maxlength="200"
required
v-model="data.description"
name="sobre"
v-validate="'required|max:200'"
:feedback-show="errors.has('sobre')"
:feedback-message="errors.first('sobre')"
:label="`Sobre o parceiro (${computedDescriptionLength})`" />
<!-- observation -->
<c-input
text-area
maxlength="140"
name="observacoes"
v-validate="'max:140'"
rows="4"
:feedback-show="errors.has('observacoes')"
:feedback-message="errors.first('observacoes')"
v-model="data.observation"
:label="`Observações (${computedObservationLength})`" />
<!-- categories (Array de ids) -->
<c-select
:options="availableCategories"
:multiple="true"
:disabled="isFetchingCategories"
label="label"
track-by="value"
required
:feedback-show="!data.categories.length"
:feedback-message="'O campo categorias é obrigatório.'"
:value="computedCategories"
@input="v => updateCategories(v)"
form-label="Categorias" />
</div>
<c-title size="3">Contato</c-title>
<div class="half -left">
<!-- contact.name -->
<c-input
label="Nome"
required
name="nomeContato"
v-validate="'required'"
:feedback-show="errors.has('nomeContato')"
:feedback-message="errors.first('nomeContato')"
v-model="data.contact.name" />
<!-- contact.phone -->
<c-input
label="Telefone"
placeholder="(DDD) + Número"
:mask="['(##) ####-####', '(##) ####-#####']"
v-model="data.contact.phone" />
<!-- contact.email -->
<c-input
label="Email"
type="email"
v-validate="'email'"
name="emailContato"
:feedback-show="errors.has('emailContato')"
:feedback-message="errors.first('emailContato')"
v-model="data.contact.email" />
</div>
<!-- contract -->
<div class="half">
<c-input
type="file"
v-model="data.contract"
file-label="Escolher arquivo"
label="Importar contrato" />
</div>
</c-form>
<c-form @submit.prevent class="new-perk-form">
<!-- address (Array de objetos) -->
<c-title size="3">Endereço</c-title>
<section class="manual-address">
<input type="hidden" :value="newAddress.id" />
<!-- code required -->
<c-input
label="CEP"
:mask="['#####-###']"
type="text"
name="cep"
@keyup="fillAddress"
v-validate="newAddress.code ? 'required' : ''"
:required="!!newAddress.code"
:feedback-show="errors.has('cep')"
:feedback-message="errors.first('cep')"
v-model="newAddress.code" />
<div class="inline">
<!-- street required -->
<c-input
label="Endereço"
type="text"
name="endereço"
v-validate="newAddress.code ? 'required' : ''"
:required="!!newAddress.code"
:feedback-show="errors.has('endereço')"
:feedback-message="errors.first('endereço')"
v-model="newAddress.street" />
<!-- number required -->
<c-input
label="Número"
type="text"
name="numero"
v-validate="newAddress.code ? 'required' : ''"
:required="!!newAddress.code"
:feedback-show="errors.has('numero')"
:feedback-message="errors.first('numero')"
v-model="newAddress.number" />
</div>
<div class="inline">
<!-- complement -->
<c-input
label="Complemento"
type="text"
v-model="newAddress.complement" />
<!-- lat -->
<c-input
label="Latitude"
type="text"
v-model="newAddress.latitude" />
<!-- long -->
<c-input
label="Longitude"
type="text"
v-model="newAddress.longitude" />
</div>
<!-- neighborhood required -->
<c-input
label="Bairro"
type="text"
name="bairro"
v-validate="newAddress.code ? 'required' : ''"
:required="!!newAddress.code"
:feedback-show="errors.has('bairro')"
:feedback-message="errors.first('bairro')"
v-model="newAddress.neighborhood" />
<div class="inline">
<!-- state required -->
<c-input
label="Estado"
type="text"
name="estado"
v-validate="newAddress.code ? 'required' : ''"
:required="!!newAddress.code"
:feedback-show="errors.has('estado')"
:feedback-message="errors.first('estado')"
v-model="newAddress.state" />
<!-- city -->
<c-input
label="Cidade"
type="text"
name="cidade"
v-validate="newAddress.code ? 'required' : ''"
:required="!!newAddress.code"
:feedback-show="errors.has('cidade')"
:feedback-message="errors.first('cidade')"
v-model="newAddress.city" />
</div>
<c-button
icon="plus"
:disabled="isNewAddressInvalid"
@click="saveAddress">
<template v-if="data.address.length">Adicionar à lista ({{data.address.length}})</template>
<template v-else>Adicionar outro</template>
</c-button>
</section>
<c-table v-if="data.address.length"
@select="v => selectedAddress = v"
:cols="addressCols"
:rows="data.address">
<template
slot="row"
slot-scope="{ row, index }">
<td class="c-table-cell -content c-table-text">{{ row.street }}</td>
<td class="c-table-cell -content c-table-text">{{ row.number }}</td>
<td class="c-table-cell -content c-table-text">{{ row.complement }}</td>
<td class="c-table-cell -content c-table-text">{{ row.neighborhood }}</td>
<td class="c-table-cell -content c-table-text">{{ row.state }}</td>
<td class="c-table-cell -content c-table-text">{{ row.city }}</td>
<td class="c-table-cell -content c-table-text">
<c-button alternative size="lg" icon="pencil"
@click="editAddress(row, index)"
popover-label="Editar" />
<c-button alternative size="lg" icon="trash"
@click="removeAddress(index)"
popover-label="Apagar" />
</td>
</template>
</c-table>
<c-button
alternative
@click="$router.push({ name: 'Parceiro', params: $route.params })"
formname="perk"
slot="actions"
type="reset">
Cancelar
</c-button>
<c-button
slot="actions"
:disabled="!data.categories.length || isProcessing || isFormInvalid"
@click="emitSubmit"
formname="perk"
icon="floppy-disk"
type="button">
Salvar
</c-button>
</c-form>
</c-card>
</template>
<script>
import { validate } from 'vue-convenia-util'
import * as types from '@store/types'
import cep from 'cep-promise'
const ADDRESS_TEMPLATE = {
id: '',
code: '',
street: '',
number: '',
complement: '',
latitude: '',
longitude: '',
neighborhood: '',
state: '',
city: ''
}
const PERK_TEMPLATE = {
id: null,
name: '',
is_main: false,
logo: '',
cnpj: '',
logo_negative: '',
description: '',
categories: [],
notify: false,
colors: {
primary: ''
},
contact: {
name: '',
phone: '',
email: ''
},
address: [],
status: true
}
const checkFields = (names) => function () {
const checkField = (name) => this.fields[name] && this.fields[name].invalid
const checked = names.some(checkField)
return checked
}
export default {
props: {
currentPerk: {
type: Object,
default: () => {}
},
availableCategories: {
type: Array,
default: () => []
},
isFetchingCategories: Boolean,
isProcessing: Boolean,
accountName: String
},
data () {
return {
logoSrc: '',
negativeLogoSrc: '',
newAddress: { ...ADDRESS_TEMPLATE },
selectedAddress: '',
addressCols: [
{ label: 'Endereço' },
{ label: 'Número' },
{ label: 'Comp.' },
{ label: 'Bairro' },
{ label: 'Estado' },
{ label: 'Cidade' },
{ label: 'Ações' }
],
data: this.currentPerk && Object.keys(this.currentPerk).length ? {
...PERK_TEMPLATE,
...this.currentPerk,
colors: {
...PERK_TEMPLATE.colors,
...this.currentPerk.colors
},
contact: {
...PERK_TEMPLATE.contact,
...this.currentPerk.contact
},
address: [
...PERK_TEMPLATE.address,
...this.currentPerk.address
],
categories: [
...PERK_TEMPLATE.categories,
...this.currentPerk.categories
]
} : {
...PERK_TEMPLATE,
colors: { ...PERK_TEMPLATE.colors },
contact: { ...PERK_TEMPLATE.contact },
address: [ ...PERK_TEMPLATE.address ],
categories: [ ...PERK_TEMPLATE.categories ]
}
}
},
computed: {
computedDescriptionLength () {
return 200 - (this.data.description ? this.data.description.length : 0)
},
computedObservationLength () {
return 140 - (this.data.observation ? this.data.observation.length : 0)
},
computedCategories () {
return this.data.categories.map(it => {
return {
label: it.name,
value: it.id
}
})
},
computedFileLabel () {
if (validate.is(this.data.logo, 'String') && this.data.logo.length > 1) {
this.logoSrc = this.data.logo
return 'Trocar foto'
}
return 'Escolher foto'
},
computedNegativeFileLabel () {
if (validate.is(this.data.logo_negative, 'String') && this.data.logo_negative.length > 1) {
this.negativeLogoSrc = this.data.logo_negative
return 'Trocar foto'
}
return 'Escolher foto'
},
isInformationInvalid: checkFields(['nome', 'sobre', 'observacoes', 'cor']),
isContactInvalid: checkFields(['nomeContato', 'emailContato']),
isNewAddressInvalid () {
// return this.hasAddress ? false : ['cep', 'endereço', 'numero', 'bairro', 'estado', 'cidade'].some(field => this.fields[field].invalid)
return false
},
isFormInvalid () {
return this.isInformationInvalid || this.isContactInvalid || this.isNewAddressInvalid
},
hasAddress () {
return this.data.address.length >= 1
}
},
methods: {
updateCategories (categories) {
this.data.categories = categories.map(it => {
return {
name: it.label,
id: it.value
}
})
},
async emitSubmit () {
const result = await this.$validator.validateAll()
const action = this.$route.params.perkId ? 'update' : 'create'
const isMain = this.data.is_main ? 1 : 0
const status = this.data.status ? 1 : 0
const payload = { ...this.data, is_main: isMain, status }
if (action === 'update' && !(payload.logo instanceof File)) {
delete payload.logo
}
if (action === 'update' && !(payload.logo_negative instanceof File)) {
delete payload.logo_negative
}
payload.categories = payload.categories.map(it => it.id)
if (result) {
if (this.newAddress.code && !this.isNewAddressInvalid) {
payload.address.push(this.newAddress)
}
this.$emit(action, payload)
} else this.$store.dispatch(types.NOTIFICATIONS_INCLUDE, Error('Verifique os campos preenchidos.'))
},
previewLogo (event, src) {
const input = event.target
if (input.files && input.files[0]) {
const reader = new FileReader()
reader.onload = e => {
this[src] = e.target.result
}
reader.readAsDataURL(input.files[0])
}
},
async fillAddress () {
if (this.newAddress.code.length <= 8) return
const response = await cep(this.newAddress.code.replace('-', ''))
this.newAddress = { ...this.newAddress, ...response }
this.$nextTick(() => {
['endereço', 'bairro', 'estado', 'cidade'].forEach(field => {
this.$validator.validate(field)
})
})
},
editAddress (data, index) {
this.newAddress = { ...data }
this.data.address.splice(index, 1)
this.$nextTick(() => {
['cep', 'endereço', 'numero', 'bairro', 'estado', 'cidade'].forEach(field => {
this.$validator.validate(field)
})
})
},
removeAddress (index) {
this.data.address.splice(index, 1)
this.clearAddressValidator()
},
saveAddress () {
this.data.address.push({ ...this.newAddress })
this.clearAddressValidator()
this.newAddress = { ...ADDRESS_TEMPLATE }
},
clearAddressValidator () {
['cep', 'endereço', 'numero', 'bairro', 'estado', 'cidade'].forEach(key => {
this.$validator.flag(key, {
dirty: false,
invalid: true,
pending: false,
pristine: true,
required: true,
touched: false,
untouched: true,
valid: false,
validated: false
})
})
}
}
}
</script>
<style lang="scss">
.new-perk-form {
display: flex;
flex-flow: row wrap;
& > .half {
flex: 0 50%;
padding-left: inner-base();
&.-left {
padding-left: 0;
padding-right: inner-base();
}
& > .c-input,
& > .c-toggle,
& > .c-select,
& > .inline {
margin-bottom: inner-base();
}
& > .c-select + .c-input {
position: relative;
top: -2px;
}
@include medium-down {
flex: 0 100%;
padding-left: 0;
&.-left {
padding-right: 0;
}
}
& > .inline {
display: flex;
flex-flow: row wrap;
align-items: flex-end;
.c-input {
flex: 1;
.input-label {
overflow: hidden;
}
}
.c-button {
flex: 0 100px;
margin-left: inner-base();
& > .c-popover {
z-index: 3;
}
}
}
}
& > .c-title,
& > .manual-address,
& > .actions {
flex: 0 100%;
width: 100%;
}
}
.manual-address {
& > .inline {
display: flex;
flex-flow: row wrap;
& > .c-input {
flex: 1;
margin-left: inner-base();
&:first-child { margin-left: 0; }
}
}
& > .inline,
& > .c-input {
margin-bottom: inner-base();
}
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment