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

@@ -3,9 +3,17 @@
<div class="row-time">
{{ formatTimeRange(booking.start_datetime, booking.end_datetime) }}
</div>
<div class="row-space">{{ booking.space?.name || 'Space' }}</div>
<div v-if="showUser && booking.user" class="row-user">
{{ booking.user.full_name }}
<div class="row-space">
{{ booking.space?.name || 'Space' }}
<span v-if="booking.space?.property_name" class="row-property">{{ booking.space.property_name }}</span>
</div>
<div v-if="showUser" class="row-user">
<template v-if="booking.is_anonymous && booking.guest_name">
{{ booking.guest_name }} <span class="guest-badge">Guest</span>
</template>
<template v-else-if="booking.user">
{{ booking.user.full_name }}
</template>
</div>
<div class="row-title" :title="booking.title">{{ booking.title }}</div>
<span :class="['row-badge', `row-badge-${booking.status}`]">
@@ -136,6 +144,18 @@ const formatTimeRange = (start: string, end: string): string => {
color: var(--color-accent);
white-space: nowrap;
min-width: 80px;
display: flex;
align-items: center;
gap: 6px;
}
.row-property {
font-size: 10px;
font-weight: 600;
color: var(--color-text-muted);
background: var(--color-bg-tertiary);
padding: 1px 6px;
border-radius: 6px;
}
.row-user {
@@ -188,6 +208,18 @@ const formatTimeRange = (start: string, end: string): string => {
color: var(--color-text-muted);
}
.guest-badge {
display: inline-block;
padding: 1px 6px;
font-size: 10px;
font-weight: 600;
border-radius: 6px;
background: color-mix(in srgb, var(--color-warning) 15%, transparent);
color: var(--color-warning);
vertical-align: middle;
margin-left: 4px;
}
@media (max-width: 640px) {
.booking-row {
flex-wrap: wrap;