refactor: enhance routing and state management in CoopBuilder, add migration checks on startup, and update Tailwind configuration for improved component styling
This commit is contained in:
parent
848386e3dd
commit
4cea1f71fe
55 changed files with 4053 additions and 1486 deletions
93
components/RevenueMixTable.vue
Normal file
93
components/RevenueMixTable.vue
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<template>
|
||||
<div class="space-y-3">
|
||||
<!-- Revenue streams table -->
|
||||
<div class="space-y-2">
|
||||
<div v-for="stream in sortedStreams" :key="stream.id"
|
||||
class="flex justify-between items-center text-sm">
|
||||
<div class="flex items-center gap-2">
|
||||
<div
|
||||
class="w-3 h-3 rounded"
|
||||
:style="{ backgroundColor: getStreamColor(stream.id) }"
|
||||
/>
|
||||
<span class="font-medium">{{ stream.name }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-gray-600">{{ formatCurrency(stream.targetMonthlyAmount || 0) }}</span>
|
||||
<span class="font-medium min-w-[40px] text-right">{{ stream.percentage }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Concentration warning -->
|
||||
<div v-if="concentrationWarning"
|
||||
class="p-3 bg-yellow-50 border border-yellow-200 rounded-md">
|
||||
<div class="flex items-start gap-2">
|
||||
<UIcon name="i-heroicons-exclamation-triangle" class="w-4 h-4 text-yellow-600 mt-0.5" />
|
||||
<div class="text-sm text-yellow-800">
|
||||
<span class="font-medium">{{ concentrationWarning.stream }}</span>
|
||||
= {{ concentrationWarning.percentage }}% of total → consider balancing
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Totals -->
|
||||
<div class="pt-2 border-t border-gray-200 text-xs text-gray-600">
|
||||
<div class="flex justify-between">
|
||||
<span>Total monthly target:</span>
|
||||
<span class="font-medium">{{ formatCurrency(totalMonthly) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const streamsStore = useStreamsStore()
|
||||
const { streams } = storeToRefs(streamsStore)
|
||||
|
||||
const totalMonthly = computed(() => {
|
||||
return streams.value.reduce((sum, s) => sum + (s.targetMonthlyAmount || 0), 0)
|
||||
})
|
||||
|
||||
const sortedStreams = computed(() => {
|
||||
const withPercentages = streams.value
|
||||
.map(stream => {
|
||||
const amount = stream.targetMonthlyAmount || 0
|
||||
const percentage = totalMonthly.value > 0
|
||||
? Math.round((amount / totalMonthly.value) * 100)
|
||||
: 0
|
||||
return { ...stream, percentage }
|
||||
})
|
||||
.filter(s => s.percentage > 0)
|
||||
|
||||
return withPercentages.sort((a, b) => b.percentage - a.percentage)
|
||||
})
|
||||
|
||||
const concentrationWarning = computed(() => {
|
||||
const topStream = sortedStreams.value[0]
|
||||
if (topStream && topStream.percentage >= 50) {
|
||||
return {
|
||||
stream: topStream.name,
|
||||
percentage: topStream.percentage
|
||||
}
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
const colors = ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#06b6d4', '#84cc16']
|
||||
|
||||
function getStreamColor(streamId: string) {
|
||||
const index = streams.value.findIndex(s => s.id === streamId)
|
||||
return colors[index % colors.length]
|
||||
}
|
||||
|
||||
function formatCurrency(amount: number) {
|
||||
return new Intl.NumberFormat('en-EU', {
|
||||
style: 'currency',
|
||||
currency: 'EUR',
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0
|
||||
}).format(amount)
|
||||
}
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue