admin firs step

This commit is contained in:
Roo
2026-03-23 21:43:40 +00:00
parent 309a72cc0b
commit cddcd34ba9
47 changed files with 22698 additions and 19 deletions

View File

@@ -0,0 +1,202 @@
<template>
<TileWrapper
title="Geographical Map"
subtitle="Service moderation map"
icon="map"
:loading="loading"
>
<div class="service-map-tile">
<div class="mini-map">
<div class="map-placeholder">
<div class="map-grid">
<div
v-for="point in mapPoints"
:key="point.id"
class="map-point"
:class="point.status"
:style="{
left: `${point.x}%`,
top: `${point.y}%`
}"
:title="point.name"
></div>
</div>
</div>
</div>
<div class="tile-stats">
<div class="stat">
<span class="stat-label">Pending in Scope</span>
<span class="stat-value">{{ pendingCount }}</span>
</div>
<div class="stat">
<span class="stat-label">Scope</span>
<span class="stat-value scope">{{ scopeLabel }}</span>
</div>
</div>
<div class="tile-actions">
<button @click="navigateToMap" class="btn-primary">
Open Full Map
</button>
<button @click="refresh" class="btn-secondary">
Refresh
</button>
</div>
</div>
</TileWrapper>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import TileWrapper from '~/components/TileWrapper.vue'
import { useServiceMap } from '~/composables/useServiceMap'
const router = useRouter()
const { pendingServices, scopeLabel } = useServiceMap()
const loading = ref(false)
const pendingCount = computed(() => pendingServices.value.length)
// Generate random points for the mini map visualization
const mapPoints = computed(() => {
return pendingServices.value.slice(0, 8).map((service, index) => ({
id: service.id,
name: service.name,
status: service.status,
x: 10 + (index % 4) * 25 + Math.random() * 10,
y: 10 + Math.floor(index / 4) * 30 + Math.random() * 10
}))
})
const navigateToMap = () => {
router.push('/moderation-map')
}
const refresh = () => {
loading.value = true
// Simulate API call
setTimeout(() => {
loading.value = false
}, 1000)
}
</script>
<style scoped>
.service-map-tile {
display: flex;
flex-direction: column;
height: 100%;
}
.mini-map {
flex: 1;
margin-bottom: 15px;
}
.map-placeholder {
width: 100%;
height: 150px;
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
border-radius: 8px;
position: relative;
overflow: hidden;
border: 1px solid #90caf9;
}
.map-grid {
position: relative;
width: 100%;
height: 100%;
}
.map-point {
position: absolute;
width: 12px;
height: 12px;
border-radius: 50%;
transform: translate(-50%, -50%);
border: 2px solid white;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
.map-point.pending {
background-color: #ffc107;
}
.map-point.approved {
background-color: #28a745;
}
.tile-stats {
display: flex;
justify-content: space-between;
margin-bottom: 15px;
}
.stat {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
.stat-label {
font-size: 0.85rem;
color: #666;
margin-bottom: 4px;
}
.stat-value {
font-size: 1.5rem;
font-weight: bold;
color: #333;
}
.stat-value.scope {
font-size: 1rem;
color: #4a90e2;
background: #e3f2fd;
padding: 4px 8px;
border-radius: 12px;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tile-actions {
display: flex;
gap: 10px;
}
.btn-primary {
flex: 2;
background-color: #4a90e2;
color: white;
border: none;
padding: 10px;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
transition: background-color 0.2s;
}
.btn-primary:hover {
background-color: #3a7bc8;
}
.btn-secondary {
flex: 1;
background-color: #f8f9fa;
color: #495057;
border: 1px solid #dee2e6;
padding: 10px;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.2s;
}
.btn-secondary:hover {
background-color: #e9ecef;
}
</style>