|
@@ -7,166 +7,342 @@
|
|
|
<DefaultDialogHeader title="Cadastrar Aluno" @close="onDialogCancel" />
|
|
<DefaultDialogHeader title="Cadastrar Aluno" @close="onDialogCancel" />
|
|
|
|
|
|
|
|
<DefaultForm ref="formRef" @submit="onOKClick">
|
|
<DefaultForm ref="formRef" @submit="onOKClick">
|
|
|
- <q-scroll-area class="dialog-form-scroll">
|
|
|
|
|
- <q-card-section class="q-pt-none">
|
|
|
|
|
- <div class="flex justify-center q-mb-md">
|
|
|
|
|
- <AvatarImageComponent
|
|
|
|
|
- ref="avatarRef"
|
|
|
|
|
- @update:file="onAvatarChange"
|
|
|
|
|
- />
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <div class="row q-col-gutter-sm">
|
|
|
|
|
- <DefaultInput
|
|
|
|
|
- v-model="form.name"
|
|
|
|
|
- :error="!!validationErrors.name"
|
|
|
|
|
- :error-message="validationErrors.name"
|
|
|
|
|
- label="Nome do Aluno"
|
|
|
|
|
- class="col-6"
|
|
|
|
|
- :rules="[inputRules.required]"
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
- <DefaultInputDatePicker
|
|
|
|
|
- v-model="form.birthdate"
|
|
|
|
|
- :error="!!validationErrors.birth_date"
|
|
|
|
|
- :error-message="validationErrors.birth_date"
|
|
|
|
|
- label="Data de Nascimento"
|
|
|
|
|
- class="col-6"
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
- <DefaultInput
|
|
|
|
|
- v-model="form.cpf"
|
|
|
|
|
- :error="!!validationErrors.document_number"
|
|
|
|
|
- :error-message="validationErrors.document_number"
|
|
|
|
|
- label="CPF"
|
|
|
|
|
- class="col-6"
|
|
|
|
|
- :mask="masks.Brasil.cpf"
|
|
|
|
|
- :rules="[inputRules.cpf]"
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
- <DefaultSelect
|
|
|
|
|
- v-model="form.gender"
|
|
|
|
|
- :error="!!validationErrors.gender"
|
|
|
|
|
- :error-message="validationErrors.gender"
|
|
|
|
|
- label="Gênero"
|
|
|
|
|
- class="col-6"
|
|
|
|
|
- emit-value
|
|
|
|
|
- map-options
|
|
|
|
|
- :options="genderOptions"
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
- <DefaultInput
|
|
|
|
|
- v-model="form.email"
|
|
|
|
|
- :error="!!validationErrors.email"
|
|
|
|
|
- :error-message="validationErrors.email"
|
|
|
|
|
- label="E-mail"
|
|
|
|
|
- class="col-6"
|
|
|
|
|
- type="email"
|
|
|
|
|
- :rules="[inputRules.email]"
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
- <DefaultInput
|
|
|
|
|
- v-model="form.phone"
|
|
|
|
|
- :error="!!validationErrors.phone"
|
|
|
|
|
- :error-message="validationErrors.phone"
|
|
|
|
|
- label="Celular com DDD"
|
|
|
|
|
- class="col-6"
|
|
|
|
|
- :mask="masks.Brasil.celular"
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
- <DefaultCepInput
|
|
|
|
|
- v-model="form.cep"
|
|
|
|
|
- :error="!!validationErrors.postal_code"
|
|
|
|
|
- :error-message="validationErrors.postal_code"
|
|
|
|
|
- class="col-4"
|
|
|
|
|
- :rules="[inputRules.cep]"
|
|
|
|
|
- @rua="(v) => (form.address = v)"
|
|
|
|
|
- @bairro="(v) => (form.neighborhood = v)"
|
|
|
|
|
- @uf="(v) => stateSelectRef?.selectStateByCode(v)"
|
|
|
|
|
- @cidade="(v) => citySelectRef?.selectCityByName(v)"
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
- <DefaultInput
|
|
|
|
|
- v-model="form.address"
|
|
|
|
|
- :error="!!validationErrors.street"
|
|
|
|
|
- :error-message="validationErrors.street"
|
|
|
|
|
- label="Endereço"
|
|
|
|
|
- class="col-5"
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
- <DefaultInput
|
|
|
|
|
- v-model="form.address_number"
|
|
|
|
|
- :error="!!validationErrors.address_number"
|
|
|
|
|
- :error-message="validationErrors.address_number"
|
|
|
|
|
- label="Número"
|
|
|
|
|
- class="col-3"
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
- <DefaultInput
|
|
|
|
|
- v-model="form.neighborhood"
|
|
|
|
|
- :error="!!validationErrors.neighborhood"
|
|
|
|
|
- :error-message="validationErrors.neighborhood"
|
|
|
|
|
- label="Bairro"
|
|
|
|
|
- class="col-4"
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
- <CitySelect
|
|
|
|
|
- ref="citySelectRef"
|
|
|
|
|
- v-model="selectedCity"
|
|
|
|
|
- :error="!!validationErrors.city_id"
|
|
|
|
|
- :error-message="validationErrors.city_id"
|
|
|
|
|
- label="Cidade"
|
|
|
|
|
- class="col-4"
|
|
|
|
|
- :state="selectedState"
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
- <StateSelect
|
|
|
|
|
- ref="stateSelectRef"
|
|
|
|
|
- v-model="selectedState"
|
|
|
|
|
- :error="!!validationErrors.state_id"
|
|
|
|
|
- :error-message="validationErrors.state_id"
|
|
|
|
|
- label="Estado"
|
|
|
|
|
- class="col-4"
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
- <DefaultInput
|
|
|
|
|
- v-model="form.complement"
|
|
|
|
|
- :error="!!validationErrors.complement"
|
|
|
|
|
- :error-message="validationErrors.complement"
|
|
|
|
|
- label="Complemento"
|
|
|
|
|
- class="col-6"
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
- <DefaultInput
|
|
|
|
|
- v-model="form.payer"
|
|
|
|
|
- label="Pagador"
|
|
|
|
|
- class="col-6"
|
|
|
|
|
- :error="!!validationErrors.payer_name"
|
|
|
|
|
- :error-message="validationErrors.payer_name"
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
- <DefaultSelect
|
|
|
|
|
- v-model="form.how_found"
|
|
|
|
|
- :error="!!validationErrors.how_did_you_know_us"
|
|
|
|
|
- :error-message="validationErrors.how_did_you_know_us"
|
|
|
|
|
- label="Como nos conheceu?"
|
|
|
|
|
- class="col-12"
|
|
|
|
|
- emit-value
|
|
|
|
|
- map-options
|
|
|
|
|
- :options="howFoundOptions"
|
|
|
|
|
|
|
+ <q-scroll-area ref="scrollAreaRef" class="dialog-form-scroll">
|
|
|
|
|
+ <q-card-section class="q-pt-sm">
|
|
|
|
|
+ <CustomTabComponent
|
|
|
|
|
+ :active-tab="activeTab"
|
|
|
|
|
+ :tabs="tabs"
|
|
|
|
|
+ class="q-mb-md"
|
|
|
|
|
+ @update:active-tab="handleTabChange"
|
|
|
/>
|
|
/>
|
|
|
|
|
|
|
|
- <DefaultInput
|
|
|
|
|
- v-model="form.notes"
|
|
|
|
|
- :error="!!validationErrors.notes"
|
|
|
|
|
- :error-message="validationErrors.notes"
|
|
|
|
|
- label="Observações"
|
|
|
|
|
- class="col-12"
|
|
|
|
|
- type="textarea"
|
|
|
|
|
- :input-style="{ minHeight: '120px' }"
|
|
|
|
|
- autogrow
|
|
|
|
|
- />
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <div v-show="activeTab === 'student'" ref="studentTabRef">
|
|
|
|
|
+ <q-banner
|
|
|
|
|
+ v-if="validationErrors.registration_draft_token"
|
|
|
|
|
+ rounded
|
|
|
|
|
+ class="bg-red-1 text-negative q-mb-md"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ validationErrors.registration_draft_token }}
|
|
|
|
|
+ </q-banner>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="flex justify-center q-mb-md">
|
|
|
|
|
+ <AvatarImageComponent
|
|
|
|
|
+ ref="avatarRef"
|
|
|
|
|
+ @update:file="onAvatarChange"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="row q-col-gutter-sm">
|
|
|
|
|
+ <DefaultInput
|
|
|
|
|
+ v-model="form.name"
|
|
|
|
|
+ :error="!!validationErrors.name"
|
|
|
|
|
+ :error-message="validationErrors.name"
|
|
|
|
|
+ label="Nome do Aluno"
|
|
|
|
|
+ class="col-6"
|
|
|
|
|
+ :rules="[inputRules.required]"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultInputDatePicker
|
|
|
|
|
+ v-model="form.birthdate"
|
|
|
|
|
+ :error="!!validationErrors.birth_date"
|
|
|
|
|
+ :error-message="validationErrors.birth_date"
|
|
|
|
|
+ label="Data de Nascimento"
|
|
|
|
|
+ class="col-6"
|
|
|
|
|
+ :rules="[inputRules.required]"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultInput
|
|
|
|
|
+ v-model="form.cpf"
|
|
|
|
|
+ :error="!!validationErrors.document_number"
|
|
|
|
|
+ :error-message="validationErrors.document_number"
|
|
|
|
|
+ label="CPF"
|
|
|
|
|
+ class="col-6"
|
|
|
|
|
+ :mask="masks.Brasil.cpf"
|
|
|
|
|
+ :rules="[inputRules.cpf]"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultSelect
|
|
|
|
|
+ v-model="form.gender"
|
|
|
|
|
+ :error="!!validationErrors.gender"
|
|
|
|
|
+ :error-message="validationErrors.gender"
|
|
|
|
|
+ label="Gênero"
|
|
|
|
|
+ class="col-6"
|
|
|
|
|
+ emit-value
|
|
|
|
|
+ map-options
|
|
|
|
|
+ :options="genderOptions"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultInput
|
|
|
|
|
+ v-model="form.email"
|
|
|
|
|
+ :error="!!validationErrors.email"
|
|
|
|
|
+ :error-message="validationErrors.email"
|
|
|
|
|
+ label="E-mail"
|
|
|
|
|
+ class="col-6"
|
|
|
|
|
+ type="email"
|
|
|
|
|
+ :rules="[inputRules.email]"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultInput
|
|
|
|
|
+ v-model="form.phone"
|
|
|
|
|
+ :error="!!validationErrors.phone"
|
|
|
|
|
+ :error-message="validationErrors.phone"
|
|
|
|
|
+ label="Celular com DDD"
|
|
|
|
|
+ class="col-6"
|
|
|
|
|
+ :mask="masks.Brasil.celular"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultCepInput
|
|
|
|
|
+ v-model="form.cep"
|
|
|
|
|
+ :error="!!validationErrors.postal_code"
|
|
|
|
|
+ :error-message="validationErrors.postal_code"
|
|
|
|
|
+ class="col-4"
|
|
|
|
|
+ :rules="[inputRules.cep]"
|
|
|
|
|
+ @rua="(v) => (form.address = v)"
|
|
|
|
|
+ @bairro="(v) => (form.neighborhood = v)"
|
|
|
|
|
+ @uf="(v) => stateSelectRef?.selectStateByCode(v)"
|
|
|
|
|
+ @cidade="(v) => citySelectRef?.selectCityByName(v)"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultInput
|
|
|
|
|
+ v-model="form.address"
|
|
|
|
|
+ :error="!!validationErrors.street"
|
|
|
|
|
+ :error-message="validationErrors.street"
|
|
|
|
|
+ label="Endereço"
|
|
|
|
|
+ class="col-5"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultInput
|
|
|
|
|
+ v-model="form.address_number"
|
|
|
|
|
+ :error="!!validationErrors.address_number"
|
|
|
|
|
+ :error-message="validationErrors.address_number"
|
|
|
|
|
+ label="Número"
|
|
|
|
|
+ class="col-3"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultInput
|
|
|
|
|
+ v-model="form.neighborhood"
|
|
|
|
|
+ :error="!!validationErrors.neighborhood"
|
|
|
|
|
+ :error-message="validationErrors.neighborhood"
|
|
|
|
|
+ label="Bairro"
|
|
|
|
|
+ class="col-4"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <CitySelect
|
|
|
|
|
+ ref="citySelectRef"
|
|
|
|
|
+ v-model="selectedCity"
|
|
|
|
|
+ :error="!!validationErrors.city_id"
|
|
|
|
|
+ :error-message="validationErrors.city_id"
|
|
|
|
|
+ label="Cidade"
|
|
|
|
|
+ class="col-4"
|
|
|
|
|
+ :state="selectedState"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <StateSelect
|
|
|
|
|
+ ref="stateSelectRef"
|
|
|
|
|
+ v-model="selectedState"
|
|
|
|
|
+ :error="!!validationErrors.state_id"
|
|
|
|
|
+ :error-message="validationErrors.state_id"
|
|
|
|
|
+ label="Estado"
|
|
|
|
|
+ class="col-4"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultInput
|
|
|
|
|
+ v-model="form.complement"
|
|
|
|
|
+ :error="!!validationErrors.complement"
|
|
|
|
|
+ :error-message="validationErrors.complement"
|
|
|
|
|
+ label="Complemento"
|
|
|
|
|
+ class="col-6"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultInput
|
|
|
|
|
+ v-model="form.payer"
|
|
|
|
|
+ :error="!!validationErrors.payer_name"
|
|
|
|
|
+ :error-message="validationErrors.payer_name"
|
|
|
|
|
+ label="Pagador"
|
|
|
|
|
+ class="col-6"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultSelect
|
|
|
|
|
+ v-model="form.how_found"
|
|
|
|
|
+ :error="!!validationErrors.how_did_you_know_us"
|
|
|
|
|
+ :error-message="validationErrors.how_did_you_know_us"
|
|
|
|
|
+ label="Como nos conheceu?"
|
|
|
|
|
+ class="col-12"
|
|
|
|
|
+ emit-value
|
|
|
|
|
+ map-options
|
|
|
|
|
+ :options="howFoundOptions"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultInput
|
|
|
|
|
+ v-model="form.notes"
|
|
|
|
|
+ :error="!!validationErrors.notes"
|
|
|
|
|
+ :error-message="validationErrors.notes"
|
|
|
|
|
+ label="Observações"
|
|
|
|
|
+ class="col-12"
|
|
|
|
|
+ type="textarea"
|
|
|
|
|
+ :input-style="{ minHeight: '120px' }"
|
|
|
|
|
+ autogrow
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-if="isStudentUnderage"
|
|
|
|
|
+ v-show="activeTab === 'responsible'"
|
|
|
|
|
+ ref="responsibleTabRef"
|
|
|
|
|
+ >
|
|
|
|
|
+ <q-banner rounded class="bg-orange-1 text-orange-10 q-mb-md">
|
|
|
|
|
+ O aluno é menor de 18 anos. Cadastre um responsável para
|
|
|
|
|
+ concluir.
|
|
|
|
|
+ </q-banner>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="row q-col-gutter-sm">
|
|
|
|
|
+ <DefaultInput
|
|
|
|
|
+ v-model="responsibleForm.name"
|
|
|
|
|
+ :error="!!validationErrors['responsible.name']"
|
|
|
|
|
+ :error-message="validationErrors['responsible.name']"
|
|
|
|
|
+ label="Nome"
|
|
|
|
|
+ class="col-6"
|
|
|
|
|
+ :rules="
|
|
|
|
|
+ activeTab === 'responsible' ? [inputRules.required] : []
|
|
|
|
|
+ "
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultInput
|
|
|
|
|
+ v-model="responsibleForm.degree"
|
|
|
|
|
+ :error="!!validationErrors['responsible.degree']"
|
|
|
|
|
+ :error-message="validationErrors['responsible.degree']"
|
|
|
|
|
+ label="Grau de Parentesco"
|
|
|
|
|
+ class="col-6"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultInputDatePicker
|
|
|
|
|
+ v-model="responsibleForm.birth_date"
|
|
|
|
|
+ :error="!!validationErrors['responsible.birth_date']"
|
|
|
|
|
+ :error-message="validationErrors['responsible.birth_date']"
|
|
|
|
|
+ label="Data de Nascimento"
|
|
|
|
|
+ class="col-4"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultInput
|
|
|
|
|
+ v-model="responsibleForm.cpf"
|
|
|
|
|
+ :error="!!validationErrors['responsible.cpf']"
|
|
|
|
|
+ :error-message="validationErrors['responsible.cpf']"
|
|
|
|
|
+ label="CPF"
|
|
|
|
|
+ class="col-4"
|
|
|
|
|
+ :mask="masks.Brasil.cpf"
|
|
|
|
|
+ :rules="
|
|
|
|
|
+ activeTab === 'responsible'
|
|
|
|
|
+ ? [inputRules.required, inputRules.cpf]
|
|
|
|
|
+ : []
|
|
|
|
|
+ "
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultSelect
|
|
|
|
|
+ v-model="responsibleForm.gender"
|
|
|
|
|
+ :error="!!validationErrors['responsible.gender']"
|
|
|
|
|
+ :error-message="validationErrors['responsible.gender']"
|
|
|
|
|
+ label="Gênero"
|
|
|
|
|
+ class="col-4"
|
|
|
|
|
+ emit-value
|
|
|
|
|
+ map-options
|
|
|
|
|
+ :options="genderOptions"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultInput
|
|
|
|
|
+ v-model="responsibleForm.email"
|
|
|
|
|
+ :error="!!validationErrors['responsible.email']"
|
|
|
|
|
+ :error-message="validationErrors['responsible.email']"
|
|
|
|
|
+ label="E-mail"
|
|
|
|
|
+ class="col-6"
|
|
|
|
|
+ type="email"
|
|
|
|
|
+ :rules="[inputRules.email]"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultInput
|
|
|
|
|
+ v-model="responsibleForm.phone"
|
|
|
|
|
+ :error="!!validationErrors['responsible.phone']"
|
|
|
|
|
+ :error-message="validationErrors['responsible.phone']"
|
|
|
|
|
+ label="Telefone"
|
|
|
|
|
+ class="col-6"
|
|
|
|
|
+ :mask="masks.Brasil.celular"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultCepInput
|
|
|
|
|
+ v-model="responsibleForm.postal_code"
|
|
|
|
|
+ :error="!!validationErrors['responsible.postal_code']"
|
|
|
|
|
+ :error-message="validationErrors['responsible.postal_code']"
|
|
|
|
|
+ class="col-4"
|
|
|
|
|
+ :rules="[inputRules.cep]"
|
|
|
|
|
+ @rua="(v) => (responsibleForm.street = v)"
|
|
|
|
|
+ @bairro="(v) => (responsibleForm.neighborhood = v)"
|
|
|
|
|
+ @uf="(v) => responsibleStateSelectRef?.selectStateByCode(v)"
|
|
|
|
|
+ @cidade="(v) => responsibleCitySelectRef?.selectCityByName(v)"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultInput
|
|
|
|
|
+ v-model="responsibleForm.street"
|
|
|
|
|
+ :error="!!validationErrors['responsible.street']"
|
|
|
|
|
+ :error-message="validationErrors['responsible.street']"
|
|
|
|
|
+ label="Endereço"
|
|
|
|
|
+ class="col-5"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultInput
|
|
|
|
|
+ v-model="responsibleForm.address_number"
|
|
|
|
|
+ :error="!!validationErrors['responsible.address_number']"
|
|
|
|
|
+ :error-message="validationErrors['responsible.address_number']"
|
|
|
|
|
+ label="Número"
|
|
|
|
|
+ class="col-3"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultInput
|
|
|
|
|
+ v-model="responsibleForm.neighborhood"
|
|
|
|
|
+ :error="!!validationErrors['responsible.neighborhood']"
|
|
|
|
|
+ :error-message="validationErrors['responsible.neighborhood']"
|
|
|
|
|
+ label="Bairro"
|
|
|
|
|
+ class="col-4"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <CitySelect
|
|
|
|
|
+ ref="responsibleCitySelectRef"
|
|
|
|
|
+ v-model="responsibleSelectedCity"
|
|
|
|
|
+ :error="!!validationErrors['responsible.city_id']"
|
|
|
|
|
+ :error-message="validationErrors['responsible.city_id']"
|
|
|
|
|
+ label="Cidade"
|
|
|
|
|
+ class="col-4"
|
|
|
|
|
+ :state="responsibleSelectedState"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <StateSelect
|
|
|
|
|
+ ref="responsibleStateSelectRef"
|
|
|
|
|
+ v-model="responsibleSelectedState"
|
|
|
|
|
+ :error="!!validationErrors['responsible.state_id']"
|
|
|
|
|
+ :error-message="validationErrors['responsible.state_id']"
|
|
|
|
|
+ label="Estado"
|
|
|
|
|
+ class="col-4"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultInput
|
|
|
|
|
+ v-model="responsibleForm.complement"
|
|
|
|
|
+ :error="!!validationErrors['responsible.complement']"
|
|
|
|
|
+ :error-message="validationErrors['responsible.complement']"
|
|
|
|
|
+ label="Complemento"
|
|
|
|
|
+ class="col-12"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DefaultInput
|
|
|
|
|
+ v-model="responsibleForm.notes"
|
|
|
|
|
+ :error="!!validationErrors['responsible.notes']"
|
|
|
|
|
+ :error-message="validationErrors['responsible.notes']"
|
|
|
|
|
+ label="Observações"
|
|
|
|
|
+ class="col-12"
|
|
|
|
|
+ type="textarea"
|
|
|
|
|
+ :input-style="{ minHeight: '120px' }"
|
|
|
|
|
+ autogrow
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</q-card-section>
|
|
</q-card-section>
|
|
|
</q-scroll-area>
|
|
</q-scroll-area>
|
|
|
|
|
|
|
@@ -179,7 +355,7 @@
|
|
|
/>
|
|
/>
|
|
|
<q-btn
|
|
<q-btn
|
|
|
color="primary"
|
|
color="primary"
|
|
|
- label="CADASTRAR"
|
|
|
|
|
|
|
+ :label="primaryActionLabel"
|
|
|
type="submit"
|
|
type="submit"
|
|
|
:loading="loading"
|
|
:loading="loading"
|
|
|
/>
|
|
/>
|
|
@@ -190,7 +366,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
-import { ref, watch, useTemplateRef } from "vue";
|
|
|
|
|
|
|
+import { computed, nextTick, ref, watch, useTemplateRef } from "vue";
|
|
|
import { useDialogPluginComponent } from "quasar";
|
|
import { useDialogPluginComponent } from "quasar";
|
|
|
|
|
|
|
|
import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
|
|
import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
|
|
@@ -199,13 +375,18 @@ import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
|
|
|
import DefaultInputDatePicker from "src/components/defaults/DefaultInputDatePicker.vue";
|
|
import DefaultInputDatePicker from "src/components/defaults/DefaultInputDatePicker.vue";
|
|
|
import DefaultCepInput from "src/components/defaults/DefaultCepInput.vue";
|
|
import DefaultCepInput from "src/components/defaults/DefaultCepInput.vue";
|
|
|
import AvatarImageComponent from "src/components/shared/AvatarImageComponent.vue";
|
|
import AvatarImageComponent from "src/components/shared/AvatarImageComponent.vue";
|
|
|
|
|
+import CustomTabComponent from "src/components/shared/CustomTabComponent.vue";
|
|
|
import StateSelect from "src/components/selects/StateSelect.vue";
|
|
import StateSelect from "src/components/selects/StateSelect.vue";
|
|
|
import CitySelect from "src/components/selects/CitySelect.vue";
|
|
import CitySelect from "src/components/selects/CitySelect.vue";
|
|
|
import { useInputRules } from "src/composables/useInputRules";
|
|
import { useInputRules } from "src/composables/useInputRules";
|
|
|
|
|
+import { useScroll } from "src/composables/useScroll";
|
|
|
import { useSubmitHandler } from "src/composables/useSubmitHandler";
|
|
import { useSubmitHandler } from "src/composables/useSubmitHandler";
|
|
|
-import { createStudent } from "src/api/student";
|
|
|
|
|
|
|
+import {
|
|
|
|
|
+ createStudent,
|
|
|
|
|
+ createStudentRegistrationDraft,
|
|
|
|
|
+} from "src/api/student";
|
|
|
import masks from "src/helpers/masks";
|
|
import masks from "src/helpers/masks";
|
|
|
-import { formatDateDMYtoYMD } from "src/helpers/utils";
|
|
|
|
|
|
|
+import { formatDateDMYtoYMD, isUnderage } from "src/helpers/utils";
|
|
|
|
|
|
|
|
defineEmits([...useDialogPluginComponent.emits]);
|
|
defineEmits([...useDialogPluginComponent.emits]);
|
|
|
|
|
|
|
@@ -215,12 +396,29 @@ const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
|
|
|
const { inputRules } = useInputRules();
|
|
const { inputRules } = useInputRules();
|
|
|
|
|
|
|
|
const formRef = useTemplateRef("formRef");
|
|
const formRef = useTemplateRef("formRef");
|
|
|
|
|
+const scrollAreaRef = useTemplateRef("scrollAreaRef");
|
|
|
|
|
+const studentTabRef = useTemplateRef("studentTabRef");
|
|
|
|
|
+const responsibleTabRef = useTemplateRef("responsibleTabRef");
|
|
|
const stateSelectRef = useTemplateRef("stateSelectRef");
|
|
const stateSelectRef = useTemplateRef("stateSelectRef");
|
|
|
const citySelectRef = useTemplateRef("citySelectRef");
|
|
const citySelectRef = useTemplateRef("citySelectRef");
|
|
|
|
|
+const responsibleStateSelectRef = useTemplateRef("responsibleStateSelectRef");
|
|
|
|
|
+const responsibleCitySelectRef = useTemplateRef("responsibleCitySelectRef");
|
|
|
|
|
+
|
|
|
|
|
+const REGISTRATION_DRAFT_TOKEN_STORAGE_KEY =
|
|
|
|
|
+ "student_registration_draft_token";
|
|
|
|
|
|
|
|
const avatarFile = ref(null);
|
|
const avatarFile = ref(null);
|
|
|
const selectedState = ref(null);
|
|
const selectedState = ref(null);
|
|
|
const selectedCity = ref(null);
|
|
const selectedCity = ref(null);
|
|
|
|
|
+const responsibleSelectedState = ref(null);
|
|
|
|
|
+const responsibleSelectedCity = ref(null);
|
|
|
|
|
+const activeTab = ref("student");
|
|
|
|
|
+
|
|
|
|
|
+const registrationDraftToken = ref(
|
|
|
|
|
+ localStorage.getItem(REGISTRATION_DRAFT_TOKEN_STORAGE_KEY) || null,
|
|
|
|
|
+);
|
|
|
|
|
+
|
|
|
|
|
+const { scrollToComponent } = useScroll();
|
|
|
|
|
|
|
|
const form = ref({
|
|
const form = ref({
|
|
|
name: null,
|
|
name: null,
|
|
@@ -241,6 +439,37 @@ const form = ref({
|
|
|
notes: null,
|
|
notes: null,
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+const responsibleForm = ref({
|
|
|
|
|
+ name: null,
|
|
|
|
|
+ birth_date: null,
|
|
|
|
|
+ cpf: null,
|
|
|
|
|
+ gender: "no_preference",
|
|
|
|
|
+ degree: null,
|
|
|
|
|
+ email: null,
|
|
|
|
|
+ phone: null,
|
|
|
|
|
+ postal_code: null,
|
|
|
|
|
+ street: null,
|
|
|
|
|
+ address_number: null,
|
|
|
|
|
+ neighborhood: null,
|
|
|
|
|
+ city_id: null,
|
|
|
|
|
+ state_id: null,
|
|
|
|
|
+ complement: null,
|
|
|
|
|
+ notes: null,
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const isStudentUnderage = computed(() => isUnderage(form.value.birthdate));
|
|
|
|
|
+const tabs = computed(() => [
|
|
|
|
|
+ { name: "student", label: "Dados do Aluno" },
|
|
|
|
|
+ ...(isStudentUnderage.value
|
|
|
|
|
+ ? [{ name: "responsible", label: "Responsável" }]
|
|
|
|
|
+ : []),
|
|
|
|
|
+]);
|
|
|
|
|
+const primaryActionLabel = computed(() =>
|
|
|
|
|
+ isStudentUnderage.value && activeTab.value === "student"
|
|
|
|
|
+ ? "PRÓXIMO"
|
|
|
|
|
+ : "CADASTRAR",
|
|
|
|
|
+);
|
|
|
|
|
+
|
|
|
watch(selectedState, (state) => {
|
|
watch(selectedState, (state) => {
|
|
|
form.value.state_id = state?.value ?? null;
|
|
form.value.state_id = state?.value ?? null;
|
|
|
});
|
|
});
|
|
@@ -249,6 +478,20 @@ watch(selectedCity, (city) => {
|
|
|
form.value.city_id = city?.value ?? null;
|
|
form.value.city_id = city?.value ?? null;
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+watch(responsibleSelectedState, (state) => {
|
|
|
|
|
+ responsibleForm.value.state_id = state?.value ?? null;
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+watch(responsibleSelectedCity, (city) => {
|
|
|
|
|
+ responsibleForm.value.city_id = city?.value ?? null;
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+watch(isStudentUnderage, (underage) => {
|
|
|
|
|
+ if (!underage && activeTab.value === "responsible") {
|
|
|
|
|
+ activeTab.value = "student";
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
const genderOptions = ref([
|
|
const genderOptions = ref([
|
|
|
{ label: "Prefiro não informar", value: "no_preference" },
|
|
{ label: "Prefiro não informar", value: "no_preference" },
|
|
|
{ label: "Masculino", value: "male" },
|
|
{ label: "Masculino", value: "male" },
|
|
@@ -263,46 +506,114 @@ const howFoundOptions = ref([
|
|
|
{ label: "Outro", value: "other" },
|
|
{ label: "Outro", value: "other" },
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
-const { loading, validationErrors, execute } = useSubmitHandler({
|
|
|
|
|
|
|
+const {
|
|
|
|
|
+ loading: saving,
|
|
|
|
|
+ validationErrors: submitValidationErrors,
|
|
|
|
|
+ execute: executeSubmit,
|
|
|
|
|
+} = useSubmitHandler({
|
|
|
formRef,
|
|
formRef,
|
|
|
|
|
+ containerRef: scrollAreaRef,
|
|
|
|
|
+ scrollFn: scrollToTabError,
|
|
|
onSuccess: (student) => {
|
|
onSuccess: (student) => {
|
|
|
|
|
+ clearRegistrationDraftToken();
|
|
|
onDialogOK(student);
|
|
onDialogOK(student);
|
|
|
},
|
|
},
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+const {
|
|
|
|
|
+ loading: validatingStudent,
|
|
|
|
|
+ validationErrors: draftValidationErrors,
|
|
|
|
|
+ execute: executeDraft,
|
|
|
|
|
+} = useSubmitHandler({
|
|
|
|
|
+ formRef,
|
|
|
|
|
+ containerRef: scrollAreaRef,
|
|
|
|
|
+ scrollFn: scrollToTabError,
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+const loading = computed(() => saving.value || validatingStudent.value);
|
|
|
|
|
+const validationErrors = computed(() => ({
|
|
|
|
|
+ ...draftValidationErrors.value,
|
|
|
|
|
+ ...submitValidationErrors.value,
|
|
|
|
|
+}));
|
|
|
|
|
+
|
|
|
|
|
+watch(validationErrors, (errors) => {
|
|
|
|
|
+ const fields = Object.keys(errors);
|
|
|
|
|
+ if (fields.some((field) => !field.startsWith("responsible"))) {
|
|
|
|
|
+ activeTab.value = "student";
|
|
|
|
|
+ } else if (fields.some((field) => field.startsWith("responsible"))) {
|
|
|
|
|
+ activeTab.value = "responsible";
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
function onAvatarChange(file) {
|
|
function onAvatarChange(file) {
|
|
|
avatarFile.value = file;
|
|
avatarFile.value = file;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function buildPayload() {
|
|
|
|
|
- if (avatarFile.value) {
|
|
|
|
|
- const formData = new FormData();
|
|
|
|
|
- formData.append("name", form.value.name ?? "");
|
|
|
|
|
- if (form.value.birthdate) {
|
|
|
|
|
- formData.append("birth_date", formatDateDMYtoYMD(form.value.birthdate));
|
|
|
|
|
- }
|
|
|
|
|
- formData.append("document_number", form.value.cpf ?? "");
|
|
|
|
|
- formData.append("gender", form.value.gender ?? "");
|
|
|
|
|
- formData.append("email", form.value.email ?? "");
|
|
|
|
|
- formData.append("phone", form.value.phone ?? "");
|
|
|
|
|
- formData.append("postal_code", form.value.cep ?? "");
|
|
|
|
|
- formData.append("street", form.value.address ?? "");
|
|
|
|
|
- formData.append("address_number", form.value.address_number ?? "");
|
|
|
|
|
- formData.append("neighborhood", form.value.neighborhood ?? "");
|
|
|
|
|
- if (form.value.state_id) {
|
|
|
|
|
- formData.append("state_id", form.value.state_id);
|
|
|
|
|
- }
|
|
|
|
|
- if (form.value.city_id) {
|
|
|
|
|
- formData.append("city_id", form.value.city_id);
|
|
|
|
|
- }
|
|
|
|
|
- formData.append("complement", form.value.complement ?? "");
|
|
|
|
|
- formData.append("payer_name", form.value.payer ?? "");
|
|
|
|
|
- formData.append("how_did_you_know_us", form.value.how_found ?? "");
|
|
|
|
|
- formData.append("notes", form.value.notes ?? "");
|
|
|
|
|
- formData.append("avatar", avatarFile.value);
|
|
|
|
|
- return formData;
|
|
|
|
|
|
|
+function saveRegistrationDraftToken(token) {
|
|
|
|
|
+ registrationDraftToken.value = token;
|
|
|
|
|
+ localStorage.setItem(REGISTRATION_DRAFT_TOKEN_STORAGE_KEY, token);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function clearRegistrationDraftToken() {
|
|
|
|
|
+ registrationDraftToken.value = null;
|
|
|
|
|
+ localStorage.removeItem(REGISTRATION_DRAFT_TOKEN_STORAGE_KEY);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function setActiveTab(tab, resetScroll = false) {
|
|
|
|
|
+ activeTab.value = tab;
|
|
|
|
|
+ await nextTick();
|
|
|
|
|
+
|
|
|
|
|
+ if (resetScroll) {
|
|
|
|
|
+ scrollAreaRef.value?.setScrollPosition("vertical", 0, 0);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function scrollToTabError(component, container, options) {
|
|
|
|
|
+ const element = component?.$el ?? component;
|
|
|
|
|
+ const studentElement = studentTabRef.value?.$el ?? studentTabRef.value;
|
|
|
|
|
+ const responsibleElement =
|
|
|
|
|
+ responsibleTabRef.value?.$el ?? responsibleTabRef.value;
|
|
|
|
|
+
|
|
|
|
|
+ if (studentElement?.contains(element)) {
|
|
|
|
|
+ await setActiveTab("student");
|
|
|
|
|
+
|
|
|
|
|
+ } else if (responsibleElement?.contains(element)) {
|
|
|
|
|
+ await setActiveTab("responsible");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ return scrollToComponent(component, container, options);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function buildResponsiblePayload() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ name: responsibleForm.value.name,
|
|
|
|
|
+ birth_date: responsibleForm.value.birth_date
|
|
|
|
|
+ ? formatDateDMYtoYMD(responsibleForm.value.birth_date)
|
|
|
|
|
+ : null,
|
|
|
|
|
+ cpf: responsibleForm.value.cpf,
|
|
|
|
|
+ gender: responsibleForm.value.gender,
|
|
|
|
|
+ degree: responsibleForm.value.degree,
|
|
|
|
|
+ email: responsibleForm.value.email || null,
|
|
|
|
|
+ phone: responsibleForm.value.phone,
|
|
|
|
|
+ postal_code: responsibleForm.value.postal_code,
|
|
|
|
|
+ street: responsibleForm.value.street,
|
|
|
|
|
+ address_number: responsibleForm.value.address_number,
|
|
|
|
|
+ neighborhood: responsibleForm.value.neighborhood,
|
|
|
|
|
+ city_id: responsibleForm.value.city_id,
|
|
|
|
|
+ state_id: responsibleForm.value.state_id,
|
|
|
|
|
+ complement: responsibleForm.value.complement,
|
|
|
|
|
+ notes: responsibleForm.value.notes,
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function appendResponsibleToFormData(formData) {
|
|
|
|
|
+ const responsible = buildResponsiblePayload();
|
|
|
|
|
+ Object.entries(responsible).forEach(([field, value]) => {
|
|
|
|
|
+ formData.append(`responsible[${field}]`, value ?? "");
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function buildStudentPayload() {
|
|
|
return {
|
|
return {
|
|
|
name: form.value.name,
|
|
name: form.value.name,
|
|
|
birth_date: form.value.birthdate
|
|
birth_date: form.value.birthdate
|
|
@@ -325,7 +636,88 @@ function buildPayload() {
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function buildRegistrationDraftPayload() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ name: form.value.name,
|
|
|
|
|
+ birth_date: form.value.birthdate
|
|
|
|
|
+ ? formatDateDMYtoYMD(form.value.birthdate)
|
|
|
|
|
+ : null,
|
|
|
|
|
+ document_number: form.value.cpf,
|
|
|
|
|
+ email: form.value.email || null,
|
|
|
|
|
+ registration_draft_token: registrationDraftToken.value,
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function appendStudentToFormData(formData) {
|
|
|
|
|
+ const student = buildStudentPayload();
|
|
|
|
|
+ Object.entries(student).forEach(([field, value]) => {
|
|
|
|
|
+ formData.append(field, value ?? "");
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function buildPayload() {
|
|
|
|
|
+ if (isStudentUnderage.value) {
|
|
|
|
|
+ if (avatarFile.value) {
|
|
|
|
|
+ const formData = new FormData();
|
|
|
|
|
+ appendStudentToFormData(formData);
|
|
|
|
|
+ formData.append(
|
|
|
|
|
+ "registration_draft_token",
|
|
|
|
|
+ registrationDraftToken.value,
|
|
|
|
|
+ );
|
|
|
|
|
+ formData.append("avatar", avatarFile.value);
|
|
|
|
|
+ appendResponsibleToFormData(formData);
|
|
|
|
|
+ return formData;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...buildStudentPayload(),
|
|
|
|
|
+ registration_draft_token: registrationDraftToken.value,
|
|
|
|
|
+ responsible: buildResponsiblePayload(),
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (avatarFile.value) {
|
|
|
|
|
+ const formData = new FormData();
|
|
|
|
|
+ appendStudentToFormData(formData);
|
|
|
|
|
+ formData.append("avatar", avatarFile.value);
|
|
|
|
|
+ return formData;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return buildStudentPayload();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function goToResponsible() {
|
|
|
|
|
+ if (validatingStudent.value) return;
|
|
|
|
|
+
|
|
|
|
|
+ const draft = await executeDraft(() =>
|
|
|
|
|
+ createStudentRegistrationDraft(buildRegistrationDraftPayload()),
|
|
|
|
|
+ );
|
|
|
|
|
+ const nextToken = draft?.registration_draft_token;
|
|
|
|
|
+
|
|
|
|
|
+ if (nextToken && nextToken !== registrationDraftToken.value) {
|
|
|
|
|
+ saveRegistrationDraftToken(nextToken);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ await setActiveTab("responsible", true);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function handleTabChange(tab) {
|
|
|
|
|
+ if (tab === activeTab.value) return;
|
|
|
|
|
+
|
|
|
|
|
+ if (tab === "responsible") {
|
|
|
|
|
+ await goToResponsible();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ await setActiveTab(tab, true);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
async function onOKClick() {
|
|
async function onOKClick() {
|
|
|
- await execute(() => createStudent(buildPayload()));
|
|
|
|
|
|
|
+ if (isStudentUnderage.value && activeTab.value === "student") {
|
|
|
|
|
+ await goToResponsible();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ await executeSubmit(() => createStudent(buildPayload()));
|
|
|
}
|
|
}
|
|
|
</script>
|
|
</script>
|