|
|
@@ -0,0 +1,280 @@
|
|
|
+<template>
|
|
|
+ <div>
|
|
|
+ <DefaultHeaderPage />
|
|
|
+
|
|
|
+ <q-tabs v-model="tab" align="left" class="text-primary q-mb-md" no-caps>
|
|
|
+ <q-tab name="send" icon="mdi-send" :label="$t('push_notification.tabs.send')" />
|
|
|
+ <q-tab name="history" icon="mdi-history" :label="$t('push_notification.tabs.history')" />
|
|
|
+ </q-tabs>
|
|
|
+
|
|
|
+ <q-tab-panels v-model="tab" animated class="bg-transparent">
|
|
|
+ <q-tab-panel name="send" class="q-pa-none">
|
|
|
+ <div class="row q-col-gutter-md">
|
|
|
+ <div class="col-12 col-md-7">
|
|
|
+ <q-form ref="formRef" class="q-gutter-md">
|
|
|
+ <q-btn-toggle
|
|
|
+ v-model="form.target"
|
|
|
+ spread
|
|
|
+ no-caps
|
|
|
+ unelevated
|
|
|
+ toggle-color="primary"
|
|
|
+ color="white"
|
|
|
+ text-color="primary"
|
|
|
+ :options="targetOptions"
|
|
|
+ />
|
|
|
+
|
|
|
+ <PushRecipientsSelect
|
|
|
+ v-model="form.recipients"
|
|
|
+ outlined
|
|
|
+ dense
|
|
|
+ :target="form.target"
|
|
|
+ :error="!!serverErrors?.user_ids"
|
|
|
+ :error-message="serverErrors?.user_ids"
|
|
|
+ />
|
|
|
+
|
|
|
+ <q-input
|
|
|
+ v-model="form.title"
|
|
|
+ outlined
|
|
|
+ dense
|
|
|
+ counter
|
|
|
+ maxlength="65"
|
|
|
+ :label="$t('push_notification.fields.title')"
|
|
|
+ :rules="[inputRules.required]"
|
|
|
+ :error="!!serverErrors?.title"
|
|
|
+ :error-message="serverErrors?.title"
|
|
|
+ />
|
|
|
+
|
|
|
+ <q-input
|
|
|
+ v-model="form.body"
|
|
|
+ outlined
|
|
|
+ dense
|
|
|
+ counter
|
|
|
+ type="textarea"
|
|
|
+ maxlength="240"
|
|
|
+ autogrow
|
|
|
+ :label="$t('push_notification.fields.body')"
|
|
|
+ :rules="[inputRules.required]"
|
|
|
+ :error="!!serverErrors?.body"
|
|
|
+ :error-message="serverErrors?.body"
|
|
|
+ />
|
|
|
+
|
|
|
+ <div class="flex justify-end">
|
|
|
+ <q-btn
|
|
|
+ color="primary"
|
|
|
+ padding="12px 24px"
|
|
|
+ unelevated
|
|
|
+ no-caps
|
|
|
+ :loading="loading"
|
|
|
+ :disable="!canSend"
|
|
|
+ :label="$t('push_notification.actions.send')"
|
|
|
+ @click="onSendClick"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </q-form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="col-12 col-md-5">
|
|
|
+ <div class="text-caption text-grey-7 q-mb-sm">
|
|
|
+ {{ $t("push_notification.messages.preview") }}
|
|
|
+ </div>
|
|
|
+ <q-card flat bordered class="q-pa-md">
|
|
|
+ <div class="flex items-center q-mb-sm" style="gap: 8px">
|
|
|
+ <q-icon name="mdi-bell" size="18px" color="grey-7" />
|
|
|
+ <span class="text-caption text-grey-7">{{ appName }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="text-weight-bold">
|
|
|
+ {{ form.title || $t("push_notification.fields.title") }}
|
|
|
+ </div>
|
|
|
+ <div class="text-body2 text-grey-8" style="white-space: pre-wrap">
|
|
|
+ {{ form.body || $t("push_notification.fields.body") }}
|
|
|
+ </div>
|
|
|
+ </q-card>
|
|
|
+
|
|
|
+ <q-banner v-if="warnings.length" dense class="bg-orange-1 text-orange-9 q-mt-md">
|
|
|
+ <template #avatar>
|
|
|
+ <q-icon name="mdi-alert-outline" />
|
|
|
+ </template>
|
|
|
+ <div v-for="warning in warnings" :key="warning">{{ warning }}</div>
|
|
|
+ </q-banner>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </q-tab-panel>
|
|
|
+
|
|
|
+ <q-tab-panel name="history" class="q-pa-none">
|
|
|
+ <DefaultTableServerSide
|
|
|
+ ref="historyTableRef"
|
|
|
+ :columns="historyColumns"
|
|
|
+ :api-call="getPushHistory"
|
|
|
+ :add-item="false"
|
|
|
+ :rows-per-page="10"
|
|
|
+ open-item
|
|
|
+ @on-row-click="onHistoryRowClick"
|
|
|
+ />
|
|
|
+ </q-tab-panel>
|
|
|
+ </q-tab-panels>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import {
|
|
|
+ computed,
|
|
|
+ defineAsyncComponent,
|
|
|
+ nextTick,
|
|
|
+ reactive,
|
|
|
+ ref,
|
|
|
+ useTemplateRef,
|
|
|
+ watch,
|
|
|
+} from "vue";
|
|
|
+import { useQuasar } from "quasar";
|
|
|
+import { useI18n } from "vue-i18n";
|
|
|
+
|
|
|
+import { getPushHistory, sendManualPush } from "src/api/pushNotification";
|
|
|
+import { useInputRules } from "src/composables/useInputRules";
|
|
|
+import { useSubmitHandler } from "src/composables/useSubmitHandler";
|
|
|
+import { permissionStore } from "src/stores/permission";
|
|
|
+
|
|
|
+import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
|
|
|
+import DefaultTableServerSide from "src/components/defaults/DefaultTableServerSide.vue";
|
|
|
+import PushRecipientsSelect from "src/components/pushNotification/PushRecipientsSelect.vue";
|
|
|
+
|
|
|
+const PushHistoryDetailDialog = defineAsyncComponent(() =>
|
|
|
+ import("src/pages/pushNotification/components/PushHistoryDetailDialog.vue"),
|
|
|
+);
|
|
|
+
|
|
|
+const $q = useQuasar();
|
|
|
+const { t } = useI18n();
|
|
|
+const inputRules = useInputRules();
|
|
|
+const permission_store = permissionStore();
|
|
|
+
|
|
|
+const appName = "Diária";
|
|
|
+
|
|
|
+const tab = ref("send");
|
|
|
+const formRef = useTemplateRef("formRef");
|
|
|
+const historyTableRef = useTemplateRef("historyTableRef");
|
|
|
+
|
|
|
+watch(tab, async (value) => {
|
|
|
+ if (value !== "history") return;
|
|
|
+
|
|
|
+ await nextTick();
|
|
|
+ historyTableRef.value?.refresh();
|
|
|
+});
|
|
|
+
|
|
|
+const form = reactive({
|
|
|
+ target: "cliente",
|
|
|
+ recipients: [],
|
|
|
+ title: "",
|
|
|
+ body: "",
|
|
|
+});
|
|
|
+
|
|
|
+const targetOptions = computed(() => [
|
|
|
+ { label: t("push_notification.targets.cliente"), value: "cliente" },
|
|
|
+ { label: t("push_notification.targets.prestador"), value: "prestador" },
|
|
|
+]);
|
|
|
+
|
|
|
+const canSend = computed(
|
|
|
+ () => form.recipients.length > 0 && !!form.title.trim() && !!form.body.trim(),
|
|
|
+);
|
|
|
+
|
|
|
+const warnings = computed(() => {
|
|
|
+ const list = [];
|
|
|
+ const noToken = form.recipients.filter((r) => !r.hasDeviceToken).length;
|
|
|
+ const disabled = form.recipients.filter((r) => !r.pushEnabled).length;
|
|
|
+
|
|
|
+ if (noToken) {
|
|
|
+ list.push(t("push_notification.messages.warn_no_device_token", { count: noToken }));
|
|
|
+ }
|
|
|
+ if (disabled) {
|
|
|
+ list.push(t("push_notification.messages.warn_push_disabled", { count: disabled }));
|
|
|
+ }
|
|
|
+ return list;
|
|
|
+});
|
|
|
+
|
|
|
+const resetForm = () => {
|
|
|
+ form.recipients = [];
|
|
|
+ form.title = "";
|
|
|
+ form.body = "";
|
|
|
+ formRef.value?.resetValidation();
|
|
|
+};
|
|
|
+
|
|
|
+const { loading, serverErrors, execute: submitForm } = useSubmitHandler({
|
|
|
+ formRef,
|
|
|
+ onSuccess: () => {
|
|
|
+ resetForm();
|
|
|
+ historyTableRef.value?.refresh();
|
|
|
+ },
|
|
|
+});
|
|
|
+
|
|
|
+const onSendClick = () => {
|
|
|
+ if (permission_store.getAccess("push.notification", "add") === false) {
|
|
|
+ $q.notify({ type: "negative", message: t("validation.permissions.add") });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ $q.dialog({
|
|
|
+ title: t("push_notification.actions.send"),
|
|
|
+ message: t("push_notification.messages.confirm_send", {
|
|
|
+ count: form.recipients.length,
|
|
|
+ }),
|
|
|
+ cancel: true,
|
|
|
+ persistent: true,
|
|
|
+ }).onOk(async () => {
|
|
|
+ try {
|
|
|
+ await submitForm(() =>
|
|
|
+ sendManualPush({
|
|
|
+ target: form.target,
|
|
|
+ userIds: form.recipients.map((r) => r.value),
|
|
|
+ title: form.title,
|
|
|
+ body: form.body,
|
|
|
+ }),
|
|
|
+ );
|
|
|
+ } catch {
|
|
|
+ // erros de validação já são exibidos pelo useSubmitHandler
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const onHistoryRowClick = ({ row }) => {
|
|
|
+ $q.dialog({
|
|
|
+ component: PushHistoryDetailDialog,
|
|
|
+ componentProps: { log: row },
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const historyColumns = computed(() => [
|
|
|
+ {
|
|
|
+ name: "title",
|
|
|
+ label: t("push_notification.fields.title"),
|
|
|
+ field: "title",
|
|
|
+ align: "left",
|
|
|
+ sortable: false,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "user_name",
|
|
|
+ label: t("push_notification.fields.recipient"),
|
|
|
+ field: "user_name",
|
|
|
+ align: "left",
|
|
|
+ sortable: false,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "user_email",
|
|
|
+ label: t("common.terms.email"),
|
|
|
+ field: "user_email",
|
|
|
+ align: "left",
|
|
|
+ sortable: false,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "target",
|
|
|
+ label: t("push_notification.fields.target"),
|
|
|
+ field: (row) => t(`push_notification.targets.${row.target}`, row.target),
|
|
|
+ align: "left",
|
|
|
+ sortable: false,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "sent_at",
|
|
|
+ label: t("push_notification.fields.sent_at"),
|
|
|
+ field: "sent_at",
|
|
|
+ align: "left",
|
|
|
+ sortable: false,
|
|
|
+ },
|
|
|
+]);
|
|
|
+</script>
|