Merge branch 'main' into phase/32-weather-warmth
This commit is contained in:
@@ -26,6 +26,7 @@ describe('t', () => {
|
||||
.toBe('Not enough money: 8 000 of 10 000 is committed, 2 000 free, 12 000 needed.');
|
||||
expect(t('staffUncoveredShort', { label: 'Начальные классы', min: 1, max: 4, n: 3 }))
|
||||
.toBe('Начальные классы (1–4) — 3 short');
|
||||
expect(t('peopleCarryMass', { held: '1.2', cap: '14' })).toBe('1.2 / 14 kg');
|
||||
expect(t('mapOccupancy', { name: 'Кабинет 204', activity: 'Математика · 5Б' }))
|
||||
.toBe('Кабинет 204 (Математика · 5Б)');
|
||||
expect(t('weatherPrecip', { temp: '\u22125', precip: t('precipSnow') })).toBe('\u22125 °C, snow');
|
||||
|
||||
@@ -128,6 +128,9 @@ const ru = {
|
||||
peopleSkills: 'Навыки',
|
||||
peopleTraits: 'Черты',
|
||||
peopleNeeds: 'Нужды',
|
||||
peopleApparel: 'Одежда',
|
||||
peopleCarry: 'Ноша',
|
||||
peopleCarryMass: '{held} / {cap} кг',
|
||||
peopleFamily: 'Семья',
|
||||
peopleParents: 'Родители',
|
||||
peopleChildren: 'Дети',
|
||||
@@ -349,6 +352,9 @@ const en: Messages = {
|
||||
peopleSkills: 'Skills',
|
||||
peopleTraits: 'Traits',
|
||||
peopleNeeds: 'Needs',
|
||||
peopleApparel: 'Clothes',
|
||||
peopleCarry: 'Carried',
|
||||
peopleCarryMass: '{held} / {cap} kg',
|
||||
peopleFamily: 'Family',
|
||||
peopleParents: 'Parents',
|
||||
peopleChildren: 'Children',
|
||||
|
||||
@@ -291,6 +291,28 @@ export interface PersonCard {
|
||||
readonly siblings: readonly PersonRel[];
|
||||
readonly partners: readonly PersonRel[];
|
||||
};
|
||||
readonly worn: readonly WornItem[];
|
||||
readonly carried: readonly CarriedItem[];
|
||||
readonly carryMass: number;
|
||||
readonly carryCapacity: number;
|
||||
}
|
||||
|
||||
export interface WornItem {
|
||||
readonly defName: string;
|
||||
readonly label: string;
|
||||
readonly color: string | null;
|
||||
readonly colorLabel: string | null;
|
||||
readonly layers: readonly DefLabel[];
|
||||
}
|
||||
|
||||
export interface CarriedItem {
|
||||
readonly defName: string;
|
||||
readonly label: string;
|
||||
readonly color: string | null;
|
||||
readonly colorLabel: string | null;
|
||||
readonly subject: string | null;
|
||||
readonly subjectLabel: string | null;
|
||||
readonly mass: number;
|
||||
}
|
||||
|
||||
export async function fetchPeople(schoolId: number, query: PeopleQuery, lang: string): Promise<PeoplePage> {
|
||||
|
||||
@@ -650,6 +650,11 @@ body {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.people__carry-mass {
|
||||
margin: 0 0 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.people__pair {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
|
||||
@@ -55,6 +55,10 @@ function personCard(): PersonCard {
|
||||
activity: null,
|
||||
activityLabel: null,
|
||||
family: { parents: [], children: [], siblings: [], partners: [] },
|
||||
worn: [],
|
||||
carried: [],
|
||||
carryMass: 0,
|
||||
carryCapacity: 0,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* @vitest-environment happy-dom
|
||||
*/
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
import type { PersonCard } from '../net/api.ts';
|
||||
import { getLocale, setLocale } from '../i18n/locale.ts';
|
||||
import { t } from '../i18n/strings.ts';
|
||||
import { renderPersonCard } from './personCard.ts';
|
||||
|
||||
const initial = getLocale();
|
||||
|
||||
afterEach(() => setLocale(initial));
|
||||
|
||||
function card(overrides: Partial<PersonCard> = {}): PersonCard {
|
||||
return {
|
||||
id: 'f0.c0',
|
||||
fullName: 'Иванова Мария',
|
||||
surname: 'Иванова',
|
||||
given: 'Мария',
|
||||
patronymic: '',
|
||||
female: true,
|
||||
age: 12,
|
||||
birthDate: '2000-03-14',
|
||||
roles: ['student'],
|
||||
classYear: 5,
|
||||
classLetter: 'А',
|
||||
classId: 'class-1',
|
||||
position: null,
|
||||
positionLabel: null,
|
||||
body: [],
|
||||
skills: [],
|
||||
traits: [],
|
||||
needs: [],
|
||||
activity: null,
|
||||
activityLabel: null,
|
||||
family: { parents: [], children: [], siblings: [], partners: [] },
|
||||
worn: [
|
||||
{
|
||||
defName: 'Shirt',
|
||||
label: 'Рубашка',
|
||||
color: 'White',
|
||||
colorLabel: 'Белый',
|
||||
layers: [{ defName: 'Top', label: 'Верх' }],
|
||||
},
|
||||
],
|
||||
carried: [
|
||||
{
|
||||
defName: 'Textbook',
|
||||
label: 'Учебник',
|
||||
color: null,
|
||||
colorLabel: null,
|
||||
subject: 'Mathematics',
|
||||
subjectLabel: 'Математика',
|
||||
mass: 0.4,
|
||||
},
|
||||
],
|
||||
carryMass: 1.2,
|
||||
carryCapacity: 14,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('renderPersonCard', () => {
|
||||
it('shows worn layers with colour and carried mass in the same scroll', () => {
|
||||
setLocale('ru');
|
||||
const root = document.createElement('div');
|
||||
renderPersonCard(root, card(), () => {});
|
||||
|
||||
expect(root.textContent).toContain(t('peopleApparel'));
|
||||
expect(root.textContent).toContain('Верх');
|
||||
expect(root.textContent).toContain('Рубашка · Белый');
|
||||
expect(root.textContent).toContain(t('peopleCarry'));
|
||||
expect(root.textContent).toContain(t('peopleCarryMass', { held: '1.2', cap: '14' }));
|
||||
expect(root.textContent).toContain('Учебник · Математика');
|
||||
expect(root.querySelector('.people__tabs')).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -47,6 +47,8 @@ export function renderPersonCard(
|
||||
appendPairs(parent, t('peopleSkills'), card.skills);
|
||||
appendTags(parent, t('peopleTraits'), card.traits.map((row) => row.label));
|
||||
appendNeeds(parent, t('peopleNeeds'), card.needs);
|
||||
appendApparel(parent, card.worn);
|
||||
appendCarry(parent, card);
|
||||
|
||||
const family = section(t('peopleFamily'));
|
||||
appendRelatives(family, t('peopleParents'), card.family.parents, onRelative);
|
||||
@@ -112,6 +114,58 @@ function appendPairs(
|
||||
parent.append(section(title), grid);
|
||||
}
|
||||
|
||||
function appendApparel(parent: HTMLElement, rows: PersonCard['worn']): void {
|
||||
if (rows.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const grid = el('dl', { class: 'people__pairs' });
|
||||
for (const row of rows) {
|
||||
const layers = row.layers.map((layer) => layer.label).join(', ');
|
||||
const color = row.colorLabel ?? row.color;
|
||||
const value = color !== null && color.length > 0 ? `${row.label} · ${color}` : row.label;
|
||||
grid.append(
|
||||
el(
|
||||
'div',
|
||||
{ class: 'people__pair' },
|
||||
el('dt', { text: layers.length > 0 ? layers : row.label }),
|
||||
el('dd', { text: value }),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
parent.append(section(t('peopleApparel')), grid);
|
||||
}
|
||||
|
||||
function appendCarry(parent: HTMLElement, card: PersonCard): void {
|
||||
const mass = el('p', {
|
||||
class: 'people__carry-mass',
|
||||
text: t('peopleCarryMass', { held: formatMass(card.carryMass), cap: formatMass(card.carryCapacity) }),
|
||||
});
|
||||
const grid = el('dl', { class: 'people__pairs' });
|
||||
for (const row of card.carried) {
|
||||
const extra = row.subjectLabel ?? row.colorLabel;
|
||||
const value = extra !== null && extra.length > 0 ? `${row.label} · ${extra}` : row.label;
|
||||
grid.append(
|
||||
el(
|
||||
'div',
|
||||
{ class: 'people__pair' },
|
||||
el('dt', { text: value }),
|
||||
el('dd', { text: formatMass(row.mass) }),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
parent.append(section(t('peopleCarry')), mass);
|
||||
if (card.carried.length > 0) {
|
||||
parent.append(grid);
|
||||
}
|
||||
}
|
||||
|
||||
function formatMass(value: number): string {
|
||||
return String(Math.round(value * 100) / 100);
|
||||
}
|
||||
|
||||
function appendTags(parent: HTMLElement, title: string, values: readonly string[]): void {
|
||||
if (values.length === 0) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user