Add more configuration pages
This commit is contained in:
parent
da8074d1e0
commit
de0fbd7188
5 changed files with 496 additions and 16 deletions
16
ts/router.ts
16
ts/router.ts
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
export interface Address {
|
||||
locality: string;
|
||||
number: string;
|
||||
street: string;
|
||||
}
|
||||
|
||||
export interface PublicReport {
|
||||
created: string;
|
||||
type: string;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
197
ts/view/configuration/User.vue
Normal file
197
ts/view/configuration/User.vue
Normal 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>
|
||||
267
ts/view/configuration/UserAdd.vue
Normal file
267
ts/view/configuration/UserAdd.vue
Normal 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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue