Add more configuration pages

This commit is contained in:
Eli Ribble 2026-03-22 17:20:46 +00:00
parent da8074d1e0
commit de0fbd7188
No known key found for this signature in database
5 changed files with 496 additions and 16 deletions

View file

@ -3,7 +3,9 @@ import type { RouteRecordRaw } from "vue-router";
import Home from "./view/Home.vue";
import About from "./view/About.vue";
import Communication from "./view/Communication.vue";
import Configuration from "./view/Configuration.vue";
import ConfigurationRoot from "./view/configuration/Root.vue";
import ConfigurationUser from "./view/configuration/User.vue";
import ConfigurationUserAdd from "./view/configuration/UserAdd.vue";
import Intelligence from "./view/Intelligence.vue";
import Operations from "./view/Operations.vue";
import Planning from "./view/Planning.vue";
@ -24,7 +26,17 @@ const routes: RouteRecordRaw[] = [
{
path: "/configuration",
name: "Configuration",
component: Configuration,
component: ConfigurationRoot,
},
{
path: "/configuration/user",
name: "User Configuration",
component: ConfigurationUser,
},
{
path: "/configuration/user/add",
name: "User Add Configuration",
component: ConfigurationUserAdd,
},
{
path: "/intelligence",

View file

@ -1,3 +1,9 @@
export interface Address {
locality: string;
number: string;
street: string;
}
export interface PublicReport {
created: string;
type: string;

View file

@ -23,13 +23,12 @@
organization.
</p>
<div class="d-flex justify-content-between align-items-center">
<a
href="{{ .URL.Configuration.User }}"
class="btn btn-outline-primary"
>
Manage Users
<i class="bi bi-arrow-right ms-1"></i>
</a>
<RouterLink to="/configuration/user">
<button class="btn btn-outline-primary">
Manage Users
<i class="bi bi-arrow-right ms-1"></i>
</button>
</RouterLink>
</div>
</div>
</div>
@ -96,13 +95,12 @@
Manage your organization service area and information.
</p>
<div class="d-flex justify-content-between align-items-center">
<a
href="{{ .URL.Configuration.Organization }}"
class="btn btn-outline-danger"
>
Manage Organization
<i class="bi bi-arrow-right ms-1"></i>
</a>
<RouterLink to="/configuration/organization">
<button class="btn btn-outline-danger">
Manage Organization
<i class="bi bi-arrow-right ms-1"></i>
</button>
</RouterLink>
</div>
</div>
</div>

View file

@ -0,0 +1,197 @@
<style scoped>
.tech-photo {
width: 48px;
height: 48px;
border-radius: 50%;
object-fit: cover;
}
.status-badge {
min-width: 70px;
text-align: center;
}
.badge {
margin-right: 0.25rem;
}
.bg-warrant {
background-color: #6c757d;
}
.bg-drone {
background-color: #0dcaf0;
}
/* Override Bootstrap hover if needed */
.table-hover tbody tr:hover {
cursor: pointer;
}
</style>
<template>
<div class="container-fluid p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="mb-0">User Management</h1>
<RouterLink to="/configuration/user/add">
<button class="btn btn-primary" id="addUserBtn">
<i class="bi bi-plus-circle me-2"></i>Add New User
</button>
</RouterLink>
</div>
<div class="card">
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="table-light">
<tr>
<th>User</th>
<th>Role</th>
<th>Status</th>
<th>Tags</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="user in users" :key="user.id">
<td>
<div class="d-flex align-items-center">
<img
:src="user.avatar"
:alt="user.name"
class="tech-photo me-3"
/>
<div>
<div class="fw-bold">{{ user.name }}</div>
</div>
</div>
</td>
<td>
<span class="badge bg-success">{{ user.role }}</span>
</td>
<td>
<span
class="badge status-badge"
:class="getStatusClass(user.status)"
>
{{ user.status }}
</span>
</td>
<td>
<span
v-for="tag in user.tags"
:key="tag"
class="badge"
:class="getTagClass(tag)"
>
{{ tag }}
</span>
</td>
<td>
<button
class="btn btn-sm btn-warning"
title="Deactivate"
@click="deactivateUser(user.id)"
>
<i class="bi bi-person-x"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
interface User {
id: number;
name: string;
avatar: string;
role: string;
status: "Active" | "Inactive";
tags: string[];
}
interface URLConfiguration {
userAdd: string;
}
// Props (if needed from parent component)
// const props = defineProps<{
// urlConfiguration?: URLConfiguration
// }>()
// Reactive state
const users = ref<User[]>([
{
id: 1,
name: "John Davis",
avatar: "https://randomuser.me/api/portraits/men/32.jpg",
role: "Tech I",
status: "Active",
tags: [],
},
{
id: 2,
name: "Sarah Johnson",
avatar: "https://randomuser.me/api/portraits/women/65.jpg",
role: "Tech III",
status: "Active",
tags: ["warrant service", "drone pilot"],
},
{
id: 3,
name: "Michael Chen",
avatar: "https://randomuser.me/api/portraits/men/44.jpg",
role: "Tech I",
status: "Active",
tags: ["drone pilot"],
},
]);
const urlConfiguration = ref<URLConfiguration>({
userAdd: "/configuration/user/add", // Update with your actual route
});
// Methods
const getStatusClass = (status: string): string => {
return status === "Active" ? "bg-success" : "bg-secondary";
};
const getTagClass = (tag: string): string => {
if (tag === "warrant service") return "bg-warrant";
if (tag === "drone pilot") return "bg-drone";
return "bg-secondary";
};
const deactivateUser = (userId: number): void => {
const user = users.value.find((u) => u.id === userId);
if (user) {
user.status = "Inactive";
// Add your deactivation logic here (e.g., API call)
console.log(`Deactivating user: ${userId}`);
}
};
// Lifecycle hooks
onMounted(() => {
// Fetch users from API if needed
// fetchUsers()
});
// Optional: API call example
// const fetchUsers = async (): Promise<void> => {
// try {
// const response = await fetch('/api/users')
// const data = await response.json()
// users.value = data
// } catch (error) {
// console.error('Error fetching users:', error)
// }
// }
</script>

View file

@ -0,0 +1,267 @@
<template>
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="card shadow">
<div class="card-header bg-white">
<div class="d-flex justify-content-between align-items-center">
<h3 class="mb-0">Add New User</h3>
</div>
</div>
<div class="card-body">
<form
id="addUserForm"
class="needs-validation"
novalidate
@submit.prevent="handleSubmit"
>
<!-- Full Name -->
<div class="mb-3">
<label for="fullName" class="form-label required-field">
Full Name
</label>
<input
type="text"
class="form-control"
id="fullName"
v-model="formData.fullName"
:class="{ 'is-invalid': isSubmitted && !formData.fullName }"
required
/>
<div class="invalid-feedback">
Please provide the user's full name.
</div>
</div>
<!-- Email Address -->
<div class="mb-3">
<label for="emailAddress" class="form-label required-field">
Email Address
</label>
<input
type="email"
class="form-control"
id="emailAddress"
v-model="formData.emailAddress"
:class="{ 'is-invalid': isSubmitted && !isValidEmail }"
required
/>
<div class="invalid-feedback">
Please provide a valid email address.
</div>
<div class="form-text">
An invitation will be sent to this email address.
</div>
</div>
<!-- Username -->
<div class="mb-3">
<label for="username" class="form-label required-field">
Username
</label>
<input
type="text"
class="form-control"
id="username"
v-model="formData.username"
:class="{ 'is-invalid': isSubmitted && !formData.username }"
required
/>
<div class="invalid-feedback">Please provide a username.</div>
<div class="form-text">
Username must be unique and contain only letters, numbers, and
underscores.
</div>
</div>
<div class="row">
<!-- Role -->
<div class="col-md-6 mb-3">
<label for="userRole" class="form-label required-field">
Role
</label>
<select
class="form-select"
id="userRole"
v-model="formData.userRole"
:class="{ 'is-invalid': isSubmitted && !formData.userRole }"
required
>
<option value="" disabled>Select a role</option>
<option value="lead">Lead</option>
<option value="technician">Technician</option>
<option value="administrator">Administrator</option>
</select>
<div class="invalid-feedback">Please select a role.</div>
</div>
<!-- Initial Status -->
<div class="col-md-6 mb-3">
<label for="initialStatus" class="form-label">
Initial Status
</label>
<select
class="form-select"
id="initialStatus"
v-model="formData.initialStatus"
>
<option value="invited">Invited</option>
<option value="active">Active</option>
</select>
</div>
</div>
<!-- Permissions -->
<div class="mb-4">
<label class="form-label d-block">Permissions</label>
<div class="form-check form-switch">
<input
class="form-check-input switch-lg"
type="checkbox"
id="serveWarrants"
v-model="formData.serveWarrants"
/>
<label class="form-check-label" for="serveWarrants">
Can serve warrants
</label>
</div>
</div>
<!-- Send welcome email checkbox -->
<div class="mb-4">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="sendWelcomeEmail"
v-model="formData.sendWelcomeEmail"
/>
<label class="form-check-label" for="sendWelcomeEmail">
Send welcome email with login instructions
</label>
</div>
</div>
<hr />
<!-- Form actions -->
<div class="d-flex justify-content-end gap-2">
<button
type="button"
class="btn btn-secondary"
@click="handleCancel"
>
Cancel
</button>
<button
type="submit"
class="btn btn-primary"
:disabled="isLoading"
>
<i class="bi bi-person-plus me-1"></i>
{{ isLoading ? "Adding..." : "Add User" }}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from "vue";
import { useRouter } from "vue-router";
interface UserFormData {
fullName: string;
emailAddress: string;
username: string;
userRole: string;
initialStatus: string;
serveWarrants: boolean;
sendWelcomeEmail: boolean;
}
interface Props {
cancelUrl?: string;
}
const props = withDefaults(defineProps<Props>(), {
cancelUrl: "/configuration/user",
});
const emit = defineEmits<{
submit: [formData: UserFormData];
cancel: [];
}>();
const router = useRouter();
const formData = ref<UserFormData>({
fullName: "",
emailAddress: "",
username: "",
userRole: "",
initialStatus: "invited",
serveWarrants: false,
sendWelcomeEmail: true,
});
const isSubmitted = ref(false);
const isLoading = ref(false);
const isValidEmail = computed(() => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(formData.value.emailAddress);
});
const isFormValid = computed(() => {
return (
formData.value.fullName.trim() !== "" &&
isValidEmail.value &&
formData.value.username.trim() !== "" &&
formData.value.userRole !== ""
);
});
const handleSubmit = async () => {
isSubmitted.value = true;
if (!isFormValid.value) {
return;
}
try {
isLoading.value = true;
emit("submit", formData.value);
// Example API call (uncomment and adjust as needed):
// await api.post('/api/users', formData.value);
// router.push(props.cancelUrl);
} catch (error) {
console.error("Error adding user:", error);
// Handle error (e.g., show toast notification)
} finally {
isLoading.value = false;
}
};
const handleCancel = () => {
emit("cancel");
router.push(props.cancelUrl);
};
</script>
<style scoped>
.form-check-input.switch-lg {
width: 3em;
height: 1.5em;
}
.required-field::after {
content: " *";
color: red;
}
</style>