فهرست منبع

feat(classes): add time calculation for class duration and apply package schedule

ebagabee 1 هفته پیش
والد
کامیت
e39941933d

+ 53 - 1
src/pages/classes/components/AddEditClassDialog.vue

@@ -127,7 +127,7 @@
 </template>
 
 <script setup>
-import { ref, reactive, onMounted } from "vue";
+import { ref, reactive, onMounted, watch } from "vue";
 import { useDialogPluginComponent, useQuasar } from "quasar";
 
 import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
@@ -175,6 +175,58 @@ const form = reactive({
   ),
 });
 
+const trimTime = (time) => (time ? time.slice(0, 5) : "");
+
+function calculateEndTime(startTime, durationMinutes = 120) {
+  if (!/^\d{2}:\d{2}$/.test(startTime ?? "")) return "";
+  const [hours, minutes] = startTime.split(":").map(Number);
+  if (hours > 23 || minutes > 59) return "";
+
+  const endMinutes = (hours * 60 + minutes + durationMinutes) % (24 * 60);
+  return `${String(Math.floor(endMinutes / 60)).padStart(2, "0")}:${String(
+    endMinutes % 60,
+  ).padStart(2, "0")}`;
+}
+
+function selectedPackage() {
+  return packageOptions.value.find(
+    (pkg) => pkg.id === form.class_package_unit_id,
+  );
+}
+
+function applyPackageSchedule() {
+  if (props.classData) return;
+  const pkg = selectedPackage();
+  if (!pkg) return;
+
+  const selectedWeekday = form.date
+    ? new Date(`${form.date}T00:00:00`).getDay()
+    : null;
+  const startTime =
+    selectedWeekday === pkg.second_weekday
+      ? pkg.second_start_time
+      : pkg.start_time;
+
+  form.start_time = trimTime(startTime);
+  form.end_time = calculateEndTime(
+    form.start_time,
+    pkg.class_duration_minutes ?? 120,
+  );
+}
+
+watch(() => form.class_package_unit_id, applyPackageSchedule);
+watch(() => form.date, applyPackageSchedule);
+watch(
+  () => form.start_time,
+  (startTime) => {
+    const endTime = calculateEndTime(
+      startTime,
+      selectedPackage()?.class_duration_minutes ?? 120,
+    );
+    if (endTime) form.end_time = endTime;
+  },
+);
+
 const loadPackages = async () => {
   loadingPackages.value = true;
   try {

+ 133 - 1
src/pages/packages/components/AddEditPackageDialog.vue

@@ -23,6 +23,73 @@
                 type="number"
               />
 
+              <q-banner class="col-12 bg-blue-1 text-primary rounded-borders">
+                A duração padrão das aulas é de 2 horas e pode ser alterada se
+                necessário. É possível configurar no máximo dois dias.
+              </q-banner>
+
+              <DefaultInput
+                v-model="form.class_duration_hours"
+                label="Duração da Aula (horas)"
+                class="col-12"
+                type="number"
+                min="0.5"
+                max="24"
+                step="0.5"
+              />
+
+              <DefaultSelect
+                v-model="form.weekday"
+                label="1º Dia da Semana"
+                class="col-4"
+                :options="weekdays"
+                option-value="value"
+                option-label="label"
+                emit-value
+                map-options
+                clearable
+              />
+
+              <DefaultInput
+                v-model="form.start_time"
+                label="Horário Inicial"
+                class="col-4"
+                mask="##:##"
+              />
+
+              <DefaultInput
+                :model-value="firstEndTime"
+                label="Horário Final"
+                class="col-4"
+                disable
+              />
+
+              <DefaultSelect
+                v-model="form.second_weekday"
+                label="2º Dia da Semana"
+                class="col-4"
+                :options="secondWeekdayOptions"
+                option-value="value"
+                option-label="label"
+                emit-value
+                map-options
+                clearable
+              />
+
+              <DefaultInput
+                v-model="form.second_start_time"
+                label="Horário Inicial do 2º dia"
+                class="col-4"
+                mask="##:##"
+              />
+
+              <DefaultInput
+                :model-value="secondEndTime"
+                label="Horário Final do 2º dia"
+                class="col-4"
+                disable
+              />
+
               <DefaultCurrencyInput
                 v-model="form.contract_register_value"
                 label="R$ Matrícula"
@@ -119,7 +186,7 @@
 </template>
 
 <script setup>
-import { ref, computed, onMounted } from "vue";
+import { ref, computed, onMounted, watch } from "vue";
 import { useDialogPluginComponent } from "quasar";
 
 import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
@@ -151,6 +218,30 @@ const loading = ref(false);
 
 const products = ref([]);
 
+const weekdays = [
+  { value: 1, label: "Segunda" },
+  { value: 2, label: "Terça" },
+  { value: 3, label: "Quarta" },
+  { value: 4, label: "Quinta" },
+  { value: 5, label: "Sexta" },
+  { value: 6, label: "Sábado" },
+  { value: 0, label: "Domingo" },
+];
+
+const trimTime = (time) => (time ? time.slice(0, 5) : null);
+
+const calculateEndTime = (startTime, durationHours) => {
+  if (!/^\d{2}:\d{2}$/.test(startTime ?? "")) return null;
+  const [hours, minutes] = startTime.split(":").map(Number);
+  if (hours > 23 || minutes > 59) return null;
+  const end =
+    (hours * 60 + minutes + Math.round(Number(durationHours) * 60)) %
+    (24 * 60);
+  return `${String(Math.floor(end / 60)).padStart(2, "0")}:${String(
+    end % 60,
+  ).padStart(2, "0")}`;
+};
+
 const productOptions = computed(() =>
   products.value.map((p) => ({
     label: p.name,
@@ -165,9 +256,37 @@ const form = ref({
   contract_register_value: props.package?.contract_register_value ?? null,
   contract_value: props.package?.contract_value ?? null,
   contrat_discount_value: props.package?.contrat_discount_value ?? null,
+  class_duration_hours:
+    (props.package?.class_duration_minutes ?? 120) / 60,
+  weekday: props.package?.weekday ?? null,
+  start_time: trimTime(props.package?.start_time),
+  second_weekday: props.package?.second_weekday ?? null,
+  second_start_time: trimTime(props.package?.second_start_time),
   materials: [{ product_id: null, quantity: 1, price: null }],
 });
 
+const secondWeekdayOptions = computed(() =>
+  weekdays.filter((day) => day.value !== form.value.weekday),
+);
+const firstEndTime = computed(() =>
+  calculateEndTime(form.value.start_time, form.value.class_duration_hours),
+);
+const secondEndTime = computed(() =>
+  calculateEndTime(
+    form.value.second_start_time,
+    form.value.class_duration_hours,
+  ),
+);
+
+watch(
+  () => form.value.weekday,
+  (weekday) => {
+    if (form.value.second_weekday === weekday) {
+      form.value.second_weekday = null;
+    }
+  },
+);
+
 const onProductSelected = (material) => {
   const option = productOptions.value.find(
     (o) => o.value === material.product_id,
@@ -189,6 +308,12 @@ onMounted(async () => {
 
   if (props.package?.id) {
     const pkg = await getUnitPackage(props.package.id);
+    form.value.class_duration_hours =
+      (pkg.class_duration_minutes ?? 120) / 60;
+    form.value.weekday = pkg.weekday ?? null;
+    form.value.start_time = trimTime(pkg.start_time);
+    form.value.second_weekday = pkg.second_weekday ?? null;
+    form.value.second_start_time = trimTime(pkg.second_start_time);
     if (pkg.materials?.length) {
       form.value.materials = pkg.materials.map((m) => ({
         product_id: m.product_id,
@@ -210,6 +335,13 @@ const onOKClick = async () => {
       contract_value: form.value.contract_value,
       contract_register_value: form.value.contract_register_value,
       contrat_discount_value: form.value.contrat_discount_value,
+      class_duration_minutes: Math.round(
+        Number(form.value.class_duration_hours) * 60,
+      ),
+      weekday: form.value.weekday,
+      start_time: form.value.start_time || null,
+      second_weekday: form.value.second_weekday,
+      second_start_time: form.value.second_start_time || null,
       materials: validMaterials.map((m) => ({
         product_id: m.product_id,
         quantity: Number(m.quantity),

+ 3 - 0
src/pages/packages/components/PackageCard.vue

@@ -13,6 +13,9 @@
         @click.stop="$emit('delete')"
       />
       <div class="text-body2 text-grey-6 q-mb-sm">{{ pkg.quantity_classes }} Aulas</div>
+      <div class="text-body2 text-grey-6 q-mb-sm">
+        Duração padrão: {{ (pkg.class_duration_minutes ?? 120) / 60 }}h
+      </div>
 
       <div class="column" style="gap: 2px">
         <span class="text-body2">Contrato {{ formatToBRLCurrency(pkg.contract_value) }}</span>

+ 48 - 0
src/pages/students/components/AddEditContractDialog.vue

@@ -400,6 +400,18 @@ const loadingMedias = ref(false);
 
 const trimTime = (t) => (t ? t.slice(0, 5) : null);
 
+function calculateEndTime(startTime, durationMinutes = 120) {
+  if (!/^\d{2}:\d{2}$/.test(startTime ?? "")) return null;
+
+  const [hours, minutes] = startTime.split(":").map(Number);
+  if (hours > 23 || minutes > 59) return null;
+
+  const endMinutes = (hours * 60 + minutes + durationMinutes) % (24 * 60);
+  return `${String(Math.floor(endMinutes / 60)).padStart(2, "0")}:${String(
+    endMinutes % 60,
+  ).padStart(2, "0")}`;
+}
+
 const defaultContractDates = (() => {
   if (props.contract) return null;
 
@@ -638,6 +650,42 @@ watch(
     form.total_classes = pkg.quantity_classes;
     form.enrollment_fee = pkg.contract_register_value;
     form.package_value = pkg.contract_value;
+    form.weekday = pkg.weekday ?? null;
+    form.start_time = trimTime(pkg.start_time);
+    form.end_time = calculateEndTime(
+      form.start_time,
+      pkg.class_duration_minutes ?? 120,
+    );
+    form.second_weekday = pkg.second_weekday ?? null;
+    form.second_start_time = trimTime(pkg.second_start_time);
+    form.second_end_time = calculateEndTime(
+      form.second_start_time,
+      pkg.class_duration_minutes ?? 120,
+    );
+  },
+);
+
+watch(
+  () => form.start_time,
+  (startTime) => {
+    const pkg = packages.value.find((p) => p.id === form.package_id);
+    const calculated = calculateEndTime(
+      startTime,
+      pkg?.class_duration_minutes ?? 120,
+    );
+    if (calculated) form.end_time = calculated;
+  },
+);
+
+watch(
+  () => form.second_start_time,
+  (startTime) => {
+    const pkg = packages.value.find((p) => p.id === form.package_id);
+    const calculated = calculateEndTime(
+      startTime,
+      pkg?.class_duration_minutes ?? 120,
+    );
+    if (calculated) form.second_end_time = calculated;
   },
 );