Files
service-finder/frontend/admin/components/TileWrapper.vue
2026-03-23 21:43:40 +00:00

174 lines
4.5 KiB
Vue

<template>
<v-card
:color="tileColor"
variant="tonal"
class="h-100 d-flex flex-column tile-wrapper"
:class="{ 'draggable-tile': draggable }"
>
<!-- Drag Handle -->
<div v-if="draggable" class="drag-handle d-flex align-center justify-center pa-2" @mousedown.prevent>
<v-icon icon="mdi-drag-vertical" size="small" class="text-disabled"></v-icon>
</div>
<!-- Tile Header -->
<v-card-title class="d-flex align-center justify-space-between pa-3 pb-0">
<div class="d-flex align-center">
<v-icon :icon="tileIcon" class="mr-2"></v-icon>
<span class="text-subtitle-1 font-weight-bold">{{ tile.title }}</span>
</div>
<div class="d-flex align-center">
<!-- RBAC Badge -->
<v-chip size="small" :color="accessLevelColor" class="text-caption mr-1">
{{ accessLevelText }}
</v-chip>
<!-- Visibility Toggle -->
<v-btn
v-if="showVisibilityToggle"
icon
size="x-small"
variant="text"
@click="toggleVisibility"
:title="tile.preference?.visible ? 'Hide tile' : 'Show tile'"
>
<v-icon :icon="tile.preference?.visible ? 'mdi-eye' : 'mdi-eye-off'"></v-icon>
</v-btn>
</div>
</v-card-title>
<!-- Tile Content Slot -->
<v-card-text class="flex-grow-1 pa-3">
<slot>
<!-- Default content if no slot provided -->
<p class="text-body-2">{{ tile.description }}</p>
</slot>
</v-card-text>
<!-- Tile Footer Actions -->
<v-card-actions class="mt-auto pa-3 pt-0">
<v-spacer></v-spacer>
<v-btn
v-if="showActionButton"
variant="text"
size="small"
:prepend-icon="actionIcon"
@click="handleTileClick"
>
{{ actionText }}
</v-btn>
</v-card-actions>
</v-card>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useTileStore } from '~/stores/tiles'
import type { TilePermission } from '~/composables/useRBAC'
interface Props {
tile: TilePermission
draggable?: boolean
showVisibilityToggle?: boolean
showActionButton?: boolean
actionIcon?: string
actionText?: string
}
const props = withDefaults(defineProps<Props>(), {
draggable: true,
showVisibilityToggle: true,
showActionButton: true,
actionIcon: 'mdi-open-in-new',
actionText: 'Open'
})
const emit = defineEmits<{
click: [tile: TilePermission]
toggleVisibility: [tileId: string, visible: boolean]
}>()
const tileStore = useTileStore()
// Tile color based on ID
const tileColor = computed(() => {
const colors: Record<string, string> = {
'ai-logs': 'indigo',
'financial-dashboard': 'green',
'salesperson-hub': 'orange',
'user-management': 'blue',
'service-moderation-map': 'teal',
'gamification-control': 'purple',
'system-health': 'red'
}
return colors[props.tile.id] || 'surface'
})
// Tile icon based on ID
const tileIcon = computed(() => {
const icons: Record<string, string> = {
'ai-logs': 'mdi-robot',
'financial-dashboard': 'mdi-chart-line',
'salesperson-hub': 'mdi-account-tie',
'user-management': 'mdi-account-group',
'service-moderation-map': 'mdi-map',
'gamification-control': 'mdi-trophy',
'system-health': 'mdi-heart-pulse'
}
return icons[props.tile.id] || 'mdi-view-dashboard'
})
// Access level indicator
const accessLevelColor = computed(() => {
if (props.tile.minRank && props.tile.minRank > 5) return 'warning'
if (props.tile.requiredRole?.includes('admin')) return 'error'
return 'success'
})
const accessLevelText = computed(() => {
if (props.tile.minRank) return `Rank ${props.tile.minRank}+`
if (props.tile.requiredRole?.length) return props.tile.requiredRole[0]
return 'All'
})
// Methods
function handleTileClick() {
emit('click', props.tile)
}
function toggleVisibility() {
const newVisible = !props.tile.preference?.visible
tileStore.toggleTileVisibility(props.tile.id)
emit('toggleVisibility', props.tile.id, newVisible)
}
</script>
<style scoped>
.tile-wrapper {
position: relative;
transition: box-shadow 0.2s ease-in-out, transform 0.2s ease-in-out;
}
.tile-wrapper:hover {
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.drag-handle {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 24px;
background-color: rgba(0, 0, 0, 0.02);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
cursor: grab;
z-index: 1;
}
.drag-handle:active {
cursor: grabbing;
}
.draggable-tile {
user-select: none;
}
</style>