mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-28 17:01:18 +00:00
FIX - align health app with observable data
Remove inferred condition, recovery, and player-health values from the Health UI and NUI contract. Keep browser mocks and tests aligned, correct the default development API port, remove the misleading Medical ID disclosure, and apply the shared page gutter to prevent clipped cards.
This commit is contained in:
@@ -22,7 +22,6 @@ const overview: HealthOverview = {
|
||||
playerName: 'Alex Morgan',
|
||||
},
|
||||
previousWeekSteps: 0,
|
||||
snapshot: { healthPercent: 100 },
|
||||
}
|
||||
|
||||
describe('health store', () => {
|
||||
|
||||
@@ -368,10 +368,6 @@ const healthFallbackLocales = {
|
||||
kilocalories: '{count} kcal',
|
||||
thisWeek: 'This week',
|
||||
snapshot: 'Health snapshot',
|
||||
condition: 'Condition',
|
||||
recovery: 'Recovery',
|
||||
currentHealth: 'Current health',
|
||||
conditions: { good: 'Good', fair: 'Fair', low: 'Low' },
|
||||
trends: {
|
||||
title: 'Trends',
|
||||
week: 'Week',
|
||||
@@ -402,8 +398,6 @@ const healthFallbackLocales = {
|
||||
phoneNumber: 'Phone number',
|
||||
emergencyCall: 'Emergency call',
|
||||
callContact: 'Call emergency contact',
|
||||
privacy:
|
||||
'Information is stored with your character and can be shown to emergency services.',
|
||||
saveFailed: 'Medical ID could not be saved.',
|
||||
},
|
||||
errors: {
|
||||
|
||||
@@ -17,17 +17,12 @@ export type HealthMedicalId = {
|
||||
playerName: string
|
||||
}
|
||||
|
||||
export type HealthSnapshot = {
|
||||
healthPercent: number
|
||||
}
|
||||
|
||||
export type HealthOverview = {
|
||||
dailyStepGoal: number
|
||||
days: HealthActivityDay[]
|
||||
emergencyNumber: string
|
||||
medicalId: HealthMedicalId
|
||||
previousWeekSteps: number
|
||||
snapshot: HealthSnapshot
|
||||
}
|
||||
|
||||
export type HealthMedicalIdInput = Omit<HealthMedicalId, 'playerName'>
|
||||
|
||||
@@ -34,7 +34,7 @@ describe('nuiCall', () => {
|
||||
})
|
||||
expect(vi.getTimerCount()).toBe(0)
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
'http://localhost:3001/api/test',
|
||||
'http://localhost:3002/api/test',
|
||||
expect.objectContaining({ signal: expect.any(AbortSignal) }),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -15,7 +15,7 @@ export async function nuiCall<T = unknown>(
|
||||
'apiPort',
|
||||
)
|
||||
const baseUrl = import.meta.env.DEV
|
||||
? `http://localhost:${developmentPort ?? '3001'}/api`
|
||||
? `http://localhost:${developmentPort ?? '3002'}/api`
|
||||
: `https://${resourceName}`
|
||||
const requestData = import.meta.env.DEV
|
||||
? {
|
||||
|
||||
@@ -8,6 +8,7 @@ describe('HealthApp Sky UI contract', () => {
|
||||
expect(source).toContain('<SkyAppPage')
|
||||
expect(source).toContain('<SkyNavbar')
|
||||
expect(source).toContain('<SkyScrollArea')
|
||||
expect(source).toContain('padded with-tabbar')
|
||||
expect(source).toContain('with-tabbar')
|
||||
expect(source).toContain('<SkyPillNavigation')
|
||||
expect(source).not.toContain(
|
||||
@@ -26,4 +27,10 @@ describe('HealthApp Sky UI contract', () => {
|
||||
expect(source).toContain("nuiCall('calls:dial'")
|
||||
expect(source).toContain('health.saveMedicalId')
|
||||
})
|
||||
|
||||
it('does not present values the phone cannot observe', () => {
|
||||
expect(source).not.toContain('healthPercent')
|
||||
expect(source).not.toContain('recoveryPercent')
|
||||
expect(source).not.toContain('conditionKey')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
MapPin,
|
||||
Phone,
|
||||
RefreshCw,
|
||||
ShieldCheck,
|
||||
Timer,
|
||||
UserRound,
|
||||
} from 'lucide-vue-next'
|
||||
@@ -102,20 +101,6 @@ const trendCopy = computed(() => {
|
||||
}
|
||||
return phone.t('Apps.health.trends.same')
|
||||
})
|
||||
const healthPercent = computed(
|
||||
() => overview.value?.snapshot.healthPercent ?? 0,
|
||||
)
|
||||
const conditionKey = computed(() =>
|
||||
healthPercent.value >= 70
|
||||
? 'good'
|
||||
: healthPercent.value >= 35
|
||||
? 'fair'
|
||||
: 'low',
|
||||
)
|
||||
const recoveryPercent = computed(() => {
|
||||
const activeLoad = Math.min(20, Math.round((today.value?.activeSeconds ?? 0) / 180))
|
||||
return Math.max(0, Math.min(100, healthPercent.value - activeLoad))
|
||||
})
|
||||
const dateRange = computed(() => {
|
||||
if (!visibleDays.value.length) return ''
|
||||
const start = new Date(`${visibleDays.value[0].date}T12:00:00`)
|
||||
@@ -248,7 +233,7 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</SkyNavbar>
|
||||
|
||||
<SkyScrollArea class="health-content" with-tabbar>
|
||||
<SkyScrollArea class="health-content" padded with-tabbar>
|
||||
<div v-if="health.isLoading && !overview" class="health-loading">
|
||||
<SkySpinner />
|
||||
<span>{{ phone.t('Apps.health.loading') }}</span>
|
||||
@@ -333,34 +318,6 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="health-section">
|
||||
<h2>{{ phone.t('Apps.health.snapshot') }}</h2>
|
||||
<div class="health-list">
|
||||
<div class="health-list__row">
|
||||
<span class="health-list__icon health-list__icon--green">
|
||||
<HeartPulse :size="21" />
|
||||
</span>
|
||||
<span>{{ phone.t('Apps.health.condition') }}</span>
|
||||
<strong :class="`health-value--${conditionKey}`">{{
|
||||
phone.t(`Apps.health.conditions.${conditionKey}`)
|
||||
}}</strong>
|
||||
</div>
|
||||
<div class="health-list__row">
|
||||
<span class="health-list__icon health-list__icon--orange">
|
||||
<Activity :size="21" />
|
||||
</span>
|
||||
<span>{{ phone.t('Apps.health.recovery') }}</span>
|
||||
<strong>{{ recoveryPercent }}%</strong>
|
||||
</div>
|
||||
<div class="health-list__row">
|
||||
<span class="health-list__icon">
|
||||
<ShieldCheck :size="21" />
|
||||
</span>
|
||||
<span>{{ phone.t('Apps.health.currentHealth') }}</span>
|
||||
<strong>{{ healthPercent }}%</strong>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template v-else-if="activeTab === 'trends'">
|
||||
@@ -568,10 +525,6 @@ onBeforeUnmount(() => {
|
||||
}}
|
||||
</SkyButton>
|
||||
|
||||
<p class="health-privacy-note">
|
||||
<ShieldCheck :size="22" aria-hidden="true" />
|
||||
<span>{{ phone.t('Apps.health.medicalId.privacy') }}</span>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<p v-if="actionError" class="health-action-error" role="alert">
|
||||
@@ -890,15 +843,6 @@ onBeforeUnmount(() => {
|
||||
color: var(--health-orange);
|
||||
}
|
||||
|
||||
.health-list__row .health-value--good {
|
||||
color: var(--health-green);
|
||||
}
|
||||
|
||||
.health-list__row .health-value--fair {
|
||||
color: var(--health-orange);
|
||||
}
|
||||
|
||||
.health-list__row .health-value--low,
|
||||
.health-list__row .health-value--accent {
|
||||
color: var(--health-accent);
|
||||
}
|
||||
@@ -1121,25 +1065,6 @@ onBeforeUnmount(() => {
|
||||
gap: 9px;
|
||||
}
|
||||
|
||||
.health-privacy-note {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin: 0;
|
||||
padding: 14px 16px;
|
||||
border: calc(var(--sky-hairline-scale) * 1px) solid var(--health-panel-border);
|
||||
border-radius: 18px;
|
||||
background: var(--health-panel);
|
||||
color: var(--sky-muted);
|
||||
font-size: 13px;
|
||||
line-height: 1.35;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.health-privacy-note svg {
|
||||
flex: none;
|
||||
color: var(--health-green);
|
||||
}
|
||||
|
||||
.health-form {
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
|
||||
@@ -72,7 +72,6 @@ function healthOverview() {
|
||||
emergencyNumber: '911',
|
||||
medicalId: healthMedicalId,
|
||||
previousWeekSteps: 36980,
|
||||
snapshot: { healthPercent: 96 },
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -326,7 +326,7 @@ async function verifyStatefulActions(baseUrl) {
|
||||
assert.equal(health.dailyStepGoal, 8000)
|
||||
assert.equal(health.days.length, 7)
|
||||
assert.equal(health.days.at(-1).steps, 6420)
|
||||
assert.equal(health.snapshot.healthPercent, 96)
|
||||
assert.equal('snapshot' in health, false)
|
||||
|
||||
const medicalId = await expectSuccess(
|
||||
baseUrl,
|
||||
|
||||
@@ -202,10 +202,6 @@ Locales["en"] = {
|
||||
kilocalories = "{count} kcal",
|
||||
thisWeek = "This week",
|
||||
snapshot = "Health snapshot",
|
||||
condition = "Condition",
|
||||
recovery = "Recovery",
|
||||
currentHealth = "Current health",
|
||||
conditions = { good = "Good", fair = "Fair", low = "Low" },
|
||||
trends = {
|
||||
title = "Trends", week = "Week", month = "Month", total = "{count} steps",
|
||||
more = "{count}% more than last week", less = "{count}% less than last week",
|
||||
@@ -219,7 +215,6 @@ Locales["en"] = {
|
||||
noneRecorded = "None recorded", emergencyContact = "Emergency contact",
|
||||
contactName = "Contact name", relation = "Relation", phoneNumber = "Phone number",
|
||||
emergencyCall = "Emergency call", callContact = "Call emergency contact",
|
||||
privacy = "Information is stored with your character and can be shown to emergency services.",
|
||||
saveFailed = "Medical ID could not be saved.",
|
||||
},
|
||||
errors = {
|
||||
|
||||
@@ -5,19 +5,6 @@ local step_progress = 0.0
|
||||
local last_coords = nil
|
||||
local last_report_at = GetGameTimer()
|
||||
|
||||
local function health_snapshot()
|
||||
local ped = PlayerPedId()
|
||||
if not DoesEntityExist(ped) then
|
||||
return { healthPercent = 0 }
|
||||
end
|
||||
|
||||
local health = GetEntityHealth(ped)
|
||||
local maximum = math.max(1, GetEntityMaxHealth(ped))
|
||||
local base = maximum > 100 and 100 or 0
|
||||
local percentage = math.floor(math.max(0, math.min(100, (health - base) / (maximum - base) * 100)) + 0.5)
|
||||
return { healthPercent = percentage }
|
||||
end
|
||||
|
||||
local function flush_activity()
|
||||
last_report_at = GetGameTimer()
|
||||
local distance_meters = math.floor(pending_distance + 0.5)
|
||||
@@ -95,9 +82,6 @@ end)
|
||||
|
||||
RegisterNUICallback("health:overview", function(data, cb)
|
||||
local result = Bridge.Callbacks.Trigger("sky_phone:health:overview", data or {})
|
||||
if result and result.success and result.data then
|
||||
result.data.snapshot = health_snapshot()
|
||||
end
|
||||
cb(result or { success = false, error = "request_failed" })
|
||||
end)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user