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
|
||||
|
||||
The is a single page application built using Vite (https://vitejs.dev/)
|
||||
|
||||
## Project setup
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
|
||||
```
|
||||
npm run serve
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
|
||||
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": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
|
||||
"format": "prettier . --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"d3": "^7.8.5",
|
||||
@ -18,6 +20,10 @@
|
||||
"devDependencies": {
|
||||
"@types/d3-sankey": "^0.12.1",
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
|
||||
148
public/data.json
148
public/data.json
@ -1,75 +1,75 @@
|
||||
{
|
||||
"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": 1000
|
||||
}
|
||||
]
|
||||
}
|
||||
"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": 1000
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
12
src/App.vue
12
src/App.vue
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<router-link to="/">Home</router-link>
|
||||
<router-link to="/about">About</router-link>
|
||||
<router-link to="/referenceId">Reference ID Tracker</router-link>
|
||||
<router-link to="/interactionsSankey">Interaction Sankey</router-link>
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
<router-link to="/">Home</router-link>
|
||||
<router-link to="/about">About</router-link>
|
||||
<router-link to="/referenceId">Reference ID Tracker</router-link>
|
||||
<router-link to="/interactionsSankey">Interaction Sankey</router-link>
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
|
||||
@ -1,47 +1,50 @@
|
||||
<script setup>
|
||||
import HandledBy from './HandledBy.vue'
|
||||
import EmailInteraction from './EmailInteraction.vue'
|
||||
import DateTime from './DateTime.vue'
|
||||
import OutcomeList from './OutcomeList.vue'
|
||||
import CustomerList from './CustomerList.vue'
|
||||
import NotesList from './NotesList.vue'
|
||||
import HandledBy from "./HandledBy.vue";
|
||||
import EmailInteraction from "./EmailInteraction.vue";
|
||||
import DateTime from "./DateTime.vue";
|
||||
import OutcomeList from "./OutcomeList.vue";
|
||||
import CustomerList from "./CustomerList.vue";
|
||||
import NotesList from "./NotesList.vue";
|
||||
|
||||
// eslint-disable-next-line
|
||||
const props = defineProps(["tableData"]);
|
||||
console.log(props.tableData)
|
||||
console.log(props.tableData);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Contact ID</th>
|
||||
<th>Start Time</th>
|
||||
<th>End Time</th>
|
||||
<th>Direction</th>
|
||||
<th>Handled By</th>
|
||||
<th>Active Duration (s)</th>
|
||||
<th>Notes</th>
|
||||
<th>Interaction</th>
|
||||
<th>Outcome</th>
|
||||
<th>Customer</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="td in tableData" :key="td">
|
||||
<td>{{ td.node.systemId }}</td>
|
||||
<td><DateTime :date="td.node.startTime"/></td>
|
||||
<td><DateTime :date="td.node.endTime"/></td>
|
||||
<td>{{ td.node.direction }}</td>
|
||||
<td><HandledBy :handledBy="td.node.handledBy"/></td>
|
||||
<td>{{ td.node.activeDuration }}</td>
|
||||
<td><NotesList :notes="td.node.notes"/></td>
|
||||
<td>
|
||||
<EmailInteraction v-if="td.node.interaction.__typename === 'Email'" :email="td.node.interaction" />
|
||||
</td>
|
||||
<td><OutcomeList :outcomes="td.node.outcome"/></td>
|
||||
<td><CustomerList :customers="td.node.customer"/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Contact ID</th>
|
||||
<th>Start Time</th>
|
||||
<th>End Time</th>
|
||||
<th>Direction</th>
|
||||
<th>Handled By</th>
|
||||
<th>Active Duration (s)</th>
|
||||
<th>Notes</th>
|
||||
<th>Interaction</th>
|
||||
<th>Outcome</th>
|
||||
<th>Customer</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="td in tableData" :key="td">
|
||||
<td>{{ td.node.systemId }}</td>
|
||||
<td><DateTime :date="td.node.startTime" /></td>
|
||||
<td><DateTime :date="td.node.endTime" /></td>
|
||||
<td>{{ td.node.direction }}</td>
|
||||
<td><HandledBy :handled-by="td.node.handledBy" /></td>
|
||||
<td>{{ td.node.activeDuration }}</td>
|
||||
<td><NotesList :notes="td.node.notes" /></td>
|
||||
<td>
|
||||
<EmailInteraction
|
||||
v-if="td.node.interaction.__typename === 'Email'"
|
||||
:email="td.node.interaction"
|
||||
/>
|
||||
</td>
|
||||
<td><OutcomeList :outcomes="td.node.outcome" /></td>
|
||||
<td><CustomerList :customers="td.node.customer" /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
@ -4,65 +4,67 @@ defineProps(["summary"]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="summary" class="stats">
|
||||
<div class="item">
|
||||
<div class="measure">{{ summary.totalCount }}</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 v-if="summary" class="stats">
|
||||
<div class="item">
|
||||
<div class="measure">{{ summary.totalCount }}</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>
|
||||
</template>
|
||||
<style>
|
||||
.stats {
|
||||
display: flex;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 100%;
|
||||
border: 2px solid rgb(57, 145, 201);
|
||||
border-radius: 4px;
|
||||
background-color: #f7f9fc;
|
||||
font-weight: bold;
|
||||
white-space: normal;
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
border: 2px solid rgb(57, 145, 201);
|
||||
border-radius: 4px;
|
||||
background-color: #f7f9fc;
|
||||
font-weight: bold;
|
||||
white-space: normal;
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.measure {
|
||||
width: 100%;
|
||||
color: rgb(57, 145, 201);
|
||||
font-family: 'DejaVu Sans', Ariel, Helvetica, sans-serif;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
padding: 5px;
|
||||
width: 100%;
|
||||
color: rgb(57, 145, 201);
|
||||
font-family: "DejaVu Sans", Ariel, Helvetica, sans-serif;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 100%;
|
||||
font-family: 'DejaVu Sans', Ariel, Helvetica, sans-serif;
|
||||
font-size: 11px;
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
width: 100%;
|
||||
font-family: "DejaVu Sans", Ariel, Helvetica, sans-serif;
|
||||
font-size: 11px;
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,11 +1,20 @@
|
||||
<script setup>
|
||||
// eslint-disable-next-line
|
||||
defineProps(["customers"]);
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<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>
|
||||
</div>
|
||||
<div v-else>No Customer</div>
|
||||
</template>
|
||||
<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>
|
||||
</div>
|
||||
<div v-else>No Customer</div>
|
||||
</template>
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
<script setup>
|
||||
// eslint-disable-next-line
|
||||
const props = defineProps(["date"]);
|
||||
|
||||
const dateValue = new Date(props.date)
|
||||
const date = dateValue.toLocaleDateString()
|
||||
const time = dateValue.toLocaleTimeString()
|
||||
const props = defineProps({ date: { type: String, required: true } });
|
||||
|
||||
const dateValue = new Date(props.date);
|
||||
const localeDate = dateValue.toLocaleDateString();
|
||||
const localeTime = dateValue.toLocaleTimeString();
|
||||
</script>
|
||||
<template>
|
||||
<p>{{ date }} {{ time }}</p>
|
||||
</template>
|
||||
<p>{{ localeDate }} {{ localeTime }}</p>
|
||||
</template>
|
||||
|
||||
@ -3,14 +3,19 @@
|
||||
defineProps(["email"]);
|
||||
</script>
|
||||
<template>
|
||||
<p>Interaction ID {{ email.systemId }}</p>
|
||||
<p>To: <span v-for="address in email.toAddresses" :key="address">{{ address }} </span> </p>
|
||||
<p>From: {{ email.fromAddress }}</p>
|
||||
<p>Subject: {{ email.subject }}</p>
|
||||
<p>Thread ID: {{ email.threadId }}</p>
|
||||
<p>Interaction ID {{ email.systemId }}</p>
|
||||
<p>
|
||||
To:
|
||||
<span v-for="address in email.toAddresses" :key="address"
|
||||
>{{ address }}
|
||||
</span>
|
||||
</p>
|
||||
<p>From: {{ email.fromAddress }}</p>
|
||||
<p>Subject: {{ email.subject }}</p>
|
||||
<p>Thread ID: {{ email.threadId }}</p>
|
||||
</template>
|
||||
<style>
|
||||
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"]);
|
||||
</script>
|
||||
<template>
|
||||
<p v-if="handledBy">{{ handledBy.username }}</p>
|
||||
<p v-else>unknown</p>
|
||||
</template>
|
||||
<p v-if="handledBy">{{ handledBy.username }}</p>
|
||||
<p v-else>unknown</p>
|
||||
</template>
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
defineProps(["notes"]);
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="notes">
|
||||
<span v-for="note in notes.edges" :key="note">{{ note.node.text }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="notes">
|
||||
<span v-for="note in notes.edges" :key="note">{{ note.node.text }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
<script setup>
|
||||
// eslint-disable-next-line
|
||||
defineProps(["outcomes"]);
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="outcomes">
|
||||
<div v-for="outcome in outcomes.edges" :key="outcome">{{ outcome.node.text }} </div>
|
||||
<div v-if="outcomes">
|
||||
<div v-for="outcome in outcomes.edges" :key="outcome">
|
||||
{{ outcome.node.text }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import Home from "../views/Home.vue";
|
||||
import ReferenceID from "../views/ReferenceID.vue";
|
||||
import InteractionsSankey from "../views/InteractionsSankey.vue";
|
||||
import HomeView from "../views/HomeView.vue";
|
||||
import ReferenSearchByReferenceViewceID from "../views/SearchByReferenceView.vue";
|
||||
import InteractionsFlowView from "../views/InteractionsFlowView.vue";
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
name: "home",
|
||||
component: Home,
|
||||
component: HomeView,
|
||||
},
|
||||
{
|
||||
path: "/about",
|
||||
@ -16,13 +16,17 @@ const routes = [
|
||||
// this generates a separate chunk (about.[hash].js) for this route
|
||||
// which is lazy-loaded when the route is visited.
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "about" */ "../views/About.vue"),
|
||||
import(/* webpackChunkName: "about" */ "../views/AbouView.vue"),
|
||||
},
|
||||
{ path: "/referenceId", name: "referenceId", component: ReferenceID },
|
||||
{
|
||||
path: "/interactionsSankey",
|
||||
name: "interactionsSankey",
|
||||
component: InteractionsSankey,
|
||||
path: "/referenceId",
|
||||
name: "referenceId",
|
||||
component: ReferenSearchByReferenceViewceID,
|
||||
},
|
||||
{
|
||||
path: "/interactionsFlow",
|
||||
name: "interactionsFlow",
|
||||
component: InteractionsFlowView,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
209
src/style.css
209
src/style.css
@ -1,113 +1,114 @@
|
||||
h1 {
|
||||
font-size: 18px;
|
||||
line-height: 19px;
|
||||
overflow: hidden;
|
||||
padding: 4px 0 12px 0;
|
||||
min-height: 23px;
|
||||
vertical-align: middle;
|
||||
font-family: "Lato", sans-serif;
|
||||
font-weight: 400;
|
||||
border-bottom: 1px solid #c7c7c7;
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
line-height: 19px;
|
||||
overflow: hidden;
|
||||
padding: 4px 0 12px 0;
|
||||
min-height: 23px;
|
||||
vertical-align: middle;
|
||||
font-family: "Lato", sans-serif;
|
||||
font-weight: 400;
|
||||
border-bottom: 1px solid #c7c7c7;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
line-height: 19px;
|
||||
overflow: hidden;
|
||||
padding: 4px 0 4px 0;
|
||||
min-height: 23px;
|
||||
vertical-align: middle;
|
||||
font-family: "Lato", sans-serif;
|
||||
font-weight: 400;
|
||||
border-bottom: 1px solid #c7c7c7;
|
||||
font-size: 14px;
|
||||
line-height: 19px;
|
||||
overflow: hidden;
|
||||
padding: 4px 0 4px 0;
|
||||
min-height: 23px;
|
||||
vertical-align: middle;
|
||||
font-family: "Lato", sans-serif;
|
||||
font-weight: 400;
|
||||
border-bottom: 1px solid #c7c7c7;
|
||||
}
|
||||
|
||||
h3 {
|
||||
border-bottom: 1px solid rgb(199, 199, 199);
|
||||
display: block;
|
||||
font-family: "Lato", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 23px;
|
||||
min-height: 23px;
|
||||
overflow: hidden;
|
||||
border-bottom: 1px solid rgb(199, 199, 199);
|
||||
display: block;
|
||||
font-family: "Lato", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 23px;
|
||||
min-height: 23px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
font: 12px/18px "OpenSans", Ariel, sans-serif;
|
||||
font: 12px/18px "OpenSans", Ariel, sans-serif;
|
||||
}
|
||||
|
||||
p {
|
||||
color: rgb(51, 51, 51);
|
||||
display: block;
|
||||
font: normal normal normal normal 12px/18px OpenSans, Arial, sans-serif;
|
||||
font-variant: normal;
|
||||
text-align: left;
|
||||
visibility: visible;
|
||||
white-space: normal;
|
||||
color: rgb(51, 51, 51);
|
||||
display: block;
|
||||
font: normal normal normal normal 12px/18px OpenSans, Arial, sans-serif;
|
||||
font-variant: normal;
|
||||
text-align: left;
|
||||
visibility: visible;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
font: normal normal normal normal 12px/18px OpenSans, Arial, sans-serif;
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font: normal normal normal normal 12px/18px OpenSans, Arial, sans-serif;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
th {
|
||||
background: rgb(238, 238, 238) none repeat-x scroll 0px 0px / auto padding-box border-box;
|
||||
border-right: 2px solid rgb(255, 255, 255);
|
||||
border-bottom: 1px solid rgb(255, 255, 255);
|
||||
padding: 3px 6px 2px;
|
||||
text-align: left;
|
||||
vertical-align: bottom;
|
||||
background: rgb(238, 238, 238) none repeat-x scroll 0px 0px / auto padding-box
|
||||
border-box;
|
||||
border-right: 2px solid rgb(255, 255, 255);
|
||||
border-bottom: 1px solid rgb(255, 255, 255);
|
||||
padding: 3px 6px 2px;
|
||||
text-align: left;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
td {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
text-align: left;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
tr {
|
||||
border-bottom: 1px solid rgb(199, 199, 199);
|
||||
padding: 4px 0px 3px;
|
||||
border-bottom: 1px solid rgb(199, 199, 199);
|
||||
padding: 4px 0px 3px;
|
||||
}
|
||||
|
||||
input {
|
||||
border-radius: 13px;
|
||||
height: 24px;
|
||||
border-color: #515151;
|
||||
outline: none;
|
||||
border-radius: 13px;
|
||||
height: 24px;
|
||||
border-color: #515151;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
border-color: #3395ff;
|
||||
outline: none;
|
||||
border-color: #3395ff;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-left: 5px;
|
||||
color: #333333;
|
||||
background-color: #ccc;
|
||||
background-image: none;
|
||||
border: 2px solid transparent;
|
||||
height: 24px;
|
||||
line-height: 22px;
|
||||
white-space: nowrap;
|
||||
width: auto;
|
||||
min-width: 0px;
|
||||
font-size: 12px;
|
||||
padding: 0 6px !important;
|
||||
font-family: "OpenSans", Arial, sans-serif;
|
||||
text-align: center;
|
||||
background-repeat: no-repeat;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 0 0 1px transparent inset;
|
||||
writing-mode: horizontal-tb !important;
|
||||
appearance: auto;
|
||||
text-rendering: auto;
|
||||
align-items: flex-start;
|
||||
margin-left: 5px;
|
||||
color: #333333;
|
||||
background-color: #ccc;
|
||||
background-image: none;
|
||||
border: 2px solid transparent;
|
||||
height: 24px;
|
||||
line-height: 22px;
|
||||
white-space: nowrap;
|
||||
width: auto;
|
||||
min-width: 0px;
|
||||
font-size: 12px;
|
||||
padding: 0 6px !important;
|
||||
font-family: "OpenSans", Arial, sans-serif;
|
||||
text-align: center;
|
||||
background-repeat: no-repeat;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 0 0 1px transparent inset;
|
||||
writing-mode: horizontal-tb !important;
|
||||
appearance: auto;
|
||||
text-rendering: auto;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
/* button {
|
||||
@ -123,60 +124,60 @@ button {
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#dfdfdf', GradientType=0);
|
||||
} */
|
||||
button:hover {
|
||||
background-color: #eeeeee;
|
||||
border-color: #007aff;
|
||||
color: var(--font-black);
|
||||
box-shadow: 0 1px 1px #ddd;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3);
|
||||
background-color: #eeeeee;
|
||||
border-color: #007aff;
|
||||
color: var(--font-black);
|
||||
box-shadow: 0 1px 1px #ddd;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
button:active,
|
||||
button:active:hover {
|
||||
border-color: #007aff;
|
||||
background-color: #d5d5d5;
|
||||
color: #007aff;
|
||||
box-shadow: 1px 0 1px #ddd;
|
||||
box-shadow: 1px 0 1px rgba(0, 0, 0, 0.3);
|
||||
border-color: #007aff;
|
||||
background-color: #d5d5d5;
|
||||
color: #007aff;
|
||||
box-shadow: 1px 0 1px #ddd;
|
||||
box-shadow: 1px 0 1px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
button[disabled],
|
||||
button[disabled]:hover,
|
||||
button[disabled]:active {
|
||||
background-image: none;
|
||||
border-style: dashed;
|
||||
background-color: transparent;
|
||||
color: #666;
|
||||
border-color: #666;
|
||||
box-shadow: none;
|
||||
background-image: none;
|
||||
border-style: dashed;
|
||||
background-color: transparent;
|
||||
color: #666;
|
||||
border-color: #666;
|
||||
box-shadow: none;
|
||||
}
|
||||
button:focus {
|
||||
background-color: #eeeeee;
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
.themeLinkStyle {
|
||||
color: #007aff;
|
||||
color: #007aff;
|
||||
}
|
||||
.themeLinkStyle:link,
|
||||
.themeLinkStyle:visited {
|
||||
color: #007aff;
|
||||
color: #007aff;
|
||||
}
|
||||
.themeLinkStyle:hover,
|
||||
.themeLinkStyle:focus {
|
||||
color: #ffffff;
|
||||
color: #ffffff;
|
||||
}
|
||||
.themeLinkStyle:active,
|
||||
.themeLinkStyle:active:hover {
|
||||
background-color: #d5d5d5;
|
||||
background-color: #d5d5d5;
|
||||
}
|
||||
.transparentButtonStyle {
|
||||
background-color: transparent;
|
||||
border: 1px solid transparent;
|
||||
background-color: transparent;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.transparentButtonStyle:hover {
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
.transparentButtonStyle:active {
|
||||
background-color: transparent;
|
||||
background-color: transparent;
|
||||
}
|
||||
.transparentButtonStyle[disabled] {
|
||||
opacity: 0.6;
|
||||
cursor: default;
|
||||
opacity: 0.6;
|
||||
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 vue from '@vitejs/plugin-vue'
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()]
|
||||
})
|
||||
plugins: [vue()],
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user