main
vue 154 lines 4.44 KB
Raw
1 <template>
2 <div class="customer-shuffle-integrations">
3 <transition name="fade" mode="out-in">
4 <div v-if="showForm" class="flex flex-col gap-4">
5 <h4>
6 {{ editingIntegration ? `Edit ${editingIntegration.display_name}` : "Add Shuffle integration" }}
7 </h4>
8 <CustomerShuffleIntegrationForm
9 :customer-code
10 :editing-integration
11 @submitted="onFormSubmitted"
12 @close="closeForm()"
13 />
14 </div>
15 <div v-else class="flex flex-col gap-4">
16 <div class="flex items-center justify-between gap-4">
17 <n-button size="small" type="primary" @click="openForm()">
18 <template #icon>
19 <Icon :name="AddIcon" :size="14" />
20 </template>
21 Add Shuffle integration
22 </n-button>
23 </div>
24
25 <n-alert
26 title="Shuffle integrations"
27 type="info"
28 class="[&_.n-alert-body\_\_content]:text-sm! [&_.n-alert-body\_\_title]:text-sm!"
29 >
30 Each row is one of this customer's Shuffle organizations. Routes set to the
31 <strong>Shuffle</strong>
32 channel reference one of these to pick the right org for outbound notifications. The deployment-wide
33 Shuffle API key lives in the CoPilot connectors table — these rows just record the per-customer
34 Org-Id.
35 </n-alert>
36
37 <n-spin :show="loading">
38 <div class="min-h-52">
39 <template v-if="list.length">
40 <CustomerShuffleIntegrationItem
41 v-for="integration of list"
42 :key="integration.id"
43 :integration
44 class="item-appear item-appear-bottom item-appear-005 mb-2"
45 @edit="openEdit(integration)"
46 @deleted="refreshList()"
47 @toggled="refreshList()"
48 @manage-apps="openAppsDrawer(integration)"
49 />
50 </template>
51 <template v-else>
52 <n-empty
53 v-if="!loading"
54 description="No Shuffle integrations configured. Add one to enable Shuffle-channel notification routes for this customer."
55 class="h-48 justify-center"
56 />
57 </template>
58 </div>
59 </n-spin>
60 </div>
61 </transition>
62
63 <CustomerShuffleAppsDrawer v-model:show="showAppsDrawer" :integration="appsDrawerIntegration" />
64 </div>
65 </template>
66
67 <script setup lang="ts">
68 import type { ShuffleIntegration } from "@/types/notifications.d"
69 import { NAlert, NButton, NEmpty, NSpin, useMessage } from "naive-ui"
70 import { onBeforeMount, ref } from "vue"
71 import Api from "@/api"
72 import Icon from "@/components/common/Icon.vue"
73 import { getApiErrorMessage } from "@/utils"
74 import CustomerShuffleAppsDrawer from "./CustomerShuffleAppsDrawer.vue"
75 import CustomerShuffleIntegrationForm from "./CustomerShuffleIntegrationForm.vue"
76 import CustomerShuffleIntegrationItem from "./CustomerShuffleIntegrationItem.vue"
77
78 const { customerCode } = defineProps<{
79 customerCode: string
80 }>()
81
82 const AddIcon = "carbon:add-alt"
83
84 const message = useMessage()
85 const showForm = ref(false)
86 const loading = ref(false)
87 const list = ref<ShuffleIntegration[]>([])
88 const editingIntegration = ref<ShuffleIntegration | null>(null)
89
90 // "Manage apps" drawer state. Opened from each integration row's
91 // catalog button — gives admins a Shuffle picker scoped to that org
92 // for authenticating new apps. Independent from the form drawer so
93 // both can be open at once if needed (e.g. edit metadata while the
94 // picker is still up after an OAuth handoff).
95 const showAppsDrawer = ref(false)
96 const appsDrawerIntegration = ref<ShuffleIntegration | null>(null)
97
98 function refreshList() {
99 loading.value = true
100 Api.notifications
101 .listShuffleIntegrations(customerCode)
102 .then(res => {
103 if (res.data.success) {
104 list.value = res.data.integrations
105 } else {
106 message.warning(res.data.message || "Failed to load Shuffle integrations")
107 }
108 })
109 .catch(err => {
110 message.error(getApiErrorMessage(err) || "Failed to load Shuffle integrations")
111 })
112 .finally(() => {
113 loading.value = false
114 })
115 }
116
117 function openForm() {
118 editingIntegration.value = null
119 showForm.value = true
120 }
121
122 function openEdit(integration: ShuffleIntegration) {
123 editingIntegration.value = integration
124 showForm.value = true
125 }
126
127 function closeForm() {
128 showForm.value = false
129 editingIntegration.value = null
130 }
131
132 function onFormSubmitted() {
133 closeForm()
134 refreshList()
135 }
136
137 function openAppsDrawer(integration: ShuffleIntegration) {
138 appsDrawerIntegration.value = integration
139 showAppsDrawer.value = true
140 }
141
142 onBeforeMount(refreshList)
143 </script>
144
145 <style lang="scss" scoped>
146 .fade-enter-active,
147 .fade-leave-active {
148 transition: opacity 0.2s ease-in-out;
149 }
150 .fade-enter-from,
151 .fade-leave-to {
152 opacity: 0;
153 }
154 </style>