feat: add multi-tenant system with properties, organizations, and public booking

Implement complete multi-property architecture:
- Properties (groups of spaces) with public/private visibility
- Property managers (many-to-many) with role-based permissions
- Organizations with member management
- Anonymous/guest booking support via public API (/api/public/*)
- Property-scoped spaces, bookings, and settings
- Frontend: property selector, organization management, public booking views
- Migration script and updated seed data

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-02-15 00:17:21 +00:00
parent d637513d92
commit e21cf03a16
51 changed files with 6324 additions and 273 deletions

View File

@@ -2,7 +2,7 @@
<div class="users">
<Breadcrumb :items="breadcrumbItems" />
<div class="page-header">
<h2>Admin Dashboard - User Management</h2>
<h2>User Management</h2>
<button class="btn btn-primary" @click="openCreateModal">
<UserPlus :size="16" />
Create New User
@@ -16,7 +16,8 @@
<label for="filter-role">Filter by Role</label>
<select id="filter-role" v-model="filterRole" @change="loadUsers">
<option value="">All Roles</option>
<option value="admin">Admin</option>
<option value="admin">Superadmin</option>
<option value="manager">Manager</option>
<option value="user">User</option>
</select>
</div>
@@ -57,8 +58,8 @@
<td>{{ user.email }}</td>
<td>{{ user.full_name }}</td>
<td>
<span :class="['badge', user.role === 'admin' ? 'badge-admin' : 'badge-user']">
{{ user.role }}
<span :class="['badge', user.role === 'admin' || user.role === 'superadmin' ? 'badge-admin' : user.role === 'manager' ? 'badge-manager' : 'badge-user']">
{{ user.role === 'admin' ? 'superadmin' : user.role }}
</span>
</td>
<td>{{ user.organization || '-' }}</td>
@@ -140,7 +141,8 @@
<label for="role">Role *</label>
<select id="role" v-model="formData.role" required>
<option value="user">User</option>
<option value="admin">Admin</option>
<option value="manager">Manager</option>
<option value="admin">Superadmin</option>
</select>
</div>
@@ -268,7 +270,8 @@ const handleSubmit = async () => {
full_name: formData.value.full_name,
password: formData.value.password,
role: formData.value.role,
organization: formData.value.organization || undefined
organization: formData.value.organization || undefined,
timezone: 'UTC'
})
success.value = 'User created successfully!'
}
@@ -389,6 +392,8 @@ onMounted(() => {
.page-header h2 {
margin: 0;
font-size: 28px;
font-weight: 700;
color: var(--color-text-primary);
}
@@ -593,6 +598,11 @@ onMounted(() => {
color: var(--color-accent);
}
.badge-manager {
background: color-mix(in srgb, var(--color-warning) 15%, transparent);
color: var(--color-warning);
}
.badge-user {
background: var(--color-bg-tertiary);
color: var(--color-text-primary);
@@ -619,15 +629,18 @@ onMounted(() => {
.modal-content {
background: var(--color-surface);
border-radius: var(--radius-md);
padding: 24px;
border-radius: var(--radius-lg);
padding: 28px;
max-width: 500px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
box-shadow: var(--shadow-lg);
}
.modal-content h3 {
margin-bottom: 16px;
margin-top: 0;
margin-bottom: 20px;
color: var(--color-text-primary);
}