Md Afiq Iskandar ef5526baf1 Refactor Application Creation and Management Logic
- Simplified the application creation process by consolidating form fields and enhancing validation.
- Updated the create application page to streamline user experience with clearer provider options and improved layout.
- Implemented SweetAlert for success and error notifications during user actions, replacing traditional alerts.
- Enhanced the applications index page with dynamic filtering and improved data fetching from the Authentik API.
- Refactored API endpoints to utilize slugs for application identification, ensuring consistency with Authentik's structure.
- Improved authentication handling by updating the requireAuth utility to support cookie-based authentication.
2025-06-17 11:53:15 +08:00

222 lines
8.0 KiB
Vue

<script setup>
definePageMeta({
title: "Users",
middleware: ["auth"],
requiresAuth: true,
breadcrumb: [
{ name: "Dashboard", path: "/dashboard" },
{ name: "Users", path: "/users", type: "current" }
]
});
import { ref, onMounted, computed } from 'vue'
// Sample user data
const users = ref([
{ id: 1, username: 'admin', email: 'admin@company.com', firstName: 'Admin', lastName: 'User', isActive: true, department: 'IT', lastLogin: '2024-01-15' },
{ id: 2, username: 'john.doe', email: 'john@company.com', firstName: 'John', lastName: 'Doe', isActive: true, department: 'HR', lastLogin: '2024-01-14' },
{ id: 3, username: 'jane.smith', email: 'jane@company.com', firstName: 'Jane', lastName: 'Smith', isActive: false, department: 'Finance', lastLogin: '2024-01-10' }
])
const isLoading = ref(false)
const searchQuery = ref('')
// Computed
const filteredUsers = computed(() => {
if (!searchQuery.value) return users.value
return users.value.filter(user =>
user.firstName.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
user.lastName.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
user.email.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
user.department.toLowerCase().includes(searchQuery.value.toLowerCase())
)
})
const activeUsersCount = computed(() =>
users.value.filter(user => user.isActive).length
)
const departmentsCount = computed(() =>
new Set(users.value.map(user => user.department)).size
)
const refreshUsers = async () => {
isLoading.value = true
try {
// API call would go here
await new Promise(resolve => setTimeout(resolve, 1000))
} finally {
isLoading.value = false
}
}
onMounted(() => {
// Load users
})
</script>
<template>
<div>
<LayoutsBreadcrumb />
<!-- Header -->
<div class="mb-6">
<div class="flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Users</h1>
<p class="text-gray-600 dark:text-gray-400">Manage user accounts and permissions</p>
</div>
<div class="flex space-x-3">
<rs-button @click="refreshUsers" :disabled="isLoading" variant="primary-outline">
<Icon name="ph:arrows-clockwise" class="w-4 h-4 mr-2" />
{{ isLoading ? 'Loading...' : 'Refresh' }}
</rs-button>
<rs-button @click="navigateTo('/users/bulk')" variant="primary-outline">
<Icon name="ph:users-four" class="w-4 h-4 mr-2" />
Bulk Operations
</rs-button>
<rs-button @click="navigateTo('/users/create')">
<Icon name="ph:user-plus" class="w-4 h-4 mr-2" />
Add User
</rs-button>
</div>
</div>
</div>
<!-- Stats Cards -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
<rs-card>
<template #body>
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-blue-100 dark:bg-blue-900 rounded-lg flex items-center justify-center">
<Icon name="ph:users" class="w-5 h-5 text-blue-600 dark:text-blue-400" />
</div>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Total Users</p>
<p class="text-2xl font-bold text-gray-900 dark:text-white">{{ users.length }}</p>
</div>
</div>
</template>
</rs-card>
<rs-card>
<template #body>
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-green-100 dark:bg-green-900 rounded-lg flex items-center justify-center">
<Icon name="ph:check-circle" class="w-5 h-5 text-green-600 dark:text-green-400" />
</div>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Active Users</p>
<p class="text-2xl font-bold text-gray-900 dark:text-white">{{ activeUsersCount }}</p>
</div>
</div>
</template>
</rs-card>
<rs-card>
<template #body>
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-purple-100 dark:bg-purple-900 rounded-lg flex items-center justify-center">
<Icon name="ph:buildings" class="w-5 h-5 text-purple-600 dark:text-purple-400" />
</div>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Departments</p>
<p class="text-2xl font-bold text-gray-900 dark:text-white">{{ departmentsCount }}</p>
</div>
</div>
</template>
</rs-card>
<rs-card>
<template #body>
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="w-8 h-8 bg-yellow-100 dark:bg-yellow-900 rounded-lg flex items-center justify-center">
<Icon name="ph:clock" class="w-5 h-5 text-yellow-600 dark:text-yellow-400" />
</div>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Recent Logins</p>
<p class="text-2xl font-bold text-gray-900 dark:text-white">{{ users.filter(u => u.lastLogin >= '2024-01-14').length }}</p>
</div>
</div>
</template>
</rs-card>
</div>
<!-- Users Table -->
<rs-card>
<template #header>
<div class="flex items-center justify-between">
<h3 class="text-lg font-medium text-gray-900 dark:text-white">All Users</h3>
<rs-badge variant="info">{{ users.length }} users</rs-badge>
</div>
</template>
<template #body>
<RsTable
:field="['user', 'department', 'status', 'lastLogin', 'actions']"
:data="users"
:advanced="true"
>
<!-- User Column -->
<template #user="{ value }">
<div class="flex items-center">
<div class="flex-shrink-0 h-10 w-10">
<div class="h-10 w-10 rounded-full bg-primary flex items-center justify-center">
<span class="text-sm font-medium text-white">{{ value.firstName[0] }}{{ value.lastName[0] }}</span>
</div>
</div>
<div class="ml-4">
<div class="text-sm font-medium text-gray-900 dark:text-white">{{ value.firstName }} {{ value.lastName }}</div>
<div class="text-sm text-gray-500 dark:text-gray-400">{{ value.email }}</div>
</div>
</div>
</template>
<!-- Department Column -->
<template #department="{ value }">
<span class="text-sm text-gray-900 dark:text-white">{{ value.department }}</span>
</template>
<!-- Status Column -->
<template #status="{ value }">
<rs-badge :variant="value.isActive ? 'success' : 'secondary'">
{{ value.isActive ? 'Active' : 'Inactive' }}
</rs-badge>
</template>
<!-- Last Login Column -->
<template #lastLogin="{ value }">
<span class="text-sm text-gray-500 dark:text-gray-400">{{ value.lastLogin }}</span>
</template>
<!-- Actions Column -->
<template #actions="{ value }">
<div class="flex items-center justify-end space-x-2">
<button class="text-primary hover:text-primary/80">
<Icon name="ph:eye" class="w-4 h-4" />
</button>
<button class="text-primary hover:text-primary/80">
<Icon name="ph:pencil" class="w-4 h-4" />
</button>
<button class="text-red-600 hover:text-red-800">
<Icon name="ph:trash" class="w-4 h-4" />
</button>
</div>
</template>
</RsTable>
</template>
</rs-card>
</div>
</template>
<style scoped>
/* Custom styles */
</style>