Jelajahi Sumber

feat(students): remover alunos e adicionar students

ebagabee 2 minggu lalu
induk
melakukan
1f27c5380a

+ 19 - 2
src/pages/alunos/AlunosPage.vue

@@ -4,6 +4,7 @@
 
     <div class="q-px-sm">
       <DefaultTable
+        ref="tableRef"
         v-model:rows="rows"
         title="Lista de alunos"
         :columns
@@ -11,7 +12,7 @@
         :feminino="false"
         no-api-call
         add-item
-        add-item-route="AlunoAddPage"
+        @on-add-item="onAddAluno"
       >
         <template #body-cell-contato="{ row }">
           <q-td>
@@ -55,9 +56,17 @@
 </template>
 
 <script setup>
+import { defineAsyncComponent, ref, useTemplateRef } from "vue";
+import { useQuasar } from "quasar";
 import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
 import DefaultTable from "src/components/defaults/DefaultTable.vue";
-import { ref } from "vue";
+
+const AddEditAlunoDialog = defineAsyncComponent(
+  () => import("src/pages/alunos/components/AddEditAlunoDialog.vue"),
+);
+
+const $q = useQuasar();
+const tableRef = useTemplateRef("tableRef");
 
 const columns = ref([
   {
@@ -86,6 +95,14 @@ const columns = ref([
   },
 ]);
 
+function onAddAluno() {
+  $q.dialog({
+    component: AddEditAlunoDialog,
+  }).onOk(() => {
+    tableRef.value?.refresh();
+  });
+}
+
 const rows = ref([
   { id: 1, name: "Heloisa Faria", phone: "(45)99999-9999", city: "Toledo-PR", status: "inactive" },
   { id: 2, name: "Carol", phone: "(45)99999-9999", city: "Arapongas-PR", status: "inactive" },

+ 230 - 0
src/pages/alunos/components/AddEditAlunoDialog.vue

@@ -0,0 +1,230 @@
+<template>
+  <q-dialog ref="dialogRef" @hide="onDialogHide">
+    <q-card
+      class="q-dialog-plugin overflow-hidden"
+      style="width: 900px; max-width: 95vw"
+    >
+      <DefaultDialogHeader title="Cadastro de Aluno" @close="onDialogCancel" />
+
+      <q-form ref="formRef" @submit="onOKClick">
+        <q-card-section class="q-pt-none">
+          <div class="text-subtitle2 q-mb-sm">Dados do Aluno</div>
+
+          <div class="row q-col-gutter-sm">
+            <DefaultInput
+              v-model="form.name"
+              label="Nome do aluno"
+              class="col-md-5 col-12"
+            />
+
+            <DefaultInputDatePicker
+              v-model="form.birthdate"
+              label="Data de Nascimento"
+              class="col-md-5 col-12"
+            />
+
+            <div class="col-md-2 col-12 flex justify-center items-start">
+              <div style="position: relative; display: inline-block">
+                <q-avatar size="72px" color="grey-3">
+                  <img v-if="avatarPreview" :src="avatarPreview" />
+                  <q-icon v-else name="mdi-account" size="42px" color="grey-6" />
+                </q-avatar>
+                <q-btn
+                  round
+                  dense
+                  color="primary"
+                  icon="mdi-camera"
+                  size="xs"
+                  style="position: absolute; bottom: 0; right: 0"
+                  @click="triggerFileInput"
+                />
+                <input
+                  ref="fileInputRef"
+                  type="file"
+                  accept="image/*"
+                  style="display: none"
+                  @change="onAvatarChange"
+                />
+              </div>
+            </div>
+
+            <DefaultInput
+              v-model="form.cpf"
+              label="CPF / CNH"
+              class="col-md-6 col-12"
+            />
+
+            <DefaultSelect
+              v-model="form.gender"
+              label="Gênero"
+              class="col-md-6 col-12"
+              emit-value
+              map-options
+              :options="genderOptions"
+            />
+
+            <DefaultInput
+              v-model="form.email"
+              label="E-mail"
+              class="col-md-6 col-12"
+              type="email"
+            />
+
+            <DefaultInput
+              v-model="form.phone"
+              label="Celular com DDD"
+              class="col-md-6 col-12"
+            />
+
+            <DefaultInput
+              v-model="form.cep"
+              label="Cep"
+              class="col-md-3 col-12"
+            />
+
+            <DefaultInput
+              v-model="form.address"
+              label="Endereço"
+              class="col-md-6 col-12"
+            />
+
+            <DefaultInput
+              v-model="form.address_number"
+              label="Numero"
+              class="col-md-3 col-12"
+            />
+
+            <DefaultInput
+              v-model="form.neighborhood"
+              label="Bairro"
+              class="col-md-6 col-12"
+            />
+
+            <DefaultInput
+              v-model="form.city_state"
+              label="Cidade / Estado"
+              class="col-md-6 col-12"
+            />
+
+            <DefaultInput
+              v-model="form.complement"
+              label="Complemento"
+              class="col-md-6 col-12"
+            />
+
+            <DefaultInput
+              v-model="form.payer"
+              label="Pagador"
+              class="col-md-6 col-12"
+            />
+
+            <DefaultSelect
+              v-model="form.how_found"
+              label="Como nos conheceu?"
+              class="col-12"
+              emit-value
+              map-options
+              :options="howFoundOptions"
+            />
+
+            <DefaultInput
+              v-model="form.notes"
+              label="Observações"
+              class="col-12"
+              type="textarea"
+              autogrow
+            />
+          </div>
+        </q-card-section>
+
+        <q-card-actions align="right">
+          <q-btn
+            outline
+            color="primary"
+            label="CANCELAR"
+            @click="onDialogCancel"
+          />
+          <q-btn
+            color="primary"
+            label="CADASTRAR"
+            type="submit"
+            :loading="loading"
+          />
+        </q-card-actions>
+      </q-form>
+    </q-card>
+  </q-dialog>
+</template>
+
+<script setup>
+import { ref, useTemplateRef } from "vue";
+import { useDialogPluginComponent } from "quasar";
+
+import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
+import DefaultInput from "src/components/defaults/DefaultInput.vue";
+import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
+import DefaultInputDatePicker from "src/components/defaults/DefaultInputDatePicker.vue";
+
+defineEmits([...useDialogPluginComponent.emits]);
+
+const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
+  useDialogPluginComponent();
+
+const formRef = useTemplateRef("formRef");
+const fileInputRef = useTemplateRef("fileInputRef");
+const loading = ref(false);
+const avatarPreview = ref(null);
+
+const form = ref({
+  name: null,
+  birthdate: null,
+  cpf: null,
+  gender: "no_preference",
+  email: null,
+  phone: null,
+  cep: null,
+  address: null,
+  address_number: null,
+  neighborhood: null,
+  city_state: null,
+  complement: null,
+  payer: null,
+  how_found: null,
+  notes: null,
+});
+
+const genderOptions = ref([
+  { label: "Prefiro não informar", value: "no_preference" },
+  { label: "Masculino", value: "male" },
+  { label: "Feminino", value: "female" },
+  { label: "Outro", value: "other" },
+]);
+
+const howFoundOptions = ref([
+  { label: "Indicação", value: "referral" },
+  { label: "Redes Sociais", value: "social_media" },
+  { label: "Google", value: "google" },
+  { label: "Outro", value: "other" },
+]);
+
+function triggerFileInput() {
+  fileInputRef.value?.click();
+}
+
+function onAvatarChange(event) {
+  const file = event.target.files[0];
+  if (file) {
+    avatarPreview.value = URL.createObjectURL(file);
+  }
+}
+
+async function onOKClick() {
+  loading.value = true;
+  try {
+    console.log("Saving aluno:", form.value);
+    onDialogOK(true);
+  } finally {
+    loading.value = false;
+  }
+}
+</script>

+ 116 - 0
src/pages/students/StudentPage.vue

@@ -0,0 +1,116 @@
+<template>
+  <div>
+    <DefaultHeaderPage title="Alunos" show-filter-icon />
+
+    <div class="q-px-sm">
+      <DefaultTable
+        ref="tableRef"
+        v-model:rows="rows"
+        title="Lista de alunos"
+        :columns
+        descricao="Alunos"
+        :feminino="false"
+        no-api-call
+        add-item
+        @on-add-item="onAddStudent"
+      >
+        <template #body-cell-contato="{ row }">
+          <q-td>
+            <div>{{ row.phone }}</div>
+            <div class="text-grey-6 text-caption">{{ row.city }}</div>
+          </q-td>
+        </template>
+
+        <template #body-cell-status="{ row }">
+          <q-td align="center">
+            <q-badge
+              :color="row.status === 'active' ? 'positive' : 'warning'"
+              :label="row.status === 'active' ? 'Ativo' : 'Inativo'"
+            />
+          </q-td>
+        </template>
+
+        <template #body-cell-actions>
+          <q-td auto-width>
+            <q-item-section class="no-wrap" style="flex-direction: row">
+              <q-btn
+                outline
+                icon="mdi-account-edit-outline"
+                style="width: 36px"
+                class="q-mr-sm"
+                @click.prevent.stop="() => {}"
+              />
+              <q-btn
+                outline
+                icon="mdi-trash-can-outline"
+                style="width: 36px"
+                class="q-mr-sm"
+                @click.prevent.stop="() => {}"
+              />
+            </q-item-section>
+          </q-td>
+        </template>
+      </DefaultTable>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { defineAsyncComponent, ref, useTemplateRef } from "vue";
+import { useQuasar } from "quasar";
+import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
+import DefaultTable from "src/components/defaults/DefaultTable.vue";
+
+const AddEditStudentDialog = defineAsyncComponent(
+  () => import("src/pages/students/components/AddEditStudentDialog.vue"),
+);
+
+const $q = useQuasar();
+const tableRef = useTemplateRef("tableRef");
+
+const columns = ref([
+  {
+    name: "name",
+    label: "Nome",
+    field: "name",
+    align: "left",
+  },
+  {
+    name: "contato",
+    label: "Contato",
+    field: "contato",
+    align: "left",
+  },
+  {
+    name: "status",
+    label: "Status",
+    field: "status",
+    align: "center",
+  },
+  {
+    name: "actions",
+    label: "Ações",
+    field: null,
+    align: "center",
+  },
+]);
+
+function onAddStudent() {
+  $q.dialog({
+    component: AddEditStudentDialog,
+  }).onOk(() => {
+    tableRef.value?.refresh();
+  });
+}
+
+const rows = ref([
+  { id: 1, name: "Heloisa Faria", phone: "(45)99999-9999", city: "Toledo-PR", status: "inactive" },
+  { id: 2, name: "Carol", phone: "(45)99999-9999", city: "Arapongas-PR", status: "inactive" },
+  { id: 3, name: "Marcelo Souza", phone: "(45)98888-8888", city: "Curitiba-PR", status: "active" },
+  { id: 4, name: "Ana Lucia", phone: "(45)97777-7777", city: "Londrina-PR", status: "active" },
+  { id: 5, name: "Ricardo Silva", phone: "(45)96666-6666", city: "Ponta Grossa-PR", status: "active" },
+  { id: 6, name: "Juliana Costa", phone: "(45)95555-5555", city: "Maringá-PR", status: "active" },
+  { id: 7, name: "Fernando Almeida", phone: "(45)94444-4444", city: "Cascavel-PR", status: "active" },
+  { id: 8, name: "Patricia Lima", phone: "(45)93333-3333", city: "Foz do Iguaçu-PR", status: "active" },
+]);
+</script>

+ 230 - 0
src/pages/students/components/AddEditStudentDialog.vue

@@ -0,0 +1,230 @@
+<template>
+  <q-dialog ref="dialogRef" @hide="onDialogHide">
+    <q-card
+      class="q-dialog-plugin overflow-hidden"
+      style="width: 900px; max-width: 95vw"
+    >
+      <DefaultDialogHeader title="Cadastro de Aluno" @close="onDialogCancel" />
+
+      <q-form ref="formRef" @submit="onOKClick">
+        <q-card-section class="q-pt-none">
+          <div class="text-subtitle2 q-mb-sm">Dados do Aluno</div>
+
+          <div class="row q-col-gutter-sm">
+            <DefaultInput
+              v-model="form.name"
+              label="Nome do aluno"
+              class="col-md-5 col-12"
+            />
+
+            <DefaultInputDatePicker
+              v-model="form.birthdate"
+              label="Data de Nascimento"
+              class="col-md-5 col-12"
+            />
+
+            <div class="col-md-2 col-12 flex justify-center items-start">
+              <div style="position: relative; display: inline-block">
+                <q-avatar size="72px" color="grey-3">
+                  <img v-if="avatarPreview" :src="avatarPreview" />
+                  <q-icon v-else name="mdi-account" size="42px" color="grey-6" />
+                </q-avatar>
+                <q-btn
+                  round
+                  dense
+                  color="primary"
+                  icon="mdi-camera"
+                  size="xs"
+                  style="position: absolute; bottom: 0; right: 0"
+                  @click="triggerFileInput"
+                />
+                <input
+                  ref="fileInputRef"
+                  type="file"
+                  accept="image/*"
+                  style="display: none"
+                  @change="onAvatarChange"
+                />
+              </div>
+            </div>
+
+            <DefaultInput
+              v-model="form.cpf"
+              label="CPF / CNH"
+              class="col-md-6 col-12"
+            />
+
+            <DefaultSelect
+              v-model="form.gender"
+              label="Gênero"
+              class="col-md-6 col-12"
+              emit-value
+              map-options
+              :options="genderOptions"
+            />
+
+            <DefaultInput
+              v-model="form.email"
+              label="E-mail"
+              class="col-md-6 col-12"
+              type="email"
+            />
+
+            <DefaultInput
+              v-model="form.phone"
+              label="Celular com DDD"
+              class="col-md-6 col-12"
+            />
+
+            <DefaultInput
+              v-model="form.cep"
+              label="Cep"
+              class="col-md-3 col-12"
+            />
+
+            <DefaultInput
+              v-model="form.address"
+              label="Endereço"
+              class="col-md-6 col-12"
+            />
+
+            <DefaultInput
+              v-model="form.address_number"
+              label="Numero"
+              class="col-md-3 col-12"
+            />
+
+            <DefaultInput
+              v-model="form.neighborhood"
+              label="Bairro"
+              class="col-md-6 col-12"
+            />
+
+            <DefaultInput
+              v-model="form.city_state"
+              label="Cidade / Estado"
+              class="col-md-6 col-12"
+            />
+
+            <DefaultInput
+              v-model="form.complement"
+              label="Complemento"
+              class="col-md-6 col-12"
+            />
+
+            <DefaultInput
+              v-model="form.payer"
+              label="Pagador"
+              class="col-md-6 col-12"
+            />
+
+            <DefaultSelect
+              v-model="form.how_found"
+              label="Como nos conheceu?"
+              class="col-12"
+              emit-value
+              map-options
+              :options="howFoundOptions"
+            />
+
+            <DefaultInput
+              v-model="form.notes"
+              label="Observações"
+              class="col-12"
+              type="textarea"
+              autogrow
+            />
+          </div>
+        </q-card-section>
+
+        <q-card-actions align="right">
+          <q-btn
+            outline
+            color="primary"
+            label="CANCELAR"
+            @click="onDialogCancel"
+          />
+          <q-btn
+            color="primary"
+            label="CADASTRAR"
+            type="submit"
+            :loading="loading"
+          />
+        </q-card-actions>
+      </q-form>
+    </q-card>
+  </q-dialog>
+</template>
+
+<script setup>
+import { ref, useTemplateRef } from "vue";
+import { useDialogPluginComponent } from "quasar";
+
+import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
+import DefaultInput from "src/components/defaults/DefaultInput.vue";
+import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
+import DefaultInputDatePicker from "src/components/defaults/DefaultInputDatePicker.vue";
+
+defineEmits([...useDialogPluginComponent.emits]);
+
+const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
+  useDialogPluginComponent();
+
+const formRef = useTemplateRef("formRef");
+const fileInputRef = useTemplateRef("fileInputRef");
+const loading = ref(false);
+const avatarPreview = ref(null);
+
+const form = ref({
+  name: null,
+  birthdate: null,
+  cpf: null,
+  gender: "no_preference",
+  email: null,
+  phone: null,
+  cep: null,
+  address: null,
+  address_number: null,
+  neighborhood: null,
+  city_state: null,
+  complement: null,
+  payer: null,
+  how_found: null,
+  notes: null,
+});
+
+const genderOptions = ref([
+  { label: "Prefiro não informar", value: "no_preference" },
+  { label: "Masculino", value: "male" },
+  { label: "Feminino", value: "female" },
+  { label: "Outro", value: "other" },
+]);
+
+const howFoundOptions = ref([
+  { label: "Indicação", value: "referral" },
+  { label: "Redes Sociais", value: "social_media" },
+  { label: "Google", value: "google" },
+  { label: "Outro", value: "other" },
+]);
+
+function triggerFileInput() {
+  fileInputRef.value?.click();
+}
+
+function onAvatarChange(event) {
+  const file = event.target.files[0];
+  if (file) {
+    avatarPreview.value = URL.createObjectURL(file);
+  }
+}
+
+async function onOKClick() {
+  loading.value = true;
+  try {
+    console.log("Saving student:", form.value);
+    onDialogOK(true);
+  } finally {
+    loading.value = false;
+  }
+}
+</script>

+ 4 - 26
src/router/routes/config.route.js

@@ -1,36 +1,14 @@
 export default [
   {
-    path: "/alunos/create",
-    name: "AlunoAddPage",
-    component: () => import("pages/alunos/AlunoActionPage.vue"),
-    meta: {
-      title: {
-        value: "Cadastro de Aluno",
-        translate: false,
-      },
-      requireAuth: true,
-      breadcrumbs: [
-        {
-          name: "AlunosPage",
-          title: "Alunos",
-        },
-        {
-          name: "AlunoAddPage",
-          title: "Cadastro de Aluno",
-        },
-      ],
-    },
-  },
-  {
-    path: "/alunos",
-    name: "AlunosPage",
-    component: () => import("pages/alunos/AlunosPage.vue"),
+    path: "/students",
+    name: "StudentPage",
+    component: () => import("pages/students/StudentPage.vue"),
     meta: {
       title: "Alunos",
       requireAuth: true,
       breadcrumbs: [
         {
-          name: "AlunosPage",
+          name: "StudentPage",
           title: "Alunos",
         },
       ],

+ 1 - 1
src/stores/navigation.js

@@ -23,7 +23,7 @@ export const navigationStore = defineStore("navigation", () => {
     {
       type: "single",
       title: "Alunos",
-      name: "AlunosPage",
+      name: "StudentPage",
       icon: "mdi-account-outline",
       disable: false,
       permission: true,