Added lint and format scripts
This commit is contained in:
parent
689554d78a
commit
794fb59842
10
.eslintrc.cjs
Normal file
10
.eslintrc.cjs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
extends: ["eslint:recommended", "plugin:vue/vue3-recommended", "prettier"],
|
||||||
|
rules: {
|
||||||
|
// override/add rules settings here, such as:
|
||||||
|
// 'vue/no-unused-vars': 'error'
|
||||||
|
},
|
||||||
|
};
|
||||||
1
.prettierrc.json
Normal file
1
.prettierrc.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@ -1,24 +1,31 @@
|
|||||||
# client
|
# client
|
||||||
|
|
||||||
|
The is a single page application built using Vite (https://vitejs.dev/)
|
||||||
|
|
||||||
## Project setup
|
## Project setup
|
||||||
|
|
||||||
```
|
```
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
### Compiles and hot-reloads for development
|
### Compiles and hot-reloads for development
|
||||||
|
|
||||||
```
|
```
|
||||||
npm run serve
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### Compiles and minifies for production
|
### Compiles and minifies for production
|
||||||
|
|
||||||
```
|
```
|
||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
### Lints and fixes files
|
### Lints and fixes files
|
||||||
|
|
||||||
```
|
```
|
||||||
npm run lint
|
npm run lint
|
||||||
```
|
```
|
||||||
|
|
||||||
### Customize configuration
|
### Customize configuration
|
||||||
|
|
||||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||||
|
|||||||
2174
package-lock.json
generated
2174
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,9 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
|
||||||
|
"format": "prettier . --write"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"d3": "^7.8.5",
|
"d3": "^7.8.5",
|
||||||
@ -18,6 +20,10 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/d3-sankey": "^0.12.1",
|
"@types/d3-sankey": "^0.12.1",
|
||||||
"@vitejs/plugin-vue": "^3.1.0",
|
"@vitejs/plugin-vue": "^3.1.0",
|
||||||
|
"eslint": "^8.43.0",
|
||||||
|
"eslint-config-prettier": "^8.8.0",
|
||||||
|
"eslint-plugin-vue": "^9.15.1",
|
||||||
|
"prettier": "2.8.8",
|
||||||
"vite": "^3.1.0"
|
"vite": "^3.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
148
public/data.json
148
public/data.json
@ -1,75 +1,75 @@
|
|||||||
{
|
{
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"name": "Email",
|
"name": "Email",
|
||||||
"category": "Channel"
|
"category": "Channel"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Messaging",
|
"name": "Messaging",
|
||||||
"category": "Channel"
|
"category": "Channel"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Legacy Live Chat",
|
"name": "Legacy Live Chat",
|
||||||
"category": "Channel"
|
"category": "Channel"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Live Chat",
|
"name": "Live Chat",
|
||||||
"category": "Sub Channel"
|
"category": "Sub Channel"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Facebook Messenger",
|
"name": "Facebook Messenger",
|
||||||
"category": "Sub Channel"
|
"category": "Sub Channel"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Twitter DM",
|
"name": "Twitter DM",
|
||||||
"category": "Sub Channel"
|
"category": "Sub Channel"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "WhatsApp",
|
"name": "WhatsApp",
|
||||||
"category": "Sub Channel"
|
"category": "Sub Channel"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Other",
|
"name": "Other",
|
||||||
"category": "Sub Channel"
|
"category": "Sub Channel"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Default",
|
"name": "Default",
|
||||||
"category": "Queue"
|
"category": "Queue"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "General Enquires",
|
"name": "General Enquires",
|
||||||
"category": "Queue"
|
"category": "Queue"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Complaints",
|
"name": "Complaints",
|
||||||
"category": "Queue"
|
"category": "Queue"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Case Closed",
|
"name": "Case Closed",
|
||||||
"category": "Outcome"
|
"category": "Outcome"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Case Updated",
|
"name": "Case Updated",
|
||||||
"category": "Outcome"
|
"category": "Outcome"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Completed",
|
"name": "Completed",
|
||||||
"category": "Outcome"
|
"category": "Outcome"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Escalated to Manager",
|
"name": "Escalated to Manager",
|
||||||
"category": "Outcome"
|
"category": "Outcome"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "No Need for response",
|
"name": "No Need for response",
|
||||||
"category": "Outcome"
|
"category": "Outcome"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [
|
"links": [
|
||||||
{
|
{
|
||||||
"source": "Email",
|
"source": "Email",
|
||||||
"target": "Default",
|
"target": "Default",
|
||||||
"value": 1000
|
"value": 1000
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/App.vue
12
src/App.vue
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<router-link to="/">Home</router-link>
|
<router-link to="/">Home</router-link>
|
||||||
<router-link to="/about">About</router-link>
|
<router-link to="/about">About</router-link>
|
||||||
<router-link to="/referenceId">Reference ID Tracker</router-link>
|
<router-link to="/referenceId">Reference ID Tracker</router-link>
|
||||||
<router-link to="/interactionsSankey">Interaction Sankey</router-link>
|
<router-link to="/interactionsSankey">Interaction Sankey</router-link>
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,47 +1,50 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import HandledBy from './HandledBy.vue'
|
import HandledBy from "./HandledBy.vue";
|
||||||
import EmailInteraction from './EmailInteraction.vue'
|
import EmailInteraction from "./EmailInteraction.vue";
|
||||||
import DateTime from './DateTime.vue'
|
import DateTime from "./DateTime.vue";
|
||||||
import OutcomeList from './OutcomeList.vue'
|
import OutcomeList from "./OutcomeList.vue";
|
||||||
import CustomerList from './CustomerList.vue'
|
import CustomerList from "./CustomerList.vue";
|
||||||
import NotesList from './NotesList.vue'
|
import NotesList from "./NotesList.vue";
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const props = defineProps(["tableData"]);
|
const props = defineProps(["tableData"]);
|
||||||
console.log(props.tableData)
|
console.log(props.tableData);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Contact ID</th>
|
<th>Contact ID</th>
|
||||||
<th>Start Time</th>
|
<th>Start Time</th>
|
||||||
<th>End Time</th>
|
<th>End Time</th>
|
||||||
<th>Direction</th>
|
<th>Direction</th>
|
||||||
<th>Handled By</th>
|
<th>Handled By</th>
|
||||||
<th>Active Duration (s)</th>
|
<th>Active Duration (s)</th>
|
||||||
<th>Notes</th>
|
<th>Notes</th>
|
||||||
<th>Interaction</th>
|
<th>Interaction</th>
|
||||||
<th>Outcome</th>
|
<th>Outcome</th>
|
||||||
<th>Customer</th>
|
<th>Customer</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="td in tableData" :key="td">
|
<tr v-for="td in tableData" :key="td">
|
||||||
<td>{{ td.node.systemId }}</td>
|
<td>{{ td.node.systemId }}</td>
|
||||||
<td><DateTime :date="td.node.startTime"/></td>
|
<td><DateTime :date="td.node.startTime" /></td>
|
||||||
<td><DateTime :date="td.node.endTime"/></td>
|
<td><DateTime :date="td.node.endTime" /></td>
|
||||||
<td>{{ td.node.direction }}</td>
|
<td>{{ td.node.direction }}</td>
|
||||||
<td><HandledBy :handledBy="td.node.handledBy"/></td>
|
<td><HandledBy :handled-by="td.node.handledBy" /></td>
|
||||||
<td>{{ td.node.activeDuration }}</td>
|
<td>{{ td.node.activeDuration }}</td>
|
||||||
<td><NotesList :notes="td.node.notes"/></td>
|
<td><NotesList :notes="td.node.notes" /></td>
|
||||||
<td>
|
<td>
|
||||||
<EmailInteraction v-if="td.node.interaction.__typename === 'Email'" :email="td.node.interaction" />
|
<EmailInteraction
|
||||||
</td>
|
v-if="td.node.interaction.__typename === 'Email'"
|
||||||
<td><OutcomeList :outcomes="td.node.outcome"/></td>
|
:email="td.node.interaction"
|
||||||
<td><CustomerList :customers="td.node.customer"/></td>
|
/>
|
||||||
</tr>
|
</td>
|
||||||
</tbody>
|
<td><OutcomeList :outcomes="td.node.outcome" /></td>
|
||||||
</table>
|
<td><CustomerList :customers="td.node.customer" /></td>
|
||||||
</template>
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
|||||||
@ -4,65 +4,67 @@ defineProps(["summary"]);
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="summary" class="stats">
|
<div v-if="summary" class="stats">
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<div class="measure">{{ summary.totalCount }}</div>
|
<div class="measure">{{ summary.totalCount }}</div>
|
||||||
<div class="label">Contacts Found</div>
|
<div class="label">Contacts Found</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item">
|
|
||||||
<div class="measure">{{ summary.totalInboundCount }}</div>
|
|
||||||
<div class="label">INBOUND Contacts Found</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item">
|
|
||||||
<div class="measure">{{ summary.totalHTHours.toFixed(2) }}</div>
|
|
||||||
<div class="label">Total Handle Time (hours)</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item">
|
|
||||||
<div class="measure">{{ summary.activeHTMinutes.toFixed(2) }}</div>
|
|
||||||
<div class="label">Active Handle Time (minutes)</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item">
|
|
||||||
<div class="measure">{{ (summary.totalInboundActiveSeconds / 60).toFixed(2) }}</div>
|
|
||||||
<div class="label">Total [INBOUND] Active Time (minutes)</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="item">
|
||||||
|
<div class="measure">{{ summary.totalInboundCount }}</div>
|
||||||
|
<div class="label">INBOUND Contacts Found</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item">
|
||||||
|
<div class="measure">{{ summary.totalHTHours.toFixed(2) }}</div>
|
||||||
|
<div class="label">Total Handle Time (hours)</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item">
|
||||||
|
<div class="measure">{{ summary.activeHTMinutes.toFixed(2) }}</div>
|
||||||
|
<div class="label">Active Handle Time (minutes)</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item">
|
||||||
|
<div class="measure">
|
||||||
|
{{ (summary.totalInboundActiveSeconds / 60).toFixed(2) }}
|
||||||
|
</div>
|
||||||
|
<div class="label">Total [INBOUND] Active Time (minutes)</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style>
|
<style>
|
||||||
.stats {
|
.stats {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 2px solid rgb(57, 145, 201);
|
border: 2px solid rgb(57, 145, 201);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: #f7f9fc;
|
background-color: #f7f9fc;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.measure {
|
.measure {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
color: rgb(57, 145, 201);
|
color: rgb(57, 145, 201);
|
||||||
font-family: 'DejaVu Sans', Ariel, Helvetica, sans-serif;
|
font-family: "DejaVu Sans", Ariel, Helvetica, sans-serif;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-family: 'DejaVu Sans', Ariel, Helvetica, sans-serif;
|
font-family: "DejaVu Sans", Ariel, Helvetica, sans-serif;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,11 +1,20 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
defineProps(["customers"]);
|
defineProps(["customers"]);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div v-if="customers">
|
<div v-if="customers">
|
||||||
<ul v-for="customer in customers.edges" :key="customer.node.ref">{{ customer.node.firstName }} {{ customer.node.lastName }} ({{ customer.node.ref }})</ul>
|
<ul v-for="customer in customers.edges" :key="customer.node.ref">
|
||||||
</div>
|
{{
|
||||||
<div v-else>No Customer</div>
|
customer.node.firstName
|
||||||
</template>
|
}}
|
||||||
|
{{
|
||||||
|
customer.node.lastName
|
||||||
|
}}
|
||||||
|
({{
|
||||||
|
customer.node.ref
|
||||||
|
}})
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div v-else>No Customer</div>
|
||||||
|
</template>
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
// eslint-disable-next-line
|
const props = defineProps({ date: { type: String, required: true } });
|
||||||
const props = defineProps(["date"]);
|
|
||||||
|
|
||||||
const dateValue = new Date(props.date)
|
|
||||||
const date = dateValue.toLocaleDateString()
|
|
||||||
const time = dateValue.toLocaleTimeString()
|
|
||||||
|
|
||||||
|
const dateValue = new Date(props.date);
|
||||||
|
const localeDate = dateValue.toLocaleDateString();
|
||||||
|
const localeTime = dateValue.toLocaleTimeString();
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<p>{{ date }} {{ time }}</p>
|
<p>{{ localeDate }} {{ localeTime }}</p>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -3,14 +3,19 @@
|
|||||||
defineProps(["email"]);
|
defineProps(["email"]);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<p>Interaction ID {{ email.systemId }}</p>
|
<p>Interaction ID {{ email.systemId }}</p>
|
||||||
<p>To: <span v-for="address in email.toAddresses" :key="address">{{ address }} </span> </p>
|
<p>
|
||||||
<p>From: {{ email.fromAddress }}</p>
|
To:
|
||||||
<p>Subject: {{ email.subject }}</p>
|
<span v-for="address in email.toAddresses" :key="address"
|
||||||
<p>Thread ID: {{ email.threadId }}</p>
|
>{{ address }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p>From: {{ email.fromAddress }}</p>
|
||||||
|
<p>Subject: {{ email.subject }}</p>
|
||||||
|
<p>Thread ID: {{ email.threadId }}</p>
|
||||||
</template>
|
</template>
|
||||||
<style>
|
<style>
|
||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
|
|
||||||
// eslint-disable-next-line
|
|
||||||
defineProps(["errorMessage"]);
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<p v-if="errorMessage">Error: {{ errorMessage }}</p>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
9
src/components/ErrorMessage.vue
Normal file
9
src/components/ErrorMessage.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup>
|
||||||
|
// eslint-disable-next-line
|
||||||
|
defineProps(["message"]);
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<p v-if="message">Error: {{ message }}</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style></style>
|
||||||
@ -3,6 +3,6 @@
|
|||||||
defineProps(["handledBy"]);
|
defineProps(["handledBy"]);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<p v-if="handledBy">{{ handledBy.username }}</p>
|
<p v-if="handledBy">{{ handledBy.username }}</p>
|
||||||
<p v-else>unknown</p>
|
<p v-else>unknown</p>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
defineProps(["notes"]);
|
defineProps(["notes"]);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div v-if="notes">
|
<div v-if="notes">
|
||||||
<span v-for="note in notes.edges" :key="note">{{ note.node.text }}</span>
|
<span v-for="note in notes.edges" :key="note">{{ note.node.text }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
defineProps(["outcomes"]);
|
defineProps(["outcomes"]);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div v-if="outcomes">
|
<div v-if="outcomes">
|
||||||
<div v-for="outcome in outcomes.edges" :key="outcome">{{ outcome.node.text }} </div>
|
<div v-for="outcome in outcomes.edges" :key="outcome">
|
||||||
|
{{ outcome.node.text }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
|
</template>
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { createRouter, createWebHistory } from "vue-router";
|
import { createRouter, createWebHistory } from "vue-router";
|
||||||
import Home from "../views/Home.vue";
|
import HomeView from "../views/HomeView.vue";
|
||||||
import ReferenceID from "../views/ReferenceID.vue";
|
import ReferenSearchByReferenceViewceID from "../views/SearchByReferenceView.vue";
|
||||||
import InteractionsSankey from "../views/InteractionsSankey.vue";
|
import InteractionsFlowView from "../views/InteractionsFlowView.vue";
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
name: "home",
|
name: "home",
|
||||||
component: Home,
|
component: HomeView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/about",
|
path: "/about",
|
||||||
@ -16,13 +16,17 @@ const routes = [
|
|||||||
// this generates a separate chunk (about.[hash].js) for this route
|
// this generates a separate chunk (about.[hash].js) for this route
|
||||||
// which is lazy-loaded when the route is visited.
|
// which is lazy-loaded when the route is visited.
|
||||||
component: () =>
|
component: () =>
|
||||||
import(/* webpackChunkName: "about" */ "../views/About.vue"),
|
import(/* webpackChunkName: "about" */ "../views/AbouView.vue"),
|
||||||
},
|
},
|
||||||
{ path: "/referenceId", name: "referenceId", component: ReferenceID },
|
|
||||||
{
|
{
|
||||||
path: "/interactionsSankey",
|
path: "/referenceId",
|
||||||
name: "interactionsSankey",
|
name: "referenceId",
|
||||||
component: InteractionsSankey,
|
component: ReferenSearchByReferenceViewceID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/interactionsFlow",
|
||||||
|
name: "interactionsFlow",
|
||||||
|
component: InteractionsFlowView,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
209
src/style.css
209
src/style.css
@ -1,113 +1,114 @@
|
|||||||
h1 {
|
h1 {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
line-height: 19px;
|
line-height: 19px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 4px 0 12px 0;
|
padding: 4px 0 12px 0;
|
||||||
min-height: 23px;
|
min-height: 23px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
font-family: "Lato", sans-serif;
|
font-family: "Lato", sans-serif;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
border-bottom: 1px solid #c7c7c7;
|
border-bottom: 1px solid #c7c7c7;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 19px;
|
line-height: 19px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 4px 0 4px 0;
|
padding: 4px 0 4px 0;
|
||||||
min-height: 23px;
|
min-height: 23px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
font-family: "Lato", sans-serif;
|
font-family: "Lato", sans-serif;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
border-bottom: 1px solid #c7c7c7;
|
border-bottom: 1px solid #c7c7c7;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
border-bottom: 1px solid rgb(199, 199, 199);
|
border-bottom: 1px solid rgb(199, 199, 199);
|
||||||
display: block;
|
display: block;
|
||||||
font-family: "Lato", sans-serif;
|
font-family: "Lato", sans-serif;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 23px;
|
line-height: 23px;
|
||||||
min-height: 23px;
|
min-height: 23px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font: 12px/18px "OpenSans", Ariel, sans-serif;
|
font: 12px/18px "OpenSans", Ariel, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
color: rgb(51, 51, 51);
|
color: rgb(51, 51, 51);
|
||||||
display: block;
|
display: block;
|
||||||
font: normal normal normal normal 12px/18px OpenSans, Arial, sans-serif;
|
font: normal normal normal normal 12px/18px OpenSans, Arial, sans-serif;
|
||||||
font-variant: normal;
|
font-variant: normal;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
font: normal normal normal normal 12px/18px OpenSans, Arial, sans-serif;
|
font: normal normal normal normal 12px/18px OpenSans, Arial, sans-serif;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
th {
|
th {
|
||||||
background: rgb(238, 238, 238) none repeat-x scroll 0px 0px / auto padding-box border-box;
|
background: rgb(238, 238, 238) none repeat-x scroll 0px 0px / auto padding-box
|
||||||
border-right: 2px solid rgb(255, 255, 255);
|
border-box;
|
||||||
border-bottom: 1px solid rgb(255, 255, 255);
|
border-right: 2px solid rgb(255, 255, 255);
|
||||||
padding: 3px 6px 2px;
|
border-bottom: 1px solid rgb(255, 255, 255);
|
||||||
text-align: left;
|
padding: 3px 6px 2px;
|
||||||
vertical-align: bottom;
|
text-align: left;
|
||||||
|
vertical-align: bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr {
|
tr {
|
||||||
border-bottom: 1px solid rgb(199, 199, 199);
|
border-bottom: 1px solid rgb(199, 199, 199);
|
||||||
padding: 4px 0px 3px;
|
padding: 4px 0px 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
border-radius: 13px;
|
border-radius: 13px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
border-color: #515151;
|
border-color: #515151;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:focus {
|
input:focus {
|
||||||
border-color: #3395ff;
|
border-color: #3395ff;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
background-color: #ccc;
|
background-color: #ccc;
|
||||||
background-image: none;
|
background-image: none;
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
width: auto;
|
width: auto;
|
||||||
min-width: 0px;
|
min-width: 0px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
padding: 0 6px !important;
|
padding: 0 6px !important;
|
||||||
font-family: "OpenSans", Arial, sans-serif;
|
font-family: "OpenSans", Arial, sans-serif;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
box-shadow: 0 0 0 1px transparent inset;
|
box-shadow: 0 0 0 1px transparent inset;
|
||||||
writing-mode: horizontal-tb !important;
|
writing-mode: horizontal-tb !important;
|
||||||
appearance: auto;
|
appearance: auto;
|
||||||
text-rendering: auto;
|
text-rendering: auto;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* button {
|
/* button {
|
||||||
@ -123,60 +124,60 @@ button {
|
|||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#dfdfdf', GradientType=0);
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#dfdfdf', GradientType=0);
|
||||||
} */
|
} */
|
||||||
button:hover {
|
button:hover {
|
||||||
background-color: #eeeeee;
|
background-color: #eeeeee;
|
||||||
border-color: #007aff;
|
border-color: #007aff;
|
||||||
color: var(--font-black);
|
color: var(--font-black);
|
||||||
box-shadow: 0 1px 1px #ddd;
|
box-shadow: 0 1px 1px #ddd;
|
||||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
button:active,
|
button:active,
|
||||||
button:active:hover {
|
button:active:hover {
|
||||||
border-color: #007aff;
|
border-color: #007aff;
|
||||||
background-color: #d5d5d5;
|
background-color: #d5d5d5;
|
||||||
color: #007aff;
|
color: #007aff;
|
||||||
box-shadow: 1px 0 1px #ddd;
|
box-shadow: 1px 0 1px #ddd;
|
||||||
box-shadow: 1px 0 1px rgba(0, 0, 0, 0.3);
|
box-shadow: 1px 0 1px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
button[disabled],
|
button[disabled],
|
||||||
button[disabled]:hover,
|
button[disabled]:hover,
|
||||||
button[disabled]:active {
|
button[disabled]:active {
|
||||||
background-image: none;
|
background-image: none;
|
||||||
border-style: dashed;
|
border-style: dashed;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: #666;
|
color: #666;
|
||||||
border-color: #666;
|
border-color: #666;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
button:focus {
|
button:focus {
|
||||||
background-color: #eeeeee;
|
background-color: #eeeeee;
|
||||||
}
|
}
|
||||||
.themeLinkStyle {
|
.themeLinkStyle {
|
||||||
color: #007aff;
|
color: #007aff;
|
||||||
}
|
}
|
||||||
.themeLinkStyle:link,
|
.themeLinkStyle:link,
|
||||||
.themeLinkStyle:visited {
|
.themeLinkStyle:visited {
|
||||||
color: #007aff;
|
color: #007aff;
|
||||||
}
|
}
|
||||||
.themeLinkStyle:hover,
|
.themeLinkStyle:hover,
|
||||||
.themeLinkStyle:focus {
|
.themeLinkStyle:focus {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
.themeLinkStyle:active,
|
.themeLinkStyle:active,
|
||||||
.themeLinkStyle:active:hover {
|
.themeLinkStyle:active:hover {
|
||||||
background-color: #d5d5d5;
|
background-color: #d5d5d5;
|
||||||
}
|
}
|
||||||
.transparentButtonStyle {
|
.transparentButtonStyle {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
}
|
}
|
||||||
.transparentButtonStyle:hover {
|
.transparentButtonStyle:hover {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
.transparentButtonStyle:active {
|
.transparentButtonStyle:active {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
.transparentButtonStyle[disabled] {
|
.transparentButtonStyle[disabled] {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="about">
|
|
||||||
<h1>About</h1>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
5
src/views/AboutView.vue
Normal file
5
src/views/AboutView.vue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<div class="about">
|
||||||
|
<h1>About</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -1,12 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="home">
|
|
||||||
<h1>Engagement Orchestration - Microservice Examples</h1>
|
|
||||||
<h2>Introduction</h2>
|
|
||||||
<p>This site provides a set of microservices to augment Engagement Orchestration using the <a
|
|
||||||
href="https://em-docs.verint.com/15_3/em-integration/Content/AdaptiveFramework/Adaptive_Framework_Components.htm?Highlight=Adaptive%20Framework">Adaptive
|
|
||||||
Framework</a> Integration.</p>
|
|
||||||
<h2>Services Available</h2>
|
|
||||||
<h3><a href=/referenceId>Reference ID</a></h3>
|
|
||||||
<p>/referenceId </p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
18
src/views/HomeView.vue
Normal file
18
src/views/HomeView.vue
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<template>
|
||||||
|
<div class="home">
|
||||||
|
<h1>Engagement Orchestration - Microservice Examples</h1>
|
||||||
|
<h2>Introduction</h2>
|
||||||
|
<p>
|
||||||
|
This site provides a set of microservices to augment Engagement
|
||||||
|
Orchestration using the
|
||||||
|
<a
|
||||||
|
href="https://em-docs.verint.com/15_3/em-integration/Content/AdaptiveFramework/Adaptive_Framework_Components.htm?Highlight=Adaptive%20Framework"
|
||||||
|
>Adaptive Framework</a
|
||||||
|
>
|
||||||
|
Integration.
|
||||||
|
</p>
|
||||||
|
<h2>Services Available</h2>
|
||||||
|
<h3><a href="/referenceId">Reference ID</a></h3>
|
||||||
|
<p>/referenceId</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
353
src/views/InteractionsFlowView.vue
Normal file
353
src/views/InteractionsFlowView.vue
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
<script setup>
|
||||||
|
import * as d3 from "d3";
|
||||||
|
import { sankey, sankeyLinkHorizontal, sankeyLeft } from "d3-sankey";
|
||||||
|
import { onMounted } from "vue";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
name: "Email",
|
||||||
|
category: "Channel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Messaging",
|
||||||
|
category: "Channel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Legacy Live Chat",
|
||||||
|
category: "Channel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Live Chat",
|
||||||
|
category: "Sub Channel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Facebook Messenger",
|
||||||
|
category: "Sub Channel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Twitter DM",
|
||||||
|
category: "Sub Channel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "WhatsApp",
|
||||||
|
category: "Sub Channel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Other",
|
||||||
|
category: "Sub Channel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Default",
|
||||||
|
category: "Queue",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "General Enquires",
|
||||||
|
category: "Queue",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Complaints",
|
||||||
|
category: "Queue",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Case Closed",
|
||||||
|
category: "Outcome",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Case Updated",
|
||||||
|
category: "Outcome",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Completed",
|
||||||
|
category: "Outcome",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Escalated to Manager",
|
||||||
|
category: "Outcome",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No need for response",
|
||||||
|
category: "Outcome",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
source: "Email",
|
||||||
|
target: "Default",
|
||||||
|
value: 342,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "Messaging",
|
||||||
|
target: "Live Chat",
|
||||||
|
value: 232,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "Messaging",
|
||||||
|
target: "Facebook Messenger",
|
||||||
|
value: 623,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "Messaging",
|
||||||
|
target: "Twitter DM",
|
||||||
|
value: 434,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "Messaging",
|
||||||
|
target: "WhatsApp",
|
||||||
|
value: 1243,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "Messaging",
|
||||||
|
target: "Other",
|
||||||
|
value: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "Live Chat",
|
||||||
|
target: "Default",
|
||||||
|
value: 132,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "Live Chat",
|
||||||
|
target: "Complaints",
|
||||||
|
value: 90,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "Live Chat",
|
||||||
|
target: "General Enquires",
|
||||||
|
value: 42,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "WhatsApp",
|
||||||
|
target: "Default",
|
||||||
|
value: 343,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "WhatsApp",
|
||||||
|
target: "Complaints",
|
||||||
|
value: 300,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "WhatsApp",
|
||||||
|
target: "General Enquires",
|
||||||
|
value: 523,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "Facebook Messenger",
|
||||||
|
target: "Default",
|
||||||
|
value: 143,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "Facebook Messenger",
|
||||||
|
target: "Complaints",
|
||||||
|
value: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "Facebook Messenger",
|
||||||
|
target: "General Enquires",
|
||||||
|
value: 323,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "Twitter DM",
|
||||||
|
target: "Default",
|
||||||
|
value: 143,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "Twitter DM",
|
||||||
|
target: "Complaints",
|
||||||
|
value: 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "Twitter DM",
|
||||||
|
target: "General Enquires",
|
||||||
|
value: 223,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "General Enquires",
|
||||||
|
target: "Case Closed",
|
||||||
|
value: 421,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "General Enquires",
|
||||||
|
target: "Completed",
|
||||||
|
value: 612,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "General Enquires",
|
||||||
|
target: "Escalated to Manager",
|
||||||
|
value: 23,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "General Enquires",
|
||||||
|
target: "No need for response",
|
||||||
|
value: 241,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "Complaints",
|
||||||
|
target: "Case Closed",
|
||||||
|
value: 21,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "Complaints",
|
||||||
|
target: "Completed",
|
||||||
|
value: 12,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "Complaints",
|
||||||
|
target: "Escalated to Manager",
|
||||||
|
value: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "Complaints",
|
||||||
|
target: "No need for response",
|
||||||
|
value: 41,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "Default",
|
||||||
|
target: "Completed",
|
||||||
|
value: 41,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "Default",
|
||||||
|
target: "Case Updated",
|
||||||
|
value: 410,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "General Enquires",
|
||||||
|
target: "Case Updated",
|
||||||
|
value: 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "Complaints",
|
||||||
|
target: "Case Updated",
|
||||||
|
value: 410,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// Specify the dimensions of the chart.
|
||||||
|
const width = 928;
|
||||||
|
const height = 600;
|
||||||
|
const format = d3.format(",.0f");
|
||||||
|
|
||||||
|
const linkColor = "source-target"; //
|
||||||
|
// ["static", "#aaa"],
|
||||||
|
// ["source-target", "source-target"],
|
||||||
|
// ["source", "source"],
|
||||||
|
// ["target", "target"]
|
||||||
|
|
||||||
|
// Create a SVG container.
|
||||||
|
const svg = d3
|
||||||
|
.select("#chart")
|
||||||
|
.append("svg")
|
||||||
|
.attr("width", width)
|
||||||
|
.attr("height", height)
|
||||||
|
.attr("viewBox", [0, 0, width, height])
|
||||||
|
.attr("style", "max-width: 100%; height: auto; font: 10px sans-serif;");
|
||||||
|
|
||||||
|
// Constructs and configures a Sankey generator.
|
||||||
|
const sankeyGen = sankey()
|
||||||
|
.nodeId((d) => d.name)
|
||||||
|
.nodeAlign(sankeyLeft) // d3.sankeyLeft, etc.
|
||||||
|
.nodeWidth(15)
|
||||||
|
.nodePadding(10)
|
||||||
|
.extent([
|
||||||
|
[1, 5],
|
||||||
|
[width - 1, height - 5],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Applies it to the data. We make a copy of the nodes and links objects
|
||||||
|
// so as to avoid mutating the original.
|
||||||
|
const { nodes, links } = sankeyGen({
|
||||||
|
nodes: data.nodes.map((d) => Object.assign({}, d)),
|
||||||
|
links: data.links.map((d) => Object.assign({}, d)),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Defines a color scale.
|
||||||
|
const color = d3.scaleOrdinal(d3.schemeCategory10);
|
||||||
|
|
||||||
|
// Creates the rects that represent the nodes.
|
||||||
|
const rect = svg
|
||||||
|
.append("g")
|
||||||
|
.attr("stroke", "#000")
|
||||||
|
.selectAll()
|
||||||
|
.data(nodes)
|
||||||
|
.join("rect")
|
||||||
|
.attr("x", (d) => d.x0)
|
||||||
|
.attr("y", (d) => d.y0)
|
||||||
|
.attr("height", (d) => d.y1 - d.y0)
|
||||||
|
.attr("width", (d) => d.x1 - d.x0)
|
||||||
|
.attr("fill", (d) => color(d.category));
|
||||||
|
|
||||||
|
// Adds a title on the nodes.
|
||||||
|
rect.append("title").text((d) => `${d.name}\n${format(d.value)} TWh`);
|
||||||
|
|
||||||
|
// Creates the paths that represent the links.
|
||||||
|
const link = svg
|
||||||
|
.append("g")
|
||||||
|
.attr("fill", "none")
|
||||||
|
.attr("stroke-opacity", 0.5)
|
||||||
|
.selectAll()
|
||||||
|
.data(links)
|
||||||
|
.join("g")
|
||||||
|
.style("mix-blend-mode", "multiply");
|
||||||
|
|
||||||
|
// Creates a gradient, if necessary, for the source-target color option.
|
||||||
|
if (linkColor === "source-target") {
|
||||||
|
const gradient = link
|
||||||
|
.append("linearGradient")
|
||||||
|
.attr("id", (d) => (d.uid = uuidv4()))
|
||||||
|
.attr("gradientUnits", "userSpaceOnUse")
|
||||||
|
.attr("x1", (d) => d.source.x1)
|
||||||
|
.attr("x2", (d) => d.target.x0);
|
||||||
|
gradient
|
||||||
|
.append("stop")
|
||||||
|
.attr("offset", "0%")
|
||||||
|
.attr("stop-color", (d) => color(d.source.category));
|
||||||
|
gradient
|
||||||
|
.append("stop")
|
||||||
|
.attr("offset", "100%")
|
||||||
|
.attr("stop-color", (d) => color(d.target.category));
|
||||||
|
}
|
||||||
|
|
||||||
|
link
|
||||||
|
.append("path")
|
||||||
|
.attr("d", sankeyLinkHorizontal())
|
||||||
|
.attr(
|
||||||
|
"stroke",
|
||||||
|
linkColor === "source-target"
|
||||||
|
? (d) => `url(#${d.uid})`
|
||||||
|
: linkColor === "source"
|
||||||
|
? (d) => color(d.source.category)
|
||||||
|
: linkColor === "target"
|
||||||
|
? (d) => color(d.target.category)
|
||||||
|
: linkColor
|
||||||
|
)
|
||||||
|
.attr("stroke-width", (d) => Math.max(1, d.width));
|
||||||
|
|
||||||
|
link
|
||||||
|
.append("title")
|
||||||
|
.text((d) => `${d.source.name} → ${d.target.name}\n${format(d.value)} TWh`);
|
||||||
|
|
||||||
|
// Adds labels on the nodes.
|
||||||
|
svg
|
||||||
|
.append("g")
|
||||||
|
.selectAll()
|
||||||
|
.data(nodes)
|
||||||
|
.join("text")
|
||||||
|
.attr("x", (d) => (d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6))
|
||||||
|
.attr("y", (d) => (d.y1 + d.y0) / 2)
|
||||||
|
.attr("dy", "0.35em")
|
||||||
|
.attr("text-anchor", (d) => (d.x0 < width / 2 ? "start" : "end"))
|
||||||
|
.text((d) => d.name);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div id="chart"></div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
svg {
|
||||||
|
padding-inline: max(2rem, calc(50% - 24rem));
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,337 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import * as d3 from 'd3'
|
|
||||||
import { sankey, sankeyLinkHorizontal, sankeyLeft} from 'd3-sankey';
|
|
||||||
import { onMounted } from 'vue'
|
|
||||||
import {v4 as uuidv4} from 'uuid'
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"name": "Email",
|
|
||||||
"category": "Channel"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Messaging",
|
|
||||||
"category": "Channel"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Legacy Live Chat",
|
|
||||||
"category": "Channel"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Live Chat",
|
|
||||||
"category": "Sub Channel"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Facebook Messenger",
|
|
||||||
"category": "Sub Channel"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Twitter DM",
|
|
||||||
"category": "Sub Channel"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "WhatsApp",
|
|
||||||
"category": "Sub Channel"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Other",
|
|
||||||
"category": "Sub Channel"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Default",
|
|
||||||
"category": "Queue"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "General Enquires",
|
|
||||||
"category": "Queue"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Complaints",
|
|
||||||
"category": "Queue"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Case Closed",
|
|
||||||
"category": "Outcome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Case Updated",
|
|
||||||
"category": "Outcome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Completed",
|
|
||||||
"category": "Outcome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Escalated to Manager",
|
|
||||||
"category": "Outcome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "No need for response",
|
|
||||||
"category": "Outcome"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"source": "Email",
|
|
||||||
"target": "Default",
|
|
||||||
"value": 342
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "Messaging",
|
|
||||||
"target": "Live Chat",
|
|
||||||
"value": 232
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "Messaging",
|
|
||||||
"target": "Facebook Messenger",
|
|
||||||
"value": 623
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "Messaging",
|
|
||||||
"target": "Twitter DM",
|
|
||||||
"value": 434
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "Messaging",
|
|
||||||
"target": "WhatsApp",
|
|
||||||
"value": 1243
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "Messaging",
|
|
||||||
"target": "Other",
|
|
||||||
"value": 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "Live Chat",
|
|
||||||
"target": "Default",
|
|
||||||
"value": 132
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "Live Chat",
|
|
||||||
"target": "Complaints",
|
|
||||||
"value": 90
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "Live Chat",
|
|
||||||
"target": "General Enquires",
|
|
||||||
"value": 42
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "WhatsApp",
|
|
||||||
"target": "Default",
|
|
||||||
"value": 343
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "WhatsApp",
|
|
||||||
"target": "Complaints",
|
|
||||||
"value": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "WhatsApp",
|
|
||||||
"target": "General Enquires",
|
|
||||||
"value": 523
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "Facebook Messenger",
|
|
||||||
"target": "Default",
|
|
||||||
"value": 143
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "Facebook Messenger",
|
|
||||||
"target": "Complaints",
|
|
||||||
"value": 200
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "Facebook Messenger",
|
|
||||||
"target": "General Enquires",
|
|
||||||
"value": 323
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "Twitter DM",
|
|
||||||
"target": "Default",
|
|
||||||
"value": 143
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "Twitter DM",
|
|
||||||
"target": "Complaints",
|
|
||||||
"value": 50
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "Twitter DM",
|
|
||||||
"target": "General Enquires",
|
|
||||||
"value": 223
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "General Enquires",
|
|
||||||
"target": "Case Closed",
|
|
||||||
"value": 421
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "General Enquires",
|
|
||||||
"target": "Completed",
|
|
||||||
"value": 612
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "General Enquires",
|
|
||||||
"target": "Escalated to Manager",
|
|
||||||
"value": 23
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "General Enquires",
|
|
||||||
"target": "No need for response",
|
|
||||||
"value": 241
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "Complaints",
|
|
||||||
"target": "Case Closed",
|
|
||||||
"value": 21
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "Complaints",
|
|
||||||
"target": "Completed",
|
|
||||||
"value": 12
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "Complaints",
|
|
||||||
"target": "Escalated to Manager",
|
|
||||||
"value": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "Complaints",
|
|
||||||
"target": "No need for response",
|
|
||||||
"value": 41
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "Default",
|
|
||||||
"target": "Completed",
|
|
||||||
"value": 41
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "Default",
|
|
||||||
"target": "Case Updated",
|
|
||||||
"value": 410
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "General Enquires",
|
|
||||||
"target": "Case Updated",
|
|
||||||
"value": 50
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "Complaints",
|
|
||||||
"target": "Case Updated",
|
|
||||||
"value": 410
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
// Specify the dimensions of the chart.
|
|
||||||
const width = 928;
|
|
||||||
const height = 600;
|
|
||||||
const format = d3.format(",.0f");
|
|
||||||
|
|
||||||
const linkColor = "source-target" //
|
|
||||||
// ["static", "#aaa"],
|
|
||||||
// ["source-target", "source-target"],
|
|
||||||
// ["source", "source"],
|
|
||||||
// ["target", "target"]
|
|
||||||
|
|
||||||
// Create a SVG container.
|
|
||||||
const svg = d3
|
|
||||||
.select("#chart").append("svg")
|
|
||||||
.attr("width", width)
|
|
||||||
.attr("height", height)
|
|
||||||
.attr("viewBox", [0, 0, width, height])
|
|
||||||
.attr("style", "max-width: 100%; height: auto; font: 10px sans-serif;");
|
|
||||||
|
|
||||||
// Constructs and configures a Sankey generator.
|
|
||||||
const sankeyGen = sankey()
|
|
||||||
.nodeId(d => d.name)
|
|
||||||
.nodeAlign(sankeyLeft) // d3.sankeyLeft, etc.
|
|
||||||
.nodeWidth(15)
|
|
||||||
.nodePadding(10)
|
|
||||||
.extent([[1, 5], [width - 1, height - 5]]);
|
|
||||||
|
|
||||||
|
|
||||||
// Applies it to the data. We make a copy of the nodes and links objects
|
|
||||||
// so as to avoid mutating the original.
|
|
||||||
const {nodes, links} = sankeyGen({
|
|
||||||
nodes: data.nodes.map(d => Object.assign({}, d)),
|
|
||||||
links: data.links.map(d => Object.assign({}, d))
|
|
||||||
});
|
|
||||||
|
|
||||||
// Defines a color scale.
|
|
||||||
const color = d3.scaleOrdinal(d3.schemeCategory10);
|
|
||||||
|
|
||||||
// Creates the rects that represent the nodes.
|
|
||||||
const rect = svg.append("g")
|
|
||||||
.attr("stroke", "#000")
|
|
||||||
.selectAll()
|
|
||||||
.data(nodes)
|
|
||||||
.join("rect")
|
|
||||||
.attr("x", d => d.x0)
|
|
||||||
.attr("y", d => d.y0)
|
|
||||||
.attr("height", d => d.y1 - d.y0)
|
|
||||||
.attr("width", d => d.x1 - d.x0)
|
|
||||||
.attr("fill", d => color(d.category));
|
|
||||||
|
|
||||||
// Adds a title on the nodes.
|
|
||||||
rect.append("title")
|
|
||||||
.text(d => `${d.name}\n${format(d.value)} TWh`);
|
|
||||||
|
|
||||||
// Creates the paths that represent the links.
|
|
||||||
const link = svg.append("g")
|
|
||||||
.attr("fill", "none")
|
|
||||||
.attr("stroke-opacity", 0.5)
|
|
||||||
.selectAll()
|
|
||||||
.data(links)
|
|
||||||
.join("g")
|
|
||||||
.style("mix-blend-mode", "multiply");
|
|
||||||
|
|
||||||
// Creates a gradient, if necessary, for the source-target color option.
|
|
||||||
if (linkColor === "source-target") {
|
|
||||||
const gradient = link.append("linearGradient")
|
|
||||||
.attr('id', (d) => (d.uid = uuidv4()))
|
|
||||||
.attr("gradientUnits", "userSpaceOnUse")
|
|
||||||
.attr("x1", d => d.source.x1)
|
|
||||||
.attr("x2", d => d.target.x0);
|
|
||||||
gradient.append("stop")
|
|
||||||
.attr("offset", "0%")
|
|
||||||
.attr("stop-color", d => color(d.source.category));
|
|
||||||
gradient.append("stop")
|
|
||||||
.attr("offset", "100%")
|
|
||||||
.attr("stop-color", d => color(d.target.category));
|
|
||||||
}
|
|
||||||
|
|
||||||
link.append("path")
|
|
||||||
.attr("d", sankeyLinkHorizontal())
|
|
||||||
.attr("stroke", linkColor === "source-target" ? (d) => `url(#${d.uid})`
|
|
||||||
: linkColor === "source" ? (d) => color(d.source.category)
|
|
||||||
: linkColor === "target" ? (d) => color(d.target.category)
|
|
||||||
: linkColor)
|
|
||||||
.attr("stroke-width", d => Math.max(1, d.width));
|
|
||||||
|
|
||||||
link.append("title")
|
|
||||||
.text(d => `${d.source.name} → ${d.target.name}\n${format(d.value)} TWh`);
|
|
||||||
|
|
||||||
// Adds labels on the nodes.
|
|
||||||
svg.append("g")
|
|
||||||
.selectAll()
|
|
||||||
.data(nodes)
|
|
||||||
.join("text")
|
|
||||||
.attr("x", d => d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6)
|
|
||||||
.attr("y", d => (d.y1 + d.y0) / 2)
|
|
||||||
.attr("dy", "0.35em")
|
|
||||||
.attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end")
|
|
||||||
.text(d => d.name);
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div id="chart"></div>
|
|
||||||
</template>
|
|
||||||
<style scoped>
|
|
||||||
svg {
|
|
||||||
padding-inline: max(2rem, calc(50% - 24rem));
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
import ContactTable from '../components/ContactTable.vue'
|
|
||||||
import ContactsSummary from '../components/ContactsSummary.vue';
|
|
||||||
import Error from '../components/Error.vue';
|
|
||||||
|
|
||||||
const referenceId = ref('')
|
|
||||||
const contactData = ref(null)
|
|
||||||
const errorMessage = ref(null)
|
|
||||||
|
|
||||||
function onInput(e) {
|
|
||||||
referenceId.value = e.target.value
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchData() {
|
|
||||||
//clear errors
|
|
||||||
errorMessage.value = null
|
|
||||||
contactData.value = null
|
|
||||||
fetch(`${import.meta.env.VITE_EO_SERVICES_URL}/unified-data-gateway?referenceId=${ referenceId.value }`, {
|
|
||||||
credentials: "include" // fetch won't send cookies unless you set credentials
|
|
||||||
}).then (response => {
|
|
||||||
// check for error response
|
|
||||||
if (!response.ok) {
|
|
||||||
// get error message from body or default to response statusText
|
|
||||||
const error = data || response.statusText;
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
response.json().then((data) => {
|
|
||||||
contactData.value = data;
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.log(error)
|
|
||||||
errorMessage.value = error;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div id="app">
|
|
||||||
<h2>Search Criteria</h2>
|
|
||||||
<div>
|
|
||||||
<label for="referenceId">Reference ID: </label>
|
|
||||||
<input id="referenceId" :value="referenceId" @input="onInput" placeholder="enter Reference ID here" />
|
|
||||||
<button @click="fetchData">Search Contacts</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>Results</h2>
|
|
||||||
<ContactsSummary v-if="contactData" :summary="contactData.data.summary" />
|
|
||||||
<div v-else>No Contacts Found</div>
|
|
||||||
<ContactTable v-if="contactData" :tableData="contactData.data.findContactsCompletedBetween.edges" />
|
|
||||||
<Error v-if="errorMessage" :errorMessage="errorMessage" />
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<style scoped></style>
|
|
||||||
|
|
||||||
69
src/views/SearchByReferenceView.vue
Normal file
69
src/views/SearchByReferenceView.vue
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
import ContactTable from "../components/ContactTable.vue";
|
||||||
|
import ContactsSummary from "../components/ContactsSummary.vue";
|
||||||
|
import Error from "../components/Error.vue";
|
||||||
|
|
||||||
|
const referenceId = ref("");
|
||||||
|
const contactData = ref(null);
|
||||||
|
const errorMessage = ref(null);
|
||||||
|
|
||||||
|
function onInput(e) {
|
||||||
|
referenceId.value = e.target.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchData() {
|
||||||
|
//clear errors
|
||||||
|
errorMessage.value = null;
|
||||||
|
contactData.value = null;
|
||||||
|
fetch(
|
||||||
|
`${import.meta.env.VITE_EO_SERVICES_URL}/unified-data-gateway?referenceId=${
|
||||||
|
referenceId.value
|
||||||
|
}`,
|
||||||
|
{
|
||||||
|
credentials: "include", // fetch won't send cookies unless you set credentials
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
// check for error response
|
||||||
|
if (!response.ok) {
|
||||||
|
// get error message from body or default to response statusText
|
||||||
|
const error = response.data || response.statusText;
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
response.json().then((data) => {
|
||||||
|
contactData.value = data;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error);
|
||||||
|
errorMessage.value = error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
<h2>Search Criteria</h2>
|
||||||
|
<div>
|
||||||
|
<label for="referenceId">Reference ID: </label>
|
||||||
|
<input
|
||||||
|
id="referenceId"
|
||||||
|
:value="referenceId"
|
||||||
|
placeholder="enter Reference ID here"
|
||||||
|
@input="onInput"
|
||||||
|
/>
|
||||||
|
<button @click="fetchData">Search Contacts</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Results</h2>
|
||||||
|
<ContactsSummary v-if="contactData" :summary="contactData.data.summary" />
|
||||||
|
<div v-else>No Contacts Found</div>
|
||||||
|
<ContactTable
|
||||||
|
v-if="contactData"
|
||||||
|
:table-data="contactData.data.findContactsCompletedBetween.edges"
|
||||||
|
/>
|
||||||
|
<Error v-if="errorMessage" :error-message="errorMessage" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped></style>
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from "vite";
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from "@vitejs/plugin-vue";
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()]
|
plugins: [vue()],
|
||||||
})
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user