Newer
Older
gn-synapse / frontend / src / views / TypesView.vue
<script setup>
// Реестр типов: (source, subject, action) — что источники могут присылать.
import { computed, onMounted, ref } from "vue";
import {
  GnButton,
  GnConfirmDialog,
  GnIconButton,
  GnInput,
  GnLoader,
  GnModal,
  GnPageHeader,
  GnSelect,
  GnStatusCard,
  GnTable,
} from "gnexus-ui-kit/vue";
import { useToast } from "gnexus-ui-kit/vue";
import { api } from "../api.js";
import { t } from "../i18n/index.js";
import { fmtTime } from "../ui.js";

const toast = useToast();

const rows = ref([]);
const sources = ref([]);
const loading = ref(true);
const error = ref("");

async function load() {
  loading.value = true;
  error.value = "";
  try {
    const [types, src] = await Promise.all([api.listTypes(), api.listSources()]);
    rows.value = types;
    sources.value = src;
  } catch (ex) {
    error.value = ex.message;
  } finally {
    loading.value = false;
  }
}

const showCreate = ref(false);
const form = ref({ source_id: "", subject: "", action: "" });
const busy = ref(false);

const sourceOptions = computed(() => [{ value: "", label: t("types.pickSource") }]);

async function createType() {
  if (!form.value.source_id || !form.value.subject || !form.value.action) return;
  busy.value = true;
  try {
    await api.createType({
      source_id: Number(form.value.source_id),
      subject: form.value.subject.trim(),
      action: form.value.action.trim(),
    });
    showCreate.value = false;
    form.value = { source_id: "", subject: "", action: "" };
    toast.success(t("types.toastCreated"));
    await load();
  } catch (ex) {
    toast.error(ex.message);
  } finally {
    busy.value = false;
  }
}

const confirmDelete = ref(null);
async function doDelete() {
  try {
    await api.deleteType(confirmDelete.value.id);
    toast.success(t("types.toastDeleted"));
    await load();
  } catch (ex) {
    toast.error(ex.message);
  } finally {
    confirmDelete.value = null;
  }
}

onMounted(load);
</script>

<template>
  <main class="container page">
    <GnPageHeader
      kicker="Synapse"
      :title="t('types.pageTitle')"
      :subtitle="t('types.pageSubtitle')"
    >
      <template #actions>
        <GnButton variant="primary" icon="ph-plus" @click="showCreate = true">{{ t("types.createButton") }}</GnButton>
      </template>
    </GnPageHeader>

    <GnLoader v-if="loading" circle :label="t('types.loading')" />
    <GnStatusCard v-else-if="error" variant="error" icon="ph-warning-circle" :text="error" />
    <template v-else>
      <GnTable
        :rows="rows"
        :empty-text="t('types.empty')"
        :columns="[
          { key: 'source_name', label: t('types.colSource') },
          { key: 'subject', label: t('types.colSubject') },
          { key: 'action', label: t('types.colAction') },
          { key: 'created_at', label: t('types.colCreated') },
          { key: 'actions', label: '' },
        ]"
      >
        <template #cell-created_at="{ value }">{{ fmtTime(value) }}</template>
        <template #cell-actions="{ row }">
          <GnIconButton icon="ph-trash" size="sm" :label="t('types.rowDelete')" @click="confirmDelete = row" />
        </template>
      </GnTable>
    </template>

    <GnModal v-model:open="showCreate" :title="t('types.createTitle')">
      <div class="form">
        <GnSelect
          v-model="form.source_id"
          :label="t('types.formSourceLabel')"
          :options="sourceOptions.concat(sources.map((s) => ({ value: s.id, label: s.name })))"
        />
        <GnInput v-model="form.subject" :label="t('types.colSubject')" icon="ph-shapes" :help="t('types.formSubjectHelp')" />
        <GnInput v-model="form.action" :label="t('types.colAction')" icon="ph-lightning" :help="t('types.formActionHelp')" />
      </div>
      <template #actions>
        <GnButton variant="secondary" @click="showCreate = false">{{ t("types.cancel") }}</GnButton>
        <GnButton
          variant="primary"
          :loading="busy"
          :disabled="!form.source_id || !form.subject.trim() || !form.action.trim()"
          @click="createType"
        >
          {{ t("types.create") }}
        </GnButton>
      </template>
    </GnModal>

    <GnConfirmDialog
      :open="confirmDelete !== null" @update:open="(v) => (v ? null : (confirmDelete = null))"
      :key="confirmDelete?.id"
      :title="t('types.confirmTitle')"
      :message="confirmDelete ? t('types.confirmMessage', { name: confirmDelete.source_name, subject: confirmDelete.subject, action: confirmDelete.action }) : ''"
      confirm-variant="danger"
      :confirm-text="t('types.delete')"
      :cancel-text="t('types.cancel')"
      @confirm="doDelete"
    />
  </main>
</template>

<style scoped>
.page {
  max-width: 1440px;
  margin-inline: auto;
  padding-block: var(--gn-space-6);
  display: flex;
  flex-direction: column;
  gap: var(--gn-space-4);
}
.form {
  display: flex;
  flex-direction: column;
  gap: var(--gn-space-4);
}
</style>