pre-tailwind removal bc the snackbar ain't coloring right
This commit is contained in:
+13
-15
@@ -1,9 +1,10 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<v-app-bar density="comfortable" color="primary" v-if="!$route.meta.hideHeader">
|
||||
<v-app-bar-title>My App, TheDisasterUnfoldingBeforeOurVeryEyesTheSequel</v-app-bar-title>
|
||||
|
||||
<v-app-bar-title>My App, The Disaster Unfolding Before Our Very Eyes: The Sequel</v-app-bar-title>
|
||||
<v-btn variant="text" to="/">Home</v-btn>
|
||||
<v-btn varient="text" to="/dashboard">Dashboard</v-btn>
|
||||
<v-btn varient="text" to="/item">Item</v-btn>
|
||||
<v-btn varient="text" to="/kitchen-sink">Kitchen Sink</v-btn>
|
||||
<v-btn varient="text" to="/copilot-test">Copilot Test</v-btn>
|
||||
<v-btn icon @click="toggleTheme">
|
||||
@@ -29,31 +30,28 @@
|
||||
<v-main>
|
||||
<v-container fluid>
|
||||
<router-view />
|
||||
<AppSnackbar ref="snackbar" />
|
||||
</v-container>
|
||||
</v-main>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
import HelloWorld from '@/components/HelloWorld.vue'
|
||||
import { useDisplay, useTheme } from 'vuetify'
|
||||
import { ref, provide } from 'vue'
|
||||
|
||||
|
||||
const theme = useTheme()
|
||||
|
||||
function toggleTheme() {
|
||||
theme.global.name.value = theme.global.current.value.dark ? 'light' : 'dark'
|
||||
}
|
||||
</script>
|
||||
|
||||
<!--
|
||||
<HelloWorld />
|
||||
<v-btn
|
||||
class="m-2"
|
||||
icon="mdi-theme-light-dark"
|
||||
location="top right"
|
||||
position="absolute"
|
||||
@click="$vuetify.theme.cycle()"
|
||||
/>
|
||||
-->
|
||||
// Snackbar
|
||||
import AppSnackbar from './components/AppSnackbar.vue'
|
||||
|
||||
const snackbar = ref(null)
|
||||
provide('snackbar', snackbar)
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<v-snackbar
|
||||
v-model="show"
|
||||
:color="options.color ?? 'success'"
|
||||
:timeout="options.timeout ?? 5000"
|
||||
timer="bottom"
|
||||
variant="flat"
|
||||
theme="dark"
|
||||
timer-color="green"
|
||||
location="bottom center"
|
||||
rounded="lg"
|
||||
>
|
||||
<v-icon start>{{ options.icon ?? 'mdi-check-circle-outline' }}</v-icon>
|
||||
{{ options.message }} — color: {{ options.color }}
|
||||
</v-snackbar>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const show = ref(false)
|
||||
const options = ref({})
|
||||
|
||||
function toast(message, opts = {}) {
|
||||
options.value = { message, ...opts }
|
||||
show.value = true
|
||||
}
|
||||
|
||||
// Shorthand helpers
|
||||
const success = (msg, opts = {}) => toast(msg, { color: 'success', icon: 'mdi-check-circle-outline', ...opts })
|
||||
const error = (msg, opts = {}) => toast(msg, { color: 'error', icon: 'mdi-alert-circle-outline', ...opts })
|
||||
const warning = (msg, opts = {}) => toast(msg, { color: 'warning', icon: 'mdi-alert-outline', ...opts })
|
||||
const info = (msg, opts = {}) => toast(msg, { color: 'info', icon: 'mdi-information-outline', ...opts })
|
||||
|
||||
defineExpose({ toast, success, error, warning, info })
|
||||
</script>
|
||||
@@ -100,18 +100,4 @@
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@reference "../styles/tailwind.css";
|
||||
|
||||
/*
|
||||
1. mixing helper classes and @apply for demonstration purposes only
|
||||
2. the classes below are NOT wrapped in any CSS layer, so they "win" over everything else
|
||||
*/
|
||||
.hero-card {
|
||||
@apply py-3 md:pr-[120px] w-full transition-none;
|
||||
}
|
||||
|
||||
:deep(.v-card) {
|
||||
@apply bg-gray-200;
|
||||
@apply dark:bg-black dark:bg-linear-to-r dark:from-primary/50 dark:to-primary/30 dark:text-white/80;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import EventBus, { ACTIONS } from '../index'
|
||||
|
||||
export const showSnackbar = (message, options = {}) => {
|
||||
EventBus.emit(ACTIONS.SNACKBAR, { message, ...options })
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import mitt from 'mitt'
|
||||
|
||||
export const ACTIONS = {
|
||||
SNACKBAR: 'snackbar',
|
||||
}
|
||||
|
||||
const EventBus = mitt()
|
||||
export default EventBus
|
||||
@@ -8,12 +8,16 @@ import { createVuetify } from 'vuetify'
|
||||
import '@mdi/font/css/materialdesignicons.css'
|
||||
import '../styles/layers.css'
|
||||
import 'vuetify/styles'
|
||||
import { VDateInput } from 'vuetify/labs/VDateInput'
|
||||
|
||||
export default createVuetify({
|
||||
theme: {
|
||||
defaultTheme: 'system',
|
||||
utilities: false,
|
||||
},
|
||||
components: {
|
||||
VDateInput,
|
||||
},
|
||||
display: {
|
||||
mobileBreakpoint: 'md',
|
||||
thresholds: {
|
||||
|
||||
@@ -4,6 +4,8 @@ import KitchenSink from '../views/KitchenSink.vue'
|
||||
import CopilotTest from '../views/CopilotTest.vue'
|
||||
import Profile from '../views/Profile.vue'
|
||||
import SystemNotice from '../views/SystemNotice.vue'
|
||||
import Dashboard from '../views/Dashboard.vue'
|
||||
import Item from '../views/Item.vue'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
@@ -11,6 +13,27 @@ const routes = [
|
||||
name: 'home',
|
||||
component: HomeView
|
||||
},
|
||||
{
|
||||
path: '/system-notice',
|
||||
name: 'SystemNotice',
|
||||
component: SystemNotice,
|
||||
meta: { hideHeader: true }
|
||||
},
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'Dashboard',
|
||||
component: Dashboard
|
||||
},
|
||||
{
|
||||
path: '/item',
|
||||
name: 'Item',
|
||||
component: Item
|
||||
},
|
||||
{
|
||||
path: '/profile',
|
||||
name: 'Profile',
|
||||
component: Profile
|
||||
},
|
||||
{
|
||||
path: '/kitchen-sink',
|
||||
name: 'Kitchen Sink',
|
||||
@@ -20,17 +43,6 @@ const routes = [
|
||||
path: '/copilot-test',
|
||||
name: 'Copilot Test',
|
||||
component: CopilotTest
|
||||
},
|
||||
{
|
||||
path: '/profile',
|
||||
name: 'Profile',
|
||||
component: Profile
|
||||
},
|
||||
{
|
||||
path: '/system-notice',
|
||||
name: 'SystemNotice',
|
||||
component: SystemNotice,
|
||||
meta: { hideHeader: true }
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -7,16 +7,6 @@
|
||||
--font-body: "Roboto", sans-serif;
|
||||
--font-mono: "Roboto Mono", monospace;
|
||||
|
||||
--color-background: rgb(var(--v-theme-background));
|
||||
--color-surface: rgb(var(--v-theme-surface));
|
||||
--color-on-surface: rgb(var(--v-theme-on-surface));
|
||||
|
||||
--color-primary: rgb(var(--v-theme-primary));
|
||||
--color-success: rgb(var(--v-theme-success));
|
||||
--color-info: rgb(var(--v-theme-info));
|
||||
--color-warning: rgb(var(--v-theme-warning));
|
||||
--color-error: rgb(var(--v-theme-error));
|
||||
|
||||
--breakpoint-*: initial;
|
||||
--breakpoint-xs: 0px;
|
||||
--breakpoint-sm: 600px;
|
||||
|
||||
@@ -0,0 +1,373 @@
|
||||
<template>
|
||||
<v-container fluid class="pa-6">
|
||||
|
||||
<!-- Page Header -->
|
||||
<v-row align="center" class="mb-6">
|
||||
<v-col>
|
||||
<div class="text-h5 font-weight-medium text-high-emphasis">Dashboard</div>
|
||||
<div class="text-body-2 text-medium-emphasis mt-1">Overview of all routed items</div>
|
||||
</v-col>
|
||||
<v-col cols="auto">
|
||||
<v-btn-toggle v-model="viewMode" mandatory density="compact" variant="outlined" divided>
|
||||
<v-btn value="cards" size="small">
|
||||
<v-icon size="16" start>mdi-view-grid-outline</v-icon>
|
||||
Cards
|
||||
</v-btn>
|
||||
<v-btn value="table" size="small">
|
||||
<v-icon size="16" start>mdi-table</v-icon>
|
||||
Table
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Stat Cards -->
|
||||
<v-row class="mb-6">
|
||||
<v-col v-for="stat in stats" :key="stat.label" cols="12" sm="6" md="3">
|
||||
<v-card rounded="lg" variant="outlined" height="100%">
|
||||
<v-card-text>
|
||||
<div class="text-overline text-medium-emphasis mb-1">{{ stat.label }}</div>
|
||||
<div class="text-h4 font-weight-medium text-high-emphasis">{{ stat.value }}</div>
|
||||
<div class="mt-2">
|
||||
<v-chip
|
||||
:color="stat.chipColor"
|
||||
size="x-small"
|
||||
variant="tonal"
|
||||
label
|
||||
>
|
||||
{{ stat.chipText }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Filter / Search Row -->
|
||||
<v-row align="center" class="mb-4">
|
||||
<v-col cols="12" sm="4" md="3">
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
placeholder="Search items…"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
clearable
|
||||
rounded="lg"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="3" md="2">
|
||||
<v-select
|
||||
v-model="filterStatus"
|
||||
:items="statusOptions"
|
||||
label="Status"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
rounded="lg"
|
||||
clearable
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="3" md="2">
|
||||
<v-select
|
||||
v-model="filterOrg"
|
||||
:items="orgOptions"
|
||||
label="Organization"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
rounded="lg"
|
||||
clearable
|
||||
/>
|
||||
</v-col>
|
||||
<v-spacer />
|
||||
<v-col cols="auto">
|
||||
<v-btn
|
||||
color="primary"
|
||||
prepend-icon="mdi-plus-circle-outline"
|
||||
rounded="lg"
|
||||
variant="flat"
|
||||
@click="$router.push({ name: 'item', params: { mode: 'create' } })"
|
||||
>
|
||||
Submit item
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- CARD VIEW -->
|
||||
<template v-if="viewMode === 'cards'">
|
||||
<v-row>
|
||||
<v-col
|
||||
v-for="item in filteredItems"
|
||||
:key="item.id"
|
||||
cols="12" sm="6" md="4" lg="3"
|
||||
>
|
||||
<v-card
|
||||
rounded="lg"
|
||||
variant="outlined"
|
||||
height="100%"
|
||||
class="item-card"
|
||||
@click="$router.push({ name: 'item', params: { id: item.id, mode: 'view' } })"
|
||||
>
|
||||
<v-card-item>
|
||||
<template #title>
|
||||
<span class="text-body-1 font-weight-medium">{{ item.title }}</span>
|
||||
</template>
|
||||
<template #subtitle>
|
||||
<span class="text-caption">{{ item.id }}</span>
|
||||
</template>
|
||||
<template #append>
|
||||
<v-chip
|
||||
:color="statusColor(item.status)"
|
||||
size="x-small"
|
||||
variant="tonal"
|
||||
label
|
||||
>
|
||||
{{ item.status }}
|
||||
</v-chip>
|
||||
</template>
|
||||
</v-card-item>
|
||||
|
||||
<v-card-text class="pt-0">
|
||||
<!-- Progress -->
|
||||
<div class="d-flex align-center justify-space-between mb-1">
|
||||
<span class="text-caption text-medium-emphasis">
|
||||
Step {{ item.step }} of {{ item.totalSteps }}
|
||||
</span>
|
||||
<span class="text-caption text-medium-emphasis">
|
||||
{{ Math.round((item.step / item.totalSteps) * 100) }}%
|
||||
</span>
|
||||
</div>
|
||||
<v-progress-linear
|
||||
:model-value="(item.step / item.totalSteps) * 100"
|
||||
:color="statusColor(item.status)"
|
||||
rounded
|
||||
height="4"
|
||||
class="mb-3"
|
||||
/>
|
||||
|
||||
<div class="text-caption text-medium-emphasis mb-3">{{ item.currentAction }}</div>
|
||||
|
||||
<!-- Org chips -->
|
||||
<div class="d-flex flex-wrap gap-1">
|
||||
<v-chip
|
||||
v-for="org in item.organizations"
|
||||
:key="org"
|
||||
size="x-small"
|
||||
variant="tonal"
|
||||
color="blue-grey"
|
||||
>
|
||||
{{ org }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</v-card-text>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-actions class="px-4 py-2">
|
||||
<span class="text-caption text-disabled">{{ item.updated }}</span>
|
||||
<v-spacer />
|
||||
<v-chip
|
||||
:color="priorityColor(item.priority)"
|
||||
size="x-small"
|
||||
variant="tonal"
|
||||
label
|
||||
>
|
||||
{{ item.priority }}
|
||||
</v-chip>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col v-if="filteredItems.length === 0" cols="12">
|
||||
<v-empty-state
|
||||
icon="mdi-file-search-outline"
|
||||
title="No items found"
|
||||
text="Try adjusting your search or filters."
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<!-- TABLE VIEW -->
|
||||
<template v-else>
|
||||
<v-card rounded="lg" variant="outlined">
|
||||
<v-tabs v-model="activeTab" color="primary" density="compact">
|
||||
<v-tab value="all">All ({{ items.length }})</v-tab>
|
||||
<v-tab value="mine">Assigned to me ({{ assignedToMe.length }})</v-tab>
|
||||
<v-tab value="drafts">Drafts ({{ drafts.length }})</v-tab>
|
||||
<v-tab value="archived">Archived ({{ archived.length }})</v-tab>
|
||||
</v-tabs>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-data-table
|
||||
:headers="tableHeaders"
|
||||
:items="tabItems"
|
||||
:search="search"
|
||||
density="compact"
|
||||
item-value="id"
|
||||
hover
|
||||
@click:row="onRowClick"
|
||||
>
|
||||
<!-- ID column -->
|
||||
<template #item.id="{ item }">
|
||||
<span class="text-caption text-medium-emphasis font-weight-medium">{{ item.id }}</span>
|
||||
</template>
|
||||
|
||||
<!-- Title column -->
|
||||
<template #item.title="{ item }">
|
||||
<span class="text-body-2 font-weight-medium">{{ item.title }}</span>
|
||||
</template>
|
||||
|
||||
<!-- Status column -->
|
||||
<template #item.status="{ item }">
|
||||
<v-chip
|
||||
:color="statusColor(item.status)"
|
||||
size="x-small"
|
||||
variant="tonal"
|
||||
label
|
||||
>
|
||||
{{ item.status }}
|
||||
</v-chip>
|
||||
</template>
|
||||
|
||||
<!-- Step column -->
|
||||
<template #item.step="{ item }">
|
||||
<span class="text-body-2">{{ item.step }}/{{ item.totalSteps }}</span>
|
||||
</template>
|
||||
|
||||
<!-- Priority column -->
|
||||
<template #item.priority="{ item }">
|
||||
<v-chip
|
||||
:color="priorityColor(item.priority)"
|
||||
size="x-small"
|
||||
variant="tonal"
|
||||
label
|
||||
>
|
||||
{{ item.priority }}
|
||||
</v-chip>
|
||||
</template>
|
||||
|
||||
<!-- Updated column -->
|
||||
<template #item.updated="{ item }">
|
||||
<span class="text-caption text-medium-emphasis">{{ item.updated }}</span>
|
||||
</template>
|
||||
|
||||
<!-- Row actions -->
|
||||
<template #item.actions="{ item }">
|
||||
<div class="d-flex ga-1 justify-end">
|
||||
<v-btn
|
||||
icon="mdi-eye-outline"
|
||||
size="x-small"
|
||||
variant="text"
|
||||
@click.stop="$router.push({ name: 'item', params: { id: item.id, mode: 'view' } })"
|
||||
/>
|
||||
<v-btn
|
||||
icon="mdi-pencil-outline"
|
||||
size="x-small"
|
||||
variant="text"
|
||||
@click.stop="$router.push({ name: 'item', params: { id: item.id, mode: 'edit' } })"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
// --- State ---
|
||||
const viewMode = ref('cards')
|
||||
const search = ref('')
|
||||
const filterStatus = ref(null)
|
||||
const filterOrg = ref(null)
|
||||
const activeTab = ref('all')
|
||||
|
||||
// --- Static data ---
|
||||
const stats = [
|
||||
{ label: 'Total Items', value: 124, chipColor: 'blue-grey', chipText: '6 organizations' },
|
||||
{ label: 'In Transit', value: 38, chipColor: 'primary', chipText: 'Active routing' },
|
||||
{ label: 'Pending Review', value: 17, chipColor: 'warning', chipText: 'Awaiting action' },
|
||||
{ label: 'Completed', value: 69, chipColor: 'success', chipText: 'This quarter' },
|
||||
]
|
||||
|
||||
const statusOptions = ['In Transit', 'Pending', 'Completed']
|
||||
const orgOptions = ['Acme Corp', 'Nexus Ltd', 'Gov Affairs', 'RegBody A', 'Agency B', 'Agency C', 'SupplyCo', 'BuildRight', 'DataOrg']
|
||||
|
||||
const items = ref([
|
||||
{ id: '#DOC-2041', title: 'Q3 Partnership Agreement', type: 'Agreement', status: 'In Transit', step: 3, totalSteps: 5, currentAction: 'Step 3 of 5 — Legal review', organizations: ['Acme', 'Nexus', 'Gov'], priority: 'High', updated: '2h ago', mine: true, draft: false, arch: false },
|
||||
{ id: '#DOC-2038', title: 'Vendor Onboarding Form', type: 'Onboarding', status: 'Completed', step: 5, totalSteps: 5, currentAction: 'All steps approved', organizations: ['Acme', 'SupplyCo'], priority: 'Normal', updated: 'Apr 22', mine: false, draft: false, arch: false },
|
||||
{ id: '#DOC-2044', title: 'Regulatory Compliance Pkg', type: 'Compliance', status: 'Pending', step: 1, totalSteps: 4, currentAction: 'Step 1 of 4 — Intake', organizations: ['RegBody A', 'Acme'], priority: 'Urgent', updated: 'Today', mine: true, draft: false, arch: false },
|
||||
{ id: '#DOC-2036', title: 'Inter-Agency MOU Draft', type: 'MOU', status: 'In Transit', step: 4, totalSteps: 5, currentAction: 'Step 4 of 5 — Final sign-off', organizations: ['Agency B', 'Agency C'], priority: 'Normal', updated: 'Yesterday',mine: false, draft: false, arch: false },
|
||||
{ id: '#DOC-2029', title: 'Subcontractor NDA', type: 'NDA', status: 'Completed', step: 5, totalSteps: 5, currentAction: 'All steps approved', organizations: ['Acme', 'BuildRight'], priority: 'Normal', updated: 'Apr 18', mine: false, draft: false, arch: true },
|
||||
{ id: '#DOC-2046', title: 'Data Sharing Agreement', type: 'Agreement', status: 'Pending', step: 0, totalSteps: 4, currentAction: 'Not yet started', organizations: ['DataOrg', 'Acme', 'Nexus'],priority: 'Normal', updated: 'Today', mine: false, draft: true, arch: false },
|
||||
{ id: '#DOC-2047', title: 'Cross-Agency Data Protocol', type: 'Compliance', status: 'Pending', step: 0, totalSteps: 3, currentAction: 'Draft — not submitted', organizations: ['Agency B', 'DataOrg'], priority: 'High', updated: 'Today', mine: true, draft: true, arch: false },
|
||||
])
|
||||
|
||||
// --- Computed ---
|
||||
const filteredItems = computed(() => {
|
||||
return items.value.filter(item => {
|
||||
const matchSearch = !search.value || item.title.toLowerCase().includes(search.value.toLowerCase()) || item.id.includes(search.value)
|
||||
const matchStatus = !filterStatus.value || item.status === filterStatus.value
|
||||
const matchOrg = !filterOrg.value || item.organizations.some(o => o.includes(filterOrg.value.split(' ')[0]))
|
||||
return matchSearch && matchStatus && matchOrg
|
||||
})
|
||||
})
|
||||
|
||||
const assignedToMe = computed(() => items.value.filter(i => i.mine))
|
||||
const drafts = computed(() => items.value.filter(i => i.draft))
|
||||
const archived = computed(() => items.value.filter(i => i.arch))
|
||||
|
||||
const tabItems = computed(() => {
|
||||
const map = { all: items.value, mine: assignedToMe.value, drafts: drafts.value, archived: archived.value }
|
||||
return (map[activeTab.value] || items.value).filter(item => {
|
||||
const matchSearch = !search.value || item.title.toLowerCase().includes(search.value.toLowerCase())
|
||||
return matchSearch
|
||||
})
|
||||
})
|
||||
|
||||
const tableHeaders = [
|
||||
{ title: 'ID', key: 'id', width: '110px' },
|
||||
{ title: 'Title', key: 'title' },
|
||||
{ title: 'Type', key: 'type', width: '110px' },
|
||||
{ title: 'Status', key: 'status', width: '120px' },
|
||||
{ title: 'Step', key: 'step', width: '70px' },
|
||||
{ title: 'Priority', key: 'priority', width: '90px' },
|
||||
{ title: 'Updated', key: 'updated', width: '100px' },
|
||||
{ title: '', key: 'actions', width: '80px', sortable: false },
|
||||
]
|
||||
|
||||
// --- Helpers ---
|
||||
function statusColor(status) {
|
||||
return { 'In Transit': 'primary', 'Pending': 'warning', 'Completed': 'success' }[status] ?? 'default'
|
||||
}
|
||||
function priorityColor(priority) {
|
||||
return { 'Urgent': 'error', 'High': 'warning', 'Normal': 'default' }[priority] ?? 'default'
|
||||
}
|
||||
|
||||
// --- Events ---
|
||||
function onRowClick(_, { item }) {
|
||||
// In real app: router.push({ name: 'item', params: { id: item.id, mode: 'view' } })
|
||||
console.log('View item:', item.id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.item-card {
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s, box-shadow 0.15s;
|
||||
}
|
||||
.item-card:hover {
|
||||
border-color: rgb(var(--v-theme-primary));
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
.gap-1 {
|
||||
gap: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,612 @@
|
||||
<template>
|
||||
<v-container fluid class="pa-6">
|
||||
|
||||
<!-- Page Header -->
|
||||
<v-row align="center" class="mb-6">
|
||||
<v-col cols="auto">
|
||||
<v-btn
|
||||
icon="mdi-arrow-left"
|
||||
variant="text"
|
||||
rounded="lg"
|
||||
@click="$router.back()"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<div class="text-h5 font-weight-medium text-high-emphasis">{{ pageTitle }}</div>
|
||||
<div class="text-body-2 text-medium-emphasis mt-1">{{ pageSubtitle }}</div>
|
||||
</v-col>
|
||||
<v-col cols="auto" class="d-flex ga-2">
|
||||
|
||||
<!-- View mode actions -->
|
||||
<template v-if="mode === 'view'">
|
||||
<v-chip
|
||||
:color="statusColor(item.status)"
|
||||
variant="tonal"
|
||||
label
|
||||
class="mr-2"
|
||||
>
|
||||
{{ item.status }}
|
||||
</v-chip>
|
||||
<v-btn
|
||||
variant="outlined"
|
||||
prepend-icon="mdi-pencil-outline"
|
||||
rounded="lg"
|
||||
@click="mode = 'edit'"
|
||||
>
|
||||
Edit
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="flat"
|
||||
prepend-icon="mdi-send-outline"
|
||||
rounded="lg"
|
||||
:disabled="item.status === 'Completed'"
|
||||
@click="advanceRoute"
|
||||
>
|
||||
Advance route
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<!-- Create mode actions -->
|
||||
<template v-else-if="mode === 'create'">
|
||||
<v-btn variant="outlined" rounded="lg" @click="saveDraft">Save draft</v-btn>
|
||||
<v-btn color="primary" variant="flat" rounded="lg" prepend-icon="mdi-send-outline" @click="submitItem">Submit for routing</v-btn>
|
||||
</template>
|
||||
|
||||
<!-- Edit mode actions -->
|
||||
<template v-else-if="mode === 'edit'">
|
||||
<v-btn variant="outlined" rounded="lg" @click="cancelEdit">Cancel</v-btn>
|
||||
<v-btn color="primary" variant="flat" rounded="lg" prepend-icon="mdi-content-save-outline" @click="saveEdit">Save changes</v-btn>
|
||||
</template>
|
||||
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- ===================== CREATE / EDIT MODE ===================== -->
|
||||
<template v-if="mode === 'create' || mode === 'edit'">
|
||||
<v-form ref="form" v-model="formValid">
|
||||
<v-row>
|
||||
|
||||
<!-- Left column: Item details -->
|
||||
<v-col cols="12" md="7">
|
||||
|
||||
<v-card rounded="lg" variant="outlined" class="mb-4">
|
||||
<v-card-title class="text-body-1 font-weight-medium pa-4 pb-2">Item details</v-card-title>
|
||||
<v-divider />
|
||||
<v-card-text>
|
||||
<v-row density="comfortable">
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="form.title"
|
||||
label="Title"
|
||||
placeholder="Describe the item briefly"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
rounded="lg"
|
||||
:rules="[v => !!v || 'Title is required']"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-select
|
||||
v-model="form.type"
|
||||
:items="itemTypes"
|
||||
item-title="title"
|
||||
item-value="value"
|
||||
label="Item type"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
rounded="lg"
|
||||
:rules="[v => !!v || 'Type is required']"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-select
|
||||
v-model="form.priority"
|
||||
:items="priorityOptions"
|
||||
item-title="title"
|
||||
item-value="value"
|
||||
label="Priority"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
rounded="lg"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-date-input
|
||||
clearable
|
||||
label="Suspense Date"
|
||||
v-model="form.dueDate"
|
||||
:min="today"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-select
|
||||
v-model="form.initiatingOrg"
|
||||
:items="orgOptions"
|
||||
item-title="title"
|
||||
item-value="value"
|
||||
label="Initiating organization"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
rounded="lg"
|
||||
:rules="[v => !!v || 'Organization is required']"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-textarea
|
||||
v-model="form.notes"
|
||||
label="Notes for reviewers"
|
||||
placeholder="Add context, instructions, or background…"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
rounded="lg"
|
||||
rows="3"
|
||||
auto-grow
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- Attachments -->
|
||||
<v-card rounded="lg" variant="outlined" class="mb-4">
|
||||
<v-card-title class="text-body-1 font-weight-medium pa-4 pb-2">Attachments</v-card-title>
|
||||
<v-divider />
|
||||
<v-card-text>
|
||||
<v-file-input
|
||||
v-model="form.files"
|
||||
label="Attach files"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
rounded="lg"
|
||||
prepend-icon=""
|
||||
prepend-inner-icon="mdi-paperclip"
|
||||
multiple
|
||||
chips
|
||||
show-size
|
||||
hint="Accepted: PDF, DOCX, XLSX, PNG, JPG"
|
||||
persistent-hint
|
||||
/>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
</v-col>
|
||||
|
||||
<!-- Right column: Routing chain -->
|
||||
<v-col cols="12" md="5">
|
||||
<v-card rounded="lg" variant="outlined">
|
||||
<v-card-title class="text-body-1 font-weight-medium pa-4 pb-2">Routing chain</v-card-title>
|
||||
<v-divider />
|
||||
<v-card-text>
|
||||
|
||||
<v-timeline density="compact" side="end" truncate-line="both">
|
||||
<v-timeline-item
|
||||
v-for="(step, i) in form.routingChain"
|
||||
:key="i"
|
||||
:dot-color="routeStepColor(step.stepStatus)"
|
||||
size="small"
|
||||
>
|
||||
<template #opposite>
|
||||
<span class="text-caption font-weight-medium">{{ i + 1 }}</span>
|
||||
</template>
|
||||
<v-card variant="tonal" rounded="lg" :color="routeStepColor(step.stepStatus)">
|
||||
<v-card-text class="pa-3">
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<div>
|
||||
<div class="text-body-2 font-weight-medium">{{ step.org }}</div>
|
||||
<div class="text-caption text-medium-emphasis">{{ step.action }}</div>
|
||||
</div>
|
||||
<div class="d-flex ga-1 align-center">
|
||||
<v-chip size="x-small" :color="routeStepColor(step.stepStatus)" variant="tonal" label>
|
||||
{{ step.stepStatus }}
|
||||
</v-chip>
|
||||
<v-btn
|
||||
v-if="mode === 'edit' || mode === 'create'"
|
||||
icon="mdi-close"
|
||||
size="x-small"
|
||||
variant="text"
|
||||
@click="removeRouteStep(i)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-timeline-item>
|
||||
</v-timeline>
|
||||
|
||||
<!-- Add routing step -->
|
||||
<v-expand-transition>
|
||||
<div v-if="addingStep" class="mt-3">
|
||||
<v-card variant="tonal" rounded="lg" color="primary">
|
||||
<v-card-text class="pa-3">
|
||||
<v-select
|
||||
v-model="newStep.org"
|
||||
:items="orgOptions"
|
||||
item-title="title"
|
||||
item-value="value"
|
||||
label="Organization"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
rounded="lg"
|
||||
class="mb-2"
|
||||
hide-details
|
||||
/>
|
||||
<v-select
|
||||
v-model="newStep.action"
|
||||
:items="actionOptions"
|
||||
item-title="title"
|
||||
item-value="value"
|
||||
label="Action type"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
rounded="lg"
|
||||
class="mb-3"
|
||||
hide-details
|
||||
/>
|
||||
<div class="d-flex ga-2 justify-end">
|
||||
<v-btn size="small" variant="text" @click="addingStep = false">Cancel</v-btn>
|
||||
<v-btn size="small" color="primary" variant="flat" @click="confirmAddStep">Add step</v-btn>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-expand-transition>
|
||||
|
||||
<v-btn
|
||||
v-if="!addingStep"
|
||||
variant="tonal"
|
||||
color="primary"
|
||||
prepend-icon="mdi-plus"
|
||||
block
|
||||
rounded="lg"
|
||||
class="mt-3"
|
||||
@click="addingStep = true"
|
||||
>
|
||||
Add routing step
|
||||
</v-btn>
|
||||
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
</v-row>
|
||||
</v-form>
|
||||
</template>
|
||||
|
||||
<!-- ===================== VIEW MODE ===================== -->
|
||||
<template v-else-if="mode === 'view'">
|
||||
<v-row>
|
||||
|
||||
<!-- Left: Item info -->
|
||||
<v-col cols="12" md="7">
|
||||
|
||||
<!-- Detail card -->
|
||||
<v-card rounded="lg" variant="outlined" class="mb-4">
|
||||
<v-card-title class="text-body-1 font-weight-medium pa-4 pb-2 d-flex align-center">
|
||||
Item details
|
||||
<v-spacer />
|
||||
<v-chip size="x-small" color="blue-grey" variant="tonal" label>{{ item.id }}</v-chip>
|
||||
</v-card-title>
|
||||
<v-divider />
|
||||
<v-card-text>
|
||||
<v-row density="comfortable">
|
||||
<v-col cols="12" sm="6">
|
||||
<div class="text-caption text-medium-emphasis mb-1">Title</div>
|
||||
<div class="text-body-2 font-weight-medium">{{ item.title }}</div>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<div class="text-caption text-medium-emphasis mb-1">Type</div>
|
||||
<div class="text-body-2">{{ item.type }}</div>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" class="mt-3">
|
||||
<div class="text-caption text-medium-emphasis mb-1">Priority</div>
|
||||
<v-chip :color="priorityColor(item.priority)" size="small" variant="tonal" label>
|
||||
{{ item.priority }}
|
||||
</v-chip>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" class="mt-3">
|
||||
<div class="text-caption text-medium-emphasis mb-1">Due date</div>
|
||||
<div class="text-body-2">{{ item.dueDate || '—' }}</div>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" class="mt-3">
|
||||
<div class="text-caption text-medium-emphasis mb-1">Initiating org</div>
|
||||
<div class="text-body-2">{{ item.initiatingOrg }}</div>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" class="mt-3">
|
||||
<div class="text-caption text-medium-emphasis mb-1">Last updated</div>
|
||||
<div class="text-body-2">{{ item.updated }}</div>
|
||||
</v-col>
|
||||
<v-col cols="12" class="mt-3">
|
||||
<div class="text-caption text-medium-emphasis mb-1">Notes</div>
|
||||
<div class="text-body-2 text-medium-emphasis">{{ item.notes || 'No notes provided.' }}</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- Attachments -->
|
||||
<v-card rounded="lg" variant="outlined" class="mb-4">
|
||||
<v-card-title class="text-body-1 font-weight-medium pa-4 pb-2">Attachments</v-card-title>
|
||||
<v-divider />
|
||||
<v-list density="compact">
|
||||
<v-list-item
|
||||
v-for="file in item.attachments"
|
||||
:key="file.name"
|
||||
:prepend-icon="fileIcon(file.type)"
|
||||
:title="file.name"
|
||||
:subtitle="file.size"
|
||||
>
|
||||
<template #append>
|
||||
<v-btn icon="mdi-download-outline" size="x-small" variant="text" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="!item.attachments?.length" title="No attachments" class="text-medium-emphasis" />
|
||||
</v-list>
|
||||
</v-card>
|
||||
|
||||
<!-- Activity log -->
|
||||
<v-card rounded="lg" variant="outlined">
|
||||
<v-card-title class="text-body-1 font-weight-medium pa-4 pb-2">Activity log</v-card-title>
|
||||
<v-divider />
|
||||
<v-timeline density="compact" side="end" class="pa-4" truncate-line="both">
|
||||
<v-timeline-item
|
||||
v-for="event in item.activity"
|
||||
:key="event.id"
|
||||
:dot-color="event.color"
|
||||
size="x-small"
|
||||
>
|
||||
<div class="text-body-2 font-weight-medium">{{ event.title }}</div>
|
||||
<div class="text-caption text-medium-emphasis">{{ event.detail }}</div>
|
||||
<div class="text-caption text-disabled mt-1">{{ event.time }}</div>
|
||||
</v-timeline-item>
|
||||
</v-timeline>
|
||||
</v-card>
|
||||
|
||||
</v-col>
|
||||
|
||||
<!-- Right: Routing chain + progress -->
|
||||
<v-col cols="12" md="5">
|
||||
|
||||
<!-- Progress summary -->
|
||||
<v-card rounded="lg" variant="outlined" class="mb-4">
|
||||
<v-card-title class="text-body-1 font-weight-medium pa-4 pb-2">Routing progress</v-card-title>
|
||||
<v-divider />
|
||||
<v-card-text>
|
||||
<div class="d-flex align-center justify-space-between mb-2">
|
||||
<span class="text-body-2 text-medium-emphasis">Step {{ item.step }} of {{ item.totalSteps }}</span>
|
||||
<span class="text-body-2 font-weight-medium">{{ Math.round((item.step / item.totalSteps) * 100) }}%</span>
|
||||
</div>
|
||||
<v-progress-linear
|
||||
:model-value="(item.step / item.totalSteps) * 100"
|
||||
:color="statusColor(item.status)"
|
||||
rounded
|
||||
height="6"
|
||||
class="mb-4"
|
||||
/>
|
||||
<div class="text-caption text-medium-emphasis">{{ item.currentAction }}</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- Routing chain (view) -->
|
||||
<v-card rounded="lg" variant="outlined">
|
||||
<v-card-title class="text-body-1 font-weight-medium pa-4 pb-2">Routing chain</v-card-title>
|
||||
<v-divider />
|
||||
<v-card-text>
|
||||
<v-timeline density="compact" side="end" truncate-line="both">
|
||||
<v-timeline-item
|
||||
v-for="(step, i) in item.routingChain"
|
||||
:key="i"
|
||||
:dot-color="routeStepColor(step.stepStatus)"
|
||||
size="small"
|
||||
>
|
||||
<template #opposite>
|
||||
<span class="text-caption font-weight-medium">{{ i + 1 }}</span>
|
||||
</template>
|
||||
<v-card :color="routeStepColor(step.stepStatus)" variant="tonal" rounded="lg">
|
||||
<v-card-text class="pa-3">
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<div>
|
||||
<div class="text-body-2 font-weight-medium">{{ step.org }}</div>
|
||||
<div class="text-caption text-medium-emphasis">{{ step.action }}</div>
|
||||
</div>
|
||||
<v-chip size="x-small" :color="routeStepColor(step.stepStatus)" variant="tonal" label>
|
||||
{{ step.stepStatus }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-timeline-item>
|
||||
</v-timeline>
|
||||
|
||||
<!-- Advance route CTA if in transit -->
|
||||
<v-alert
|
||||
v-if="item.status === 'In Transit'"
|
||||
type="info"
|
||||
variant="tonal"
|
||||
rounded="lg"
|
||||
class="mt-4"
|
||||
density="compact"
|
||||
>
|
||||
<template #text>
|
||||
Action required at <strong>{{ currentStepOrg }}</strong>. Use "Advance route" to move forward.
|
||||
</template>
|
||||
</v-alert>
|
||||
|
||||
<v-alert
|
||||
v-else-if="item.status === 'Completed'"
|
||||
type="success"
|
||||
variant="tonal"
|
||||
rounded="lg"
|
||||
class="mt-4"
|
||||
density="compact"
|
||||
text="All routing steps have been completed."
|
||||
/>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<!-- Snackbar feedback -->
|
||||
<v-snackbar
|
||||
v-model="snackbar.show"
|
||||
:color="snackbar.color"
|
||||
location="bottom right"
|
||||
rounded="lg"
|
||||
timeout="3000"
|
||||
>
|
||||
<v-icon start>{{ snackbar.icon }}</v-icon>
|
||||
{{ snackbar.text }}
|
||||
</v-snackbar>
|
||||
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, inject } from 'vue'
|
||||
|
||||
const snackbar = inject('snackbar')
|
||||
|
||||
// --- Props ---
|
||||
// In real app these come from router: props: ['id', 'initialMode']
|
||||
const props = defineProps({
|
||||
id: { type: String, default: null },
|
||||
initialMode: { type: String, default: 'create' },
|
||||
})
|
||||
|
||||
// --- Mode management ---
|
||||
const mode = ref(props.id ? (props.initialMode ?? 'view') : 'create')
|
||||
|
||||
// --- Form state (create / edit) ---
|
||||
const form = ref({
|
||||
title: '',
|
||||
type: '',
|
||||
priority: 'Normal',
|
||||
dueDate: '',
|
||||
initiatingOrg:'',
|
||||
notes: '',
|
||||
files: [],
|
||||
routingChain: [
|
||||
{ org: 'Acme Corp', action: 'Creates and submits', stepStatus: 'Complete' },
|
||||
{ org: 'Nexus Ltd', action: 'Legal & compliance review', stepStatus: 'Active' },
|
||||
{ org: 'Gov Affairs', action: 'Final approval', stepStatus: 'Queued' },
|
||||
],
|
||||
})
|
||||
const formValid = ref(false)
|
||||
const addingStep = ref(false)
|
||||
const newStep = ref({ org: '', action: '' })
|
||||
|
||||
// --- Mock item data (view mode) ---
|
||||
const item = ref({
|
||||
id: '#DOC-2041',
|
||||
title: 'Q3 Partnership Agreement',
|
||||
type: 'Agreement',
|
||||
status: 'In Transit',
|
||||
priority: 'High',
|
||||
dueDate: '2024-06-30',
|
||||
initiatingOrg: 'Acme Corp',
|
||||
notes: 'This agreement governs the cross-agency data partnership for Q3. Legal review required before final sign-off.',
|
||||
updated: '2 hours ago',
|
||||
step: 3,
|
||||
totalSteps: 5,
|
||||
currentAction: 'Step 3 of 5 — Legal review at Nexus Ltd',
|
||||
attachments: [
|
||||
{ name: 'Q3_Partnership_v2.pdf', type: 'pdf', size: '1.2 MB' },
|
||||
{ name: 'Exhibit_A_Terms.docx', type: 'docx', size: '340 KB' },
|
||||
],
|
||||
routingChain: [
|
||||
{ org: 'Acme Corp', action: 'Initiated & submitted', stepStatus: 'Complete' },
|
||||
{ org: 'Nexus Ltd', action: 'Legal & compliance review', stepStatus: 'Active' },
|
||||
{ org: 'Gov Affairs', action: 'Regulatory sign-off', stepStatus: 'Queued' },
|
||||
{ org: 'Acme Corp', action: 'Counter-signature', stepStatus: 'Queued' },
|
||||
{ org: 'Archive', action: 'Record & close', stepStatus: 'Queued' },
|
||||
],
|
||||
activity: [
|
||||
{ id: 1, title: 'Submitted for routing', detail: 'Jamie Doe — Acme Corp', time: 'Apr 28, 09:14', color: 'success' },
|
||||
{ id: 2, title: 'Received by Nexus Ltd', detail: 'Auto-routed to legal queue', time: 'Apr 28, 09:15', color: 'primary' },
|
||||
{ id: 3, title: 'Under legal review', detail: 'Assigned to K. Walsh', time: 'Apr 28, 11:30', color: 'warning' },
|
||||
{ id: 4, title: 'Comment added', detail: 'K. Walsh — "Clause 4 needs revision"', time: 'Apr 29, 14:02', color: 'blue-grey' },
|
||||
],
|
||||
})
|
||||
|
||||
// --- Options ---
|
||||
// Using {title, value} objects fixes a Vuetify 3 bug where plain string arrays
|
||||
// fail to display the selected value until the field is blurred.
|
||||
const toOpts = (arr) => arr.map(s => ({ title: s, value: s }))
|
||||
const itemTypes = toOpts(['Agreement', 'MOU', 'NDA', 'Compliance', 'Onboarding', 'Other'])
|
||||
const priorityOptions = toOpts(['Normal', 'High', 'Urgent'])
|
||||
const orgOptions = toOpts(['Acme Corp', 'Nexus Ltd', 'Gov Affairs', 'RegBody A', 'Agency B', 'Agency C', 'SupplyCo', 'BuildRight', 'DataOrg'])
|
||||
const actionOptions = toOpts(['Review & approve', 'Legal & compliance review', 'Sign-off', 'Counter-signature', 'Record & close', 'Other'])
|
||||
|
||||
// --- Computed ---
|
||||
const pageTitle = computed(() => ({
|
||||
create: 'Submit item',
|
||||
view: item.value.title,
|
||||
edit: `Editing — ${item.value.title}`,
|
||||
})[mode.value])
|
||||
|
||||
const pageSubtitle = computed(() => ({
|
||||
create: 'Route a new item through organizations',
|
||||
view: `${item.value.id} · ${item.value.type}`,
|
||||
edit: 'Make changes and save to update the record',
|
||||
})[mode.value])
|
||||
|
||||
const currentStepOrg = computed(() => {
|
||||
const step = item.value.routingChain?.find(s => s.stepStatus === 'Active')
|
||||
return step?.org ?? '—'
|
||||
})
|
||||
|
||||
const today = new Date().toISOString().split('T')[0]
|
||||
|
||||
// --- Methods ---
|
||||
function statusColor(status) {
|
||||
return { 'In Transit': 'primary', 'Pending': 'warning', 'Completed': 'success' }[status] ?? 'default'
|
||||
}
|
||||
function priorityColor(priority) {
|
||||
return { 'Urgent': 'error', 'High': 'warning', 'Normal': 'default' }[priority] ?? 'default'
|
||||
}
|
||||
function routeStepColor(stepStatus) {
|
||||
return { 'Complete': 'success', 'Active': 'primary', 'Queued': 'blue-grey' }[stepStatus] ?? 'default'
|
||||
}
|
||||
function fileIcon(type) {
|
||||
return { pdf: 'mdi-file-pdf-box', docx: 'mdi-file-word-box', xlsx: 'mdi-file-excel-box' }[type] ?? 'mdi-file-outline'
|
||||
}
|
||||
|
||||
function removeRouteStep(index) {
|
||||
form.value.routingChain.splice(index, 1)
|
||||
}
|
||||
function confirmAddStep() {
|
||||
if (newStep.value.org && newStep.value.action) {
|
||||
form.value.routingChain.push({ org: newStep.value.org, action: newStep.value.action, stepStatus: 'Queued' })
|
||||
newStep.value = { org: '', action: '' }
|
||||
addingStep.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function saveDraft() {
|
||||
snackbar.value.toast('Draft saved', {
|
||||
color: 'success',
|
||||
icon: 'mdi-content-save-outline'
|
||||
})
|
||||
}
|
||||
function submitItem() {
|
||||
snackbar.value.success('Item submitted for routing')
|
||||
}
|
||||
function handleError() {
|
||||
snackbar.value.error('Something went wrong', { timeout: 5000 })
|
||||
}
|
||||
function cancelEdit() {
|
||||
mode.value = 'view'
|
||||
}
|
||||
function saveEdit() {
|
||||
snackbar.value.toast('Changes saved', {
|
||||
color: 'primary',
|
||||
icon: 'mdi-check-circle-outline'
|
||||
})
|
||||
mode.value = 'view'
|
||||
}
|
||||
function advanceRoute() {
|
||||
toast('Route advanced to next step', 'primary', 'mdi-arrow-right-circle-outline')
|
||||
}
|
||||
</script>
|
||||
@@ -59,12 +59,10 @@
|
||||
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref, shallowRef } from 'vue'
|
||||
|
||||
const dialog = ref(false)
|
||||
|
||||
import { shallowRef } from 'vue'
|
||||
|
||||
const tab = shallowRef('tab-1')
|
||||
const tabs = [
|
||||
{
|
||||
@@ -88,4 +86,5 @@
|
||||
value: 'tab-4',
|
||||
},
|
||||
]
|
||||
|
||||
</script>
|
||||
Reference in New Issue
Block a user