refactor: update app.vue and various components to enhance UI consistency, replace color classes for improved accessibility, and refine layout for better user experience

This commit is contained in:
Jennie Robinson Faber 2025-09-10 11:02:54 +01:00
parent 7b4fb6c2fd
commit 24e8b7a3a8
41 changed files with 2395 additions and 1603 deletions

View file

@ -3,36 +3,39 @@
<template #header>
<div class="flex items-center justify-between">
<h4 class="font-medium">Milestones</h4>
<UButton
size="xs"
<UButton
size="xs"
variant="ghost"
icon="i-heroicons-plus"
@click="showAddForm = true"
>
@click="showAddForm = true">
Add
</UButton>
</div>
</template>
<div class="space-y-3">
<div v-if="milestoneStatuses.length === 0" class="text-sm text-gray-500 italic py-2">
<div
v-if="milestoneStatuses.length === 0"
class="text-sm text-neutral-500 italic py-2">
No milestones set. Add key dates to track progress.
</div>
<div
v-for="milestone in milestoneStatuses"
:key="milestone.id"
class="flex items-center justify-between p-2 border border-gray-200 rounded"
>
class="flex items-center justify-between p-2 border border-neutral-200 rounded">
<div class="flex items-center gap-2">
<UIcon
:name="milestone.willReach ? 'i-heroicons-check-circle' : 'i-heroicons-exclamation-triangle'"
:name="
milestone.willReach
? 'i-heroicons-check-circle'
: 'i-heroicons-exclamation-triangle'
"
:class="milestone.willReach ? 'text-green-500' : 'text-amber-500'"
class="w-4 h-4"
/>
class="w-4 h-4" />
<div>
<div class="text-sm font-medium">{{ milestone.label }}</div>
<div class="text-xs text-gray-500">
<div class="text-xs text-neutral-500">
{{ formatDate(milestone.date) }}
</div>
</div>
@ -42,8 +45,7 @@
variant="ghost"
color="red"
icon="i-heroicons-trash"
@click="removeMilestone(milestone.id)"
/>
@click="removeMilestone(milestone.id)" />
</div>
</div>
@ -54,12 +56,8 @@
<div class="space-y-3">
<UInput
v-model="newMilestone.label"
placeholder="Milestone name (e.g., 'Product launch')"
/>
<UInput
v-model="newMilestone.date"
type="date"
/>
placeholder="Milestone name (e.g., 'Product launch')" />
<UInput v-model="newMilestone.date" type="date" />
<div class="flex gap-2">
<UButton @click="saveMilestone">Save</UButton>
<UButton variant="ghost" @click="cancelAdd">Cancel</UButton>
@ -71,28 +69,28 @@
</template>
<script setup lang="ts">
const { milestoneStatus, addMilestone, removeMilestone } = useCoopBuilder()
const { milestoneStatus, addMilestone, removeMilestone } = useCoopBuilder();
const showAddForm = ref(false)
const newMilestone = ref({ label: '', date: '' })
const showAddForm = ref(false);
const newMilestone = ref({ label: "", date: "" });
const milestoneStatuses = computed(() => milestoneStatus())
const milestoneStatuses = computed(() => milestoneStatus());
function formatDate(dateString: string): string {
const date = new Date(dateString)
return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' })
const date = new Date(dateString);
return date.toLocaleDateString("en-US", { month: "short", year: "numeric" });
}
function saveMilestone() {
if (newMilestone.value.label && newMilestone.value.date) {
addMilestone(newMilestone.value.label, newMilestone.value.date)
newMilestone.value = { label: '', date: '' }
showAddForm.value = false
addMilestone(newMilestone.value.label, newMilestone.value.date);
newMilestone.value = { label: "", date: "" };
showAddForm.value = false;
}
}
function cancelAdd() {
newMilestone.value = { label: '', date: '' }
showAddForm.value = false
newMilestone.value = { label: "", date: "" };
showAddForm.value = false;
}
</script>
</script>

View file

@ -3,10 +3,10 @@
<template #header>
<h4 class="font-medium">Stress Test</h4>
</template>
<div class="space-y-4">
<div>
<label class="text-sm font-medium text-gray-700 mb-2 block">
<label class="text-sm font-medium text-neutral-700 mb-2 block">
Revenue Delay: {{ stress.revenueDelay }} months
</label>
<input
@ -15,13 +15,14 @@
min="0"
max="6"
step="1"
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
@input="(e) => updateStress({ revenueDelay: Number(e.target.value) })"
/>
class="w-full h-2 bg-neutral-200 rounded-lg appearance-none cursor-pointer"
@input="
(e) => updateStress({ revenueDelay: Number(e.target.value) })
" />
</div>
<div>
<label class="text-sm font-medium text-gray-700 mb-2 block">
<label class="text-sm font-medium text-neutral-700 mb-2 block">
Cost Shock: +{{ stress.costShockPct }}%
</label>
<input
@ -30,29 +31,32 @@
min="0"
max="30"
step="5"
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
@input="(e) => updateStress({ costShockPct: Number(e.target.value) })"
/>
class="w-full h-2 bg-neutral-200 rounded-lg appearance-none cursor-pointer"
@input="
(e) => updateStress({ costShockPct: Number(e.target.value) })
" />
</div>
<div>
<UCheckbox
:model-value="stress.grantLost"
label="Major Grant Lost"
@update:model-value="(val) => updateStress({ grantLost: val })"
/>
@update:model-value="(val) => updateStress({ grantLost: val })" />
</div>
<div v-if="isStressActive" class="p-3 bg-orange-50 border border-orange-200 rounded">
<div
v-if="isStressActive"
class="p-3 bg-orange-50 border border-orange-200 rounded">
<div class="text-sm">
<div class="flex items-center gap-2 text-orange-800 mb-1">
<UIcon name="i-heroicons-exclamation-triangle" class="w-4 h-4" />
<span class="font-medium">Stress Test Active</span>
</div>
<div class="text-orange-700">
Projected runway: <span class="font-semibold">{{ displayStressedRunway }}</span>
Projected runway:
<span class="font-semibold">{{ displayStressedRunway }}</span>
<span v-if="runwayChange !== 0" class="ml-2">
({{ runwayChange > 0 ? '+' : '' }}{{ runwayChange }} months)
({{ runwayChange > 0 ? "+" : "" }}{{ runwayChange }} months)
</span>
</div>
</div>
@ -62,32 +66,35 @@
</template>
<script setup lang="ts">
const { stress, updateStress, runwayMonths } = useCoopBuilder()
const { stress, updateStress, runwayMonths } = useCoopBuilder();
const isStressActive = computed(() =>
stress.value.revenueDelay > 0 ||
stress.value.costShockPct > 0 ||
stress.value.grantLost
)
const isStressActive = computed(
() =>
stress.value.revenueDelay > 0 ||
stress.value.costShockPct > 0 ||
stress.value.grantLost
);
const stressedRunway = computed(() => runwayMonths(undefined, { useStress: true }))
const normalRunway = computed(() => runwayMonths())
const stressedRunway = computed(() =>
runwayMonths(undefined, { useStress: true })
);
const normalRunway = computed(() => runwayMonths());
const displayStressedRunway = computed(() => {
const months = stressedRunway.value
if (!isFinite(months)) return '∞'
if (months < 1) return '<1 month'
return `${Math.round(months)} months`
})
const months = stressedRunway.value;
if (!isFinite(months)) return "∞";
if (months < 1) return "<1 month";
return `${Math.round(months)} months`;
});
const runwayChange = computed(() => {
const normal = normalRunway.value
const stressed = stressedRunway.value
if (!isFinite(normal) && !isFinite(stressed)) return 0
if (!isFinite(normal)) return -99 // Very large negative change
if (!isFinite(stressed)) return 0
return Math.round(stressed - normal)
})
</script>
const normal = normalRunway.value;
const stressed = stressedRunway.value;
if (!isFinite(normal) && !isFinite(stressed)) return 0;
if (!isFinite(normal)) return -99; // Very large negative change
if (!isFinite(stressed)) return 0;
return Math.round(stressed - normal);
});
</script>