|
|
@@ -0,0 +1,165 @@
|
|
|
+<template>
|
|
|
+ <q-dialog ref="dialogRef" @hide="onDialogHide">
|
|
|
+ <q-card class="q-dialog-plugin overflow-hidden" style="width: 720px; max-width: 92vw">
|
|
|
+ <DefaultDialogHeader :title="title" @close="handleClose" />
|
|
|
+
|
|
|
+ <q-card-section class="q-gutter-y-sm">
|
|
|
+ <div class="row items-center justify-between">
|
|
|
+ <div class="text-h6">{{ form.title }}</div>
|
|
|
+ <q-badge :color="statusColor(form.status)" :label="statusLabel(form.status)" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <q-list dense>
|
|
|
+ <q-item>
|
|
|
+ <q-item-section>
|
|
|
+ <q-item-label caption>{{ $t("support_request.fields.category") }}</q-item-label>
|
|
|
+ <q-item-label>{{ categoryLabel(form.category) }}</q-item-label>
|
|
|
+ </q-item-section>
|
|
|
+ <q-item-section>
|
|
|
+ <q-item-label caption>{{ $t("support_request.fields.user_type") }}</q-item-label>
|
|
|
+ <q-item-label>{{ userTypeLabel(form.user_type) }}</q-item-label>
|
|
|
+ </q-item-section>
|
|
|
+ </q-item>
|
|
|
+
|
|
|
+ <q-item>
|
|
|
+ <q-item-section>
|
|
|
+ <q-item-label caption>{{ $t("support_request.fields.name") }}</q-item-label>
|
|
|
+ <q-item-label>{{ form.name || emptyLabel }}</q-item-label>
|
|
|
+ </q-item-section>
|
|
|
+ <q-item-section>
|
|
|
+ <q-item-label caption>{{ $t("support_request.fields.email") }}</q-item-label>
|
|
|
+ <q-item-label>
|
|
|
+ <a v-if="form.email" :href="`mailto:${form.email}`">{{ form.email }}</a>
|
|
|
+ <span v-else>{{ emptyLabel }}</span>
|
|
|
+ </q-item-label>
|
|
|
+ </q-item-section>
|
|
|
+ <q-item-section>
|
|
|
+ <q-item-label caption>{{ $t("support_request.fields.phone") }}</q-item-label>
|
|
|
+ <q-item-label>
|
|
|
+ <a v-if="form.phone" :href="`tel:${form.phone}`">{{ form.phone }}</a>
|
|
|
+ <span v-else>{{ emptyLabel }}</span>
|
|
|
+ </q-item-label>
|
|
|
+ </q-item-section>
|
|
|
+ </q-item>
|
|
|
+
|
|
|
+ <q-item>
|
|
|
+ <q-item-section>
|
|
|
+ <q-item-label caption>{{ $t("common.terms.created_at") }}</q-item-label>
|
|
|
+ <q-item-label>{{ form.created_at }}</q-item-label>
|
|
|
+ </q-item-section>
|
|
|
+ <q-item-section v-if="form.handler">
|
|
|
+ <q-item-label caption>{{ $t("support_request.fields.handled_by") }}</q-item-label>
|
|
|
+ <q-item-label>{{ form.handler }}</q-item-label>
|
|
|
+ </q-item-section>
|
|
|
+ <q-item-section v-if="form.resolved_at">
|
|
|
+ <q-item-label caption>{{ $t("support_request.fields.resolved_at") }}</q-item-label>
|
|
|
+ <q-item-label>{{ form.resolved_at }}</q-item-label>
|
|
|
+ </q-item-section>
|
|
|
+ </q-item>
|
|
|
+ </q-list>
|
|
|
+
|
|
|
+ <div>
|
|
|
+ <div class="text-caption text-grey-7 q-mb-xs">
|
|
|
+ {{ $t("support_request.fields.message") }}
|
|
|
+ </div>
|
|
|
+ <div class="support-message">{{ form.message }}</div>
|
|
|
+ </div>
|
|
|
+ </q-card-section>
|
|
|
+
|
|
|
+ <q-card-actions align="right" class="q-px-md q-pb-md q-gutter-x-sm">
|
|
|
+ <q-btn :label="$t('common.actions.close')" flat color="primary" @click="handleClose" />
|
|
|
+ <q-btn
|
|
|
+ v-if="canEdit && form.status !== 'in_progress' && form.status !== 'resolved'"
|
|
|
+ color="blue"
|
|
|
+ unelevated
|
|
|
+ :label="$t('support_request.actions.mark_in_progress')"
|
|
|
+ :loading="loading"
|
|
|
+ @click="changeStatus('in_progress')"
|
|
|
+ />
|
|
|
+ <q-btn
|
|
|
+ v-if="canEdit && form.status !== 'resolved'"
|
|
|
+ color="green"
|
|
|
+ unelevated
|
|
|
+ :label="$t('support_request.actions.mark_resolved')"
|
|
|
+ :loading="loading"
|
|
|
+ @click="changeStatus('resolved')"
|
|
|
+ />
|
|
|
+ </q-card-actions>
|
|
|
+ </q-card>
|
|
|
+ </q-dialog>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, computed } from "vue";
|
|
|
+import { useDialogPluginComponent, useQuasar } from "quasar";
|
|
|
+import { useI18n } from "vue-i18n";
|
|
|
+import { permissionStore } from "src/stores/permission";
|
|
|
+import { updateSupportRequestStatus } from "src/api/supportRequest";
|
|
|
+import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
|
|
|
+
|
|
|
+defineEmits([...useDialogPluginComponent.emits]);
|
|
|
+
|
|
|
+const { supportRequest } = defineProps({
|
|
|
+ supportRequest: {
|
|
|
+ type: Object,
|
|
|
+ required: true,
|
|
|
+ },
|
|
|
+});
|
|
|
+
|
|
|
+const { t } = useI18n();
|
|
|
+const $q = useQuasar();
|
|
|
+const permission_store = permissionStore();
|
|
|
+
|
|
|
+const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent();
|
|
|
+
|
|
|
+const form = ref({ ...supportRequest });
|
|
|
+const loading = ref(false);
|
|
|
+const updated = ref(false);
|
|
|
+
|
|
|
+const title = () => t("ui.navigation.support_requests");
|
|
|
+const canEdit = computed(() => permission_store.getAccess("support.request", "edit") !== false);
|
|
|
+
|
|
|
+const emptyLabel = "—";
|
|
|
+const categoryLabel = (category) => t(`support_request.categories.${category}`, category);
|
|
|
+const statusLabel = (status) => t(`support_request.status.${status}`, status);
|
|
|
+const userTypeLabel = (type) =>
|
|
|
+ type ? t(`support_request.user_types.${type.toLowerCase()}`, type) : emptyLabel;
|
|
|
+const statusColor = (status) =>
|
|
|
+ ({ pending: "orange", in_progress: "blue", resolved: "green" }[status] || "grey");
|
|
|
+
|
|
|
+const changeStatus = async (status) => {
|
|
|
+ loading.value = true;
|
|
|
+ try {
|
|
|
+ const result = await updateSupportRequestStatus(form.value.id, status);
|
|
|
+ form.value = { ...form.value, ...result };
|
|
|
+ updated.value = true;
|
|
|
+ $q.notify({ type: "positive", message: t("common.ui.messages.updated") });
|
|
|
+ } catch (error) {
|
|
|
+ console.error(error);
|
|
|
+ $q.notify({ type: "negative", message: t("http.errors.failed") });
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const handleClose = () => {
|
|
|
+ if (updated.value) {
|
|
|
+ onDialogOK(true);
|
|
|
+ } else {
|
|
|
+ onDialogCancel();
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.support-message {
|
|
|
+ white-space: pre-wrap;
|
|
|
+ word-break: break-word;
|
|
|
+ background: #f5f5f7;
|
|
|
+ border-radius: 12px;
|
|
|
+ padding: 14px;
|
|
|
+ line-height: 1.5;
|
|
|
+ max-height: 320px;
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+</style>
|