From 2b9fc4f7aed7ed79e5382ac6334e6211fe8d9be8 Mon Sep 17 00:00:00 2001 From: patinen Date: Thu, 29 Jan 2026 09:09:54 +0200 Subject: [PATCH 1/5] refactor(members): migrate to V2 roles-based schema --- .../src/entities/About/api/aboutApi.ts | 2 +- .../src/entities/Member/api/mappers.ts | 167 ++++++++++++++---- .../src/entities/Member/api/memberTeamsApi.ts | 4 +- .../src/entities/Member/api/membersApi.ts | 14 +- .../src/entities/Member/api/translations.ts | 53 +++++- .../src/entities/Member/model/types/types.ts | 27 ++- .../src/entities/Member/ui/MemberItem.tsx | 8 +- 7 files changed, 213 insertions(+), 62 deletions(-) diff --git a/frontend-next-migration/src/entities/About/api/aboutApi.ts b/frontend-next-migration/src/entities/About/api/aboutApi.ts index b195e6c62..69997d41f 100644 --- a/frontend-next-migration/src/entities/About/api/aboutApi.ts +++ b/frontend-next-migration/src/entities/About/api/aboutApi.ts @@ -26,7 +26,7 @@ export const aboutApi = directusApi.injectEndpoints({ queryFn: async () => { try { const members = await client.request( - readItems('members', { limit: 500 }), + readItems('members_v2', { limit: 500 }), ); const normalize = (input: unknown) => diff --git a/frontend-next-migration/src/entities/Member/api/mappers.ts b/frontend-next-migration/src/entities/Member/api/mappers.ts index dcf5522d7..2db192a8c 100644 --- a/frontend-next-migration/src/entities/Member/api/mappers.ts +++ b/frontend-next-migration/src/entities/Member/api/mappers.ts @@ -1,6 +1,6 @@ import { faGithub, faLinkedin, faInstagram, faFacebook } from '@fortawesome/free-brands-svg-icons'; import { faGlobe, faEnvelope } from '@fortawesome/free-solid-svg-icons'; -import { Member, Team } from '@/entities/Member/model/types/types'; +import { Member, Team, MemberRole, Department } from '@/entities/Member/model/types/types'; import { getDepartmentTranslation, getTeamTranslation, getLanguageCode } from './translations'; /** @@ -81,11 +81,103 @@ const fiDepartmentOrder = [ */ /** - * Organizes members into teams and departments based on their properties, + * Creates or retrieves a team from the teams map. + */ +const getOrCreateTeam = ( + teamsMap: Map, + memberTeam: Team, + fullLanguageCode: string, +): Team => { + let team = teamsMap.get(memberTeam.id); + if (!team) { + const teamName = getTeamTranslation(memberTeam.translations || [], fullLanguageCode); + team = { + id: memberTeam.id, + name: teamName || '', + translations: memberTeam.translations || [], + members: [], + departments: [], + }; + teamsMap.set(memberTeam.id, team); + } + return team; +}; + +/** + * Creates or retrieves a department within a team. + */ +const getOrCreateDepartment = ( + team: Team, + memberDepartment: Department, + fullLanguageCode: string, +): Department => { + let department = team.departments.find( + (departmentItem) => departmentItem.id === memberDepartment.id, + ); + if (!department) { + const departmentName = getDepartmentTranslation( + memberDepartment.translations || [], + fullLanguageCode, + ); + department = { + id: memberDepartment.id, + name: departmentName || '', + translations: memberDepartment.translations || [], + members: [], + }; + team.departments.push(department); + } + return department; +}; + +/** + * Adds a member to a team or department if not already present. + */ +const addMemberToTeamOrDepartment = ( + member: Member, + team: Team, + department: Department | null, +): void => { + if (department) { + if (!department.members.find((memberItem: Member) => memberItem.id === member.id)) { + department.members.push(member); + } + } else { + if (!team.members.find((memberItem: Member) => memberItem.id === member.id)) { + team.members.push(member); + } + } +}; + +/** + * Processes a single role and adds the member to the appropriate team/department. + */ +const processRole = ( + role: MemberRole, + member: Member, + teamsMap: Map, + fullLanguageCode: string, +): void => { + const memberTeam = role.team; + if (!memberTeam) { + return; + } + + const team = getOrCreateTeam(teamsMap, memberTeam, fullLanguageCode); + const memberDepartment = role.department; + const department = memberDepartment + ? getOrCreateDepartment(team, memberDepartment, fullLanguageCode) + : null; + + addMemberToTeamOrDepartment(member, team, department); +}; + +/** + * Organizes members into teams and departments based on their roles, * and sorts both the members alphabetically within their teams and departments, * as well as the teams based on a predefined order dictated by language. * - * @param {Member[]} members - An array of member objects, each containing associated team and department data. + * @param {Member[]} members - An array of member objects, each containing roles with team and department data. * @param {string} lng - The language code used to determine which language to use for translations and sorting. * @returns {OrganizedData} The organized data containing teams mapped by their IDs. */ @@ -98,40 +190,13 @@ export const organizeMembers = (members: Member[], lng: string) => { const departmentOrder = lng === 'fi' ? fiDepartmentOrder : enDepartmentOrder; members.forEach((member: Member) => { - const memberTeam = member.team; - const memberDepartment = member.department; - - if (memberTeam) { - let team = teamsMap.get(memberTeam.id); - if (!team) { - const teamName = getTeamTranslation( - memberTeam.translations || [], - fullLanguageCode, - ); - - team = { ...memberTeam, name: teamName, members: [], departments: [] }; - teamsMap.set(memberTeam.id, team); - } - - if (memberDepartment) { - let department = team.departments.find( - (departmentItem) => departmentItem.id === memberDepartment.id, - ); - if (!department) { - const departmentName = getDepartmentTranslation( - memberDepartment.translations || [], - fullLanguageCode, - ); - - department = { ...memberDepartment, name: departmentName, members: [] }; - team.departments.push(department); - } - - department.members.push(member); - } else { - team.members.push(member); - } + if (!member.roles || member.roles.length === 0) { + return; } + + member.roles.forEach((role: MemberRole) => { + processRole(role, member, teamsMap, fullLanguageCode); + }); }); teamsMap.forEach((team) => { team.members.sort((a, b) => a.name.localeCompare(b.name)); @@ -155,10 +220,36 @@ export const organizeMembers = (members: Member[], lng: string) => { department.members.sort((a, b) => a.name.localeCompare(b.name)); }); }); - const sortedTeams = Array.from(teamsMap.values()).sort((a, b) => { + // Sort teams according to predefined order + // Teams not in the order array go to the end, sorted by name + // Filter out teams with empty names + const validTeams = Array.from(teamsMap.values()).filter( + (team) => team.name && team.name.trim() !== '', + ); + + // Sort teams according to predefined order + // Teams not in the order array go to the end, sorted by name + const sortedTeams = validTeams.sort((a, b) => { const indexA = order.indexOf(a.name); const indexB = order.indexOf(b.name); - return indexA - indexB; + + // Both teams are in the order array - sort by position + if (indexA !== -1 && indexB !== -1) { + return indexA - indexB; + } + + // Team A is in order, Team B is not - A comes first + if (indexA !== -1 && indexB === -1) { + return -1; + } + + // Team B is in order, Team A is not - B comes first + if (indexA === -1 && indexB !== -1) { + return 1; + } + + // Neither team is in order array - sort alphabetically + return a.name.localeCompare(b.name); }); return { teamsMap: new Map(sortedTeams.map((team) => [team.id, team])) }; diff --git a/frontend-next-migration/src/entities/Member/api/memberTeamsApi.ts b/frontend-next-migration/src/entities/Member/api/memberTeamsApi.ts index df7b84520..7781b0cb5 100644 --- a/frontend-next-migration/src/entities/Member/api/memberTeamsApi.ts +++ b/frontend-next-migration/src/entities/Member/api/memberTeamsApi.ts @@ -14,7 +14,7 @@ const client = createDirectus(directusBaseUrl).with(rest()); * @module memberTeamsApi * * @endpoint getMemberTeams - * Endpoint to fetch member teams from the Directus `teams` collection. + * Endpoint to fetch member teams from the Directus `teams_v2` collection. * Retrieves information about teams, including their translations. * * @returns {Record[]} Response containing an array of teams. @@ -25,7 +25,7 @@ const memberTeamsApi = directusApi.injectEndpoints({ getMemberTeams: builder.query({ queryFn: async (_arg: void) => { const teams = await client.request( - readItems('teams', { + readItems('teams_v2', { fields: ['id', 'translations.*'], deep: { translations: true }, }), diff --git a/frontend-next-migration/src/entities/Member/api/membersApi.ts b/frontend-next-migration/src/entities/Member/api/membersApi.ts index 60b16d7a2..d9f0a26b4 100644 --- a/frontend-next-migration/src/entities/Member/api/membersApi.ts +++ b/frontend-next-migration/src/entities/Member/api/membersApi.ts @@ -13,14 +13,15 @@ const membersApi = directusApi.injectEndpoints({ queryFn: async (): Promise<{ data: Member[] } | { error: FetchBaseQueryError }> => { try { const members = await client.request[]>( - readItems('members', { + readItems('members_v2', { fields: [ '*', - 'department.*', - 'department.translations.*', - 'team.*', - 'team.translations.*', - 'translations.*', + 'roles.id', + 'roles.team.*', + 'roles.team.translations.*', + 'roles.department.*', + 'roles.department.translations.*', + 'roles.translations.*', 'logo.*', 'portrait.id', 'portrait.title', @@ -28,6 +29,7 @@ const membersApi = directusApi.injectEndpoints({ limit: 500, }), ); + return { data: members as Member[] }; } catch (error: any) { return { diff --git a/frontend-next-migration/src/entities/Member/api/translations.ts b/frontend-next-migration/src/entities/Member/api/translations.ts index b5f3714f1..fbac703ac 100644 --- a/frontend-next-migration/src/entities/Member/api/translations.ts +++ b/frontend-next-migration/src/entities/Member/api/translations.ts @@ -1,4 +1,9 @@ -import { Translation, DepartmentTranslation, TeamTranslation } from '../model/types/types'; +import { + Translation, + DepartmentTranslation, + TeamTranslation, + RoleTranslation, +} from '../model/types/types'; /** * Returns the language code in a standardized format. @@ -27,24 +32,62 @@ const getTranslation = ( key: keyof T, defaultValue: string = '', ): string => { - const translation = translations.find((t) => t.languages_code === languageCode); - return translation && key in translation ? (translation[key] as string) : defaultValue; + if (!translations || translations.length === 0) { + return defaultValue; + } + + // Try exact match first + let translation = translations.find((t) => t.languages_code === languageCode); + + // If no exact match, try fallback to 'en-US' or first available + if (!translation) { + translation = + translations.find((t) => t.languages_code === 'en-US') || + translations.find((t) => t.languages_code === 'fi-FI') || + translations[0]; + } + + if (translation) { + // Debug: log what keys are available + if (!(key in translation)) { + console.warn('[getTranslation] Key not found in translation:', { + key, + availableKeys: Object.keys(translation), + translation, + languageCode, + }); + } + + if (key in translation) { + const value = translation[key] as string; + return value || defaultValue; + } + } + + return defaultValue; }; export const getTaskTranslation = (translations: Translation[], languageCode: string): string => { return getTranslation(translations, languageCode, 'task', ''); }; +export const getRoleTaskTranslation = ( + translations: RoleTranslation[], + languageCode: string, +): string => { + return getTranslation(translations, languageCode, 'task', ''); +}; + export const getDepartmentTranslation = ( translations: DepartmentTranslation[], languageCode: string, ): string => { - return getTranslation(translations, languageCode, 'department', ''); + return getTranslation(translations, languageCode, 'name', ''); }; export const getTeamTranslation = ( translations: TeamTranslation[], languageCode: string, ): string => { - return getTranslation(translations, languageCode, 'team', ''); + return getTranslation(translations, languageCode, 'name', ''); }; diff --git a/frontend-next-migration/src/entities/Member/model/types/types.ts b/frontend-next-migration/src/entities/Member/model/types/types.ts index 42e643bf5..11bb8c68b 100644 --- a/frontend-next-migration/src/entities/Member/model/types/types.ts +++ b/frontend-next-migration/src/entities/Member/model/types/types.ts @@ -10,7 +10,6 @@ export interface Asset { export interface Member { id: number; name: string; - task?: string; email?: string; logo?: Logo | null; website?: string; @@ -19,12 +18,17 @@ export interface Member { facebook?: string | null; instagram?: string | null; language?: string; - department?: Department | null; - team?: Team | null; - translations?: Translation[]; + roles?: MemberRole[]; portrait?: Asset | null; } +export interface MemberRole { + id: number; + team?: Team | null; + department?: Department | null; + translations?: RoleTranslation[]; +} + export interface Department { id: number; name: string; @@ -42,16 +46,16 @@ export interface Team { export interface DepartmentTranslation { id: number; - departments_id: number; + departments_v2_id: number; languages_code: string; - department: string; + name: string; } export interface TeamTranslation { id: number; - teams_id: number; + teams_v2_id: number; languages_code: string; - team: string; + name: string; } export interface Translation { @@ -60,3 +64,10 @@ export interface Translation { languages_code: string; task?: string; } + +export interface RoleTranslation { + id: number; + members_roles_id: number; + languages_code: string; + task?: string; +} diff --git a/frontend-next-migration/src/entities/Member/ui/MemberItem.tsx b/frontend-next-migration/src/entities/Member/ui/MemberItem.tsx index 93e553a06..47089e162 100644 --- a/frontend-next-migration/src/entities/Member/ui/MemberItem.tsx +++ b/frontend-next-migration/src/entities/Member/ui/MemberItem.tsx @@ -6,7 +6,7 @@ import { getLinks } from '../api/mappers'; import { Member } from '../model/types/types'; import cls from './MemberItem.module.scss'; import { envHelper } from '@/shared/const/envHelper'; -import { getTaskTranslation, getLanguageCode } from '../api/translations'; +import { getRoleTaskTranslation, getLanguageCode } from '../api/translations'; interface MemberItemProps { member: Member; @@ -36,7 +36,11 @@ const MemberItem: FC = ({ member, language }) => { : null; const fullLanguageCode = getLanguageCode(language); - const task = getTaskTranslation(member.translations || [], fullLanguageCode); + // Get task from first role's translations, or fall back to empty string + const task = + member.roles && member.roles.length > 0 && member.roles[0].translations + ? getRoleTaskTranslation(member.roles[0].translations, fullLanguageCode) + : ''; return (
  • From 1e196abb87b13732c888ffe0aa205e5232ceb13d Mon Sep 17 00:00:00 2001 From: patinen Date: Thu, 29 Jan 2026 09:28:32 +0200 Subject: [PATCH 2/5] updated mappers.test to match the refactor --- .../src/entities/Member/api/mappers.test.ts | 104 ++++++++++-------- 1 file changed, 57 insertions(+), 47 deletions(-) diff --git a/frontend-next-migration/src/entities/Member/api/mappers.test.ts b/frontend-next-migration/src/entities/Member/api/mappers.test.ts index 974d9871e..b4487064a 100644 --- a/frontend-next-migration/src/entities/Member/api/mappers.test.ts +++ b/frontend-next-migration/src/entities/Member/api/mappers.test.ts @@ -1,13 +1,13 @@ import { organizeMembers } from './mappers'; -import { Member, Team, Department } from '@/entities/Member/model/types/types'; +import { Member, Team, Department, MemberRole } from '@/entities/Member/model/types/types'; const mockTeams: Team[] = [ { id: 1, name: 'Development', translations: [ - { id: 101, teams_id: 1, languages_code: 'en-US', team: 'Development' }, - { id: 102, teams_id: 1, languages_code: 'fi-FI', team: 'Ohjelmistokehitys' }, + { id: 101, teams_v2_id: 1, languages_code: 'en-US', name: 'Development' }, + { id: 102, teams_v2_id: 1, languages_code: 'fi-FI', name: 'Ohjelmistokehitys' }, ], members: [], departments: [], @@ -16,8 +16,8 @@ const mockTeams: Team[] = [ id: 2, name: 'Game Design', translations: [ - { id: 103, teams_id: 2, languages_code: 'en-US', team: 'Game Design' }, - { id: 104, teams_id: 2, languages_code: 'fi-FI', team: 'Pelisuunnittelu' }, + { id: 103, teams_v2_id: 2, languages_code: 'en-US', name: 'Game Design' }, + { id: 104, teams_v2_id: 2, languages_code: 'fi-FI', name: 'Pelisuunnittelu' }, ], members: [], departments: [], @@ -33,27 +33,32 @@ describe('organizeMembers', () => { github: 'https://github.com/Jonroi', linkedin: 'https://www.linkedin.com/in/joni-roine/', website: 'https://jonroi.netlify.app/', - team: mockTeams[0], - department: { - id: 10, - name: 'Website Developer', - translations: [ - { - id: 110, - departments_id: 10, - languages_code: 'en-US', - department: 'Website Developer', - }, - { - id: 111, - departments_id: 10, - languages_code: 'fi-FI', - department: 'Verkkosivukehittäjä', - }, - ], - members: [], - } as Department, - translations: [], + roles: [ + { + id: 1, + team: mockTeams[0], + department: { + id: 10, + name: 'Website Developer', + translations: [ + { + id: 110, + departments_v2_id: 10, + languages_code: 'en-US', + name: 'Website Developer', + }, + { + id: 111, + departments_v2_id: 10, + languages_code: 'fi-FI', + name: 'Verkkosivukehittäjä', + }, + ], + members: [], + } as Department, + translations: [], + } as MemberRole, + ], }, { id: 2, @@ -62,27 +67,32 @@ describe('organizeMembers', () => { github: 'https://github.com/AliceSmith', linkedin: 'https://www.linkedin.com/in/alice-smith/', website: 'https://alicesmith.com/', - team: mockTeams[1], - department: { - id: 20, - name: 'Game Developer', - translations: [ - { - id: 120, - departments_id: 20, - languages_code: 'en-US', - department: 'Game Developer', - }, - { - id: 121, - departments_id: 20, - languages_code: 'fi-FI', - department: 'Pelikehittäjä', - }, - ], - members: [], - } as Department, - translations: [], + roles: [ + { + id: 2, + team: mockTeams[1], + department: { + id: 20, + name: 'Game Developer', + translations: [ + { + id: 120, + departments_v2_id: 20, + languages_code: 'en-US', + name: 'Game Developer', + }, + { + id: 121, + departments_v2_id: 20, + languages_code: 'fi-FI', + name: 'Pelikehittäjä', + }, + ], + members: [], + } as Department, + translations: [], + } as MemberRole, + ], }, ]; From 80f39b19628da22069e56f920432130304cafed4 Mon Sep 17 00:00:00 2001 From: patinen Date: Mon, 2 Feb 2026 18:27:18 +0200 Subject: [PATCH 3/5] display all tasks from all roles for each member --- frontend-next-migration/package.json | 2 +- .../src/entities/Member/ui/MemberItem.tsx | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/frontend-next-migration/package.json b/frontend-next-migration/package.json index 19c3a27eb..2fb40b27b 100644 --- a/frontend-next-migration/package.json +++ b/frontend-next-migration/package.json @@ -26,7 +26,7 @@ }, "lint-staged": { "*.{js,ts,tsx,scss,css,md}": [ - "next lin:fix" + "next lint:fix" ] }, "dependencies": { diff --git a/frontend-next-migration/src/entities/Member/ui/MemberItem.tsx b/frontend-next-migration/src/entities/Member/ui/MemberItem.tsx index 47089e162..0ce7353c3 100644 --- a/frontend-next-migration/src/entities/Member/ui/MemberItem.tsx +++ b/frontend-next-migration/src/entities/Member/ui/MemberItem.tsx @@ -36,11 +36,19 @@ const MemberItem: FC = ({ member, language }) => { : null; const fullLanguageCode = getLanguageCode(language); - // Get task from first role's translations, or fall back to empty string - const task = - member.roles && member.roles.length > 0 && member.roles[0].translations - ? getRoleTaskTranslation(member.roles[0].translations, fullLanguageCode) - : ''; + // Get all tasks from all roles, avoiding duplicates + const allTasks = member.roles + ? member.roles + .map((role) => { + if (role.translations && role.translations.length > 0) { + return getRoleTaskTranslation(role.translations, fullLanguageCode); + } + return ''; + }) + .filter((task) => task && task.trim() !== '') + .filter((task, index, array) => array.indexOf(task) === index) // Remove duplicates + : []; + const task = allTasks.join(', '); return (
  • From 763f84f4cc1e33efe2ea8830dd780d3256c541d9 Mon Sep 17 00:00:00 2001 From: patinen Date: Thu, 2 Apr 2026 13:47:47 +0300 Subject: [PATCH 4/5] feat: add order field to teams and sort teams by it --- .../src/app/_styles/index.scss | 2 +- .../src/entities/Member/api/mappers.ts | 69 +++++-------------- .../src/entities/Member/model/types/types.ts | 1 + 3 files changed, 21 insertions(+), 51 deletions(-) diff --git a/frontend-next-migration/src/app/_styles/index.scss b/frontend-next-migration/src/app/_styles/index.scss index 50da966d4..d71a3ecd7 100644 --- a/frontend-next-migration/src/app/_styles/index.scss +++ b/frontend-next-migration/src/app/_styles/index.scss @@ -13,7 +13,7 @@ @media screen and (min-width: 960px) { html { - width: 100vw; + width: 100%; // todo may be there is a better solution, it is done to avoid navbar jumping overflow-x: hidden; } diff --git a/frontend-next-migration/src/entities/Member/api/mappers.ts b/frontend-next-migration/src/entities/Member/api/mappers.ts index 2db192a8c..693a1e862 100644 --- a/frontend-next-migration/src/entities/Member/api/mappers.ts +++ b/frontend-next-migration/src/entities/Member/api/mappers.ts @@ -19,30 +19,6 @@ export const getLinks = () => { }; }; -const enOrder = [ - 'Design Team', - 'Technical Team', - 'Artistic Team', - 'Art Education', - 'Production', - 'Community Management', - 'Community Content', - 'Participated in the Development of the Project', - 'Special Thanks', -]; - -const fiOrder = [ - 'Suunnittelutiimi', - 'Tekninen Tiimi', - 'Taiteellinen Tiimi', - 'Taidekasvatus', - 'Tuotanto', - 'Yhteisömanagerointi', - 'Yhteisösisältö', - 'Projektin Kehityksessä Mukana Olleet', - 'Erityiskiitokset', -]; - // Define department order for each language const enDepartmentOrder = [ 'Coreteam', @@ -94,6 +70,7 @@ const getOrCreateTeam = ( team = { id: memberTeam.id, name: teamName || '', + order: memberTeam.order ?? null, translations: memberTeam.translations || [], members: [], departments: [], @@ -185,8 +162,6 @@ const processRole = ( export const organizeMembers = (members: Member[], lng: string) => { const teamsMap = new Map(); const fullLanguageCode = getLanguageCode(lng); - - const order = lng === 'fi' ? fiOrder : enOrder; const departmentOrder = lng === 'fi' ? fiDepartmentOrder : enDepartmentOrder; members.forEach((member: Member) => { @@ -227,28 +202,16 @@ export const organizeMembers = (members: Member[], lng: string) => { (team) => team.name && team.name.trim() !== '', ); - // Sort teams according to predefined order - // Teams not in the order array go to the end, sorted by name + // Sort teams by Directus-provided order first (ascending). + // Teams without an order go to the end and are sorted by name. const sortedTeams = validTeams.sort((a, b) => { - const indexA = order.indexOf(a.name); - const indexB = order.indexOf(b.name); + const orderA = a.order ?? Number.POSITIVE_INFINITY; + const orderB = b.order ?? Number.POSITIVE_INFINITY; - // Both teams are in the order array - sort by position - if (indexA !== -1 && indexB !== -1) { - return indexA - indexB; + if (orderA !== orderB) { + return orderA - orderB; } - // Team A is in order, Team B is not - A comes first - if (indexA !== -1 && indexB === -1) { - return -1; - } - - // Team B is in order, Team A is not - B comes first - if (indexA === -1 && indexB !== -1) { - return 1; - } - - // Neither team is in order array - sort alphabetically return a.name.localeCompare(b.name); }); @@ -263,13 +226,19 @@ export const organizeMembers = (members: Member[], lng: string) => { * @returns {Record[]} The organized teams mapped by their IDs. */ -export const organizeTeams = (teamProps: Record[], lng: string) => { - const order = lng === 'fi' ? fiOrder : enOrder; - +export const organizeTeams = (teamProps: Record[], _lng: string) => { const sortedTeams = Array.from(teamProps).sort((a, b) => { - const indexA = order.indexOf(a.label); - const indexB = order.indexOf(b.label); - return indexA - indexB; + const orderA = + typeof (a as any).order === 'number' + ? ((a as any).order as number) + : Number.POSITIVE_INFINITY; + const orderB = + typeof (b as any).order === 'number' + ? ((b as any).order as number) + : Number.POSITIVE_INFINITY; + + if (orderA !== orderB) return orderA - orderB; + return String(a.label ?? '').localeCompare(String(b.label ?? '')); }); return sortedTeams; diff --git a/frontend-next-migration/src/entities/Member/model/types/types.ts b/frontend-next-migration/src/entities/Member/model/types/types.ts index 11bb8c68b..ee44febf8 100644 --- a/frontend-next-migration/src/entities/Member/model/types/types.ts +++ b/frontend-next-migration/src/entities/Member/model/types/types.ts @@ -39,6 +39,7 @@ export interface Department { export interface Team { name: string; id: number; + order?: number | null; translations: TeamTranslation[]; members: Member[]; departments: Department[]; From 85f7d055b1fbb6d1c73007f4450d78e391794c47 Mon Sep 17 00:00:00 2001 From: patinen <114212867+patinen@users.noreply.github.com> Date: Thu, 2 Apr 2026 13:52:08 +0300 Subject: [PATCH 5/5] Update width to 100vw in media query Not sure why I had this changed in the first place, must've been from some previous fiddling with the news' image size issues --- frontend-next-migration/src/app/_styles/index.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend-next-migration/src/app/_styles/index.scss b/frontend-next-migration/src/app/_styles/index.scss index d71a3ecd7..50da966d4 100644 --- a/frontend-next-migration/src/app/_styles/index.scss +++ b/frontend-next-migration/src/app/_styles/index.scss @@ -13,7 +13,7 @@ @media screen and (min-width: 960px) { html { - width: 100%; + width: 100vw; // todo may be there is a better solution, it is done to avoid navbar jumping overflow-x: hidden; }