Sort truster selects by team (#1893)

* Initial

* Main team first

* Sorting, deduplicating

* To SQ

* No "Others" if also no teams
This commit is contained in:
Kalle
2024-09-29 11:20:34 +03:00
committed by GitHub
parent d8e163fcd6
commit 17c8596c7d
13 changed files with 101 additions and 30 deletions

View File

@@ -278,9 +278,10 @@ export function deletePrivateUserNote({
}
export async function usersThatTrusted(userId: number) {
const teamIds = await db
const teams = await db
.selectFrom("TeamMemberWithSecondary")
.select("teamId")
.innerJoin("Team", "Team.id", "TeamMemberWithSecondary.teamId")
.select(["Team.id", "Team.name", "TeamMemberWithSecondary.isMainTeam"])
.where("userId", "=", userId)
.execute();
@@ -288,24 +289,47 @@ export async function usersThatTrusted(userId: number) {
.selectFrom("TeamMemberWithSecondary")
.innerJoin("User", "User.id", "TeamMemberWithSecondary.userId")
.innerJoin("UserFriendCode", "UserFriendCode.userId", "User.id")
.select([...COMMON_USER_FIELDS, "User.inGameName"])
.select([
...COMMON_USER_FIELDS,
"User.inGameName",
"TeamMemberWithSecondary.teamId",
])
.where(
"TeamMemberWithSecondary.teamId",
"in",
teamIds.map((t) => t.teamId),
teams.map((t) => t.id),
)
.union((eb) =>
eb
.selectFrom("TrustRelationship")
.innerJoin("User", "User.id", "TrustRelationship.trustGiverUserId")
.innerJoin("UserFriendCode", "UserFriendCode.userId", "User.id")
.select([...COMMON_USER_FIELDS, "User.inGameName"])
.select([
...COMMON_USER_FIELDS,
"User.inGameName",
sql.raw<any>("null").as("teamId"),
])
.where("TrustRelationship.trustReceiverUserId", "=", userId),
)
.orderBy("User.username asc")
.execute();
const rowsWithoutBanned = rows.filter((row) => !userIsBanned(row.id));
return rowsWithoutBanned;
const teamMemberIds = rowsWithoutBanned
.filter((row) => row.teamId)
.map((row) => row.id);
// we want user to show twice if member of two different teams
// but we don't want a user from the team to show in teamless section
const deduplicatedRows = rowsWithoutBanned.filter(
(row) => row.teamId || !teamMemberIds.includes(row.id),
);
// done here at not sql just because it was easier to do here ignoring case
deduplicatedRows.sort((a, b) => a.username.localeCompare(b.username));
return {
teams: teams.sort((a, b) => b.isMainTeam - a.isMainTeam),
trusters: deduplicatedRows,
};
}

View File

@@ -98,7 +98,7 @@ function TrusterDropdown({
groupMemberIds: number[];
}) {
const { t } = useTranslation(["q"]);
const { trusters } = useTrusted();
const { trusters, teams } = useTrusted();
if (!trusters || trusters.length === 0) {
return (
@@ -115,6 +115,16 @@ function TrusterDropdown({
(truster) => !groupMemberIds.includes(truster.id),
);
const othersOptions = trustersNotInGroup
.filter((player) => !player.teamId)
.map((player) => {
return (
<option key={player.id} value={player.id}>
{player.username}
</option>
);
});
return (
<select
name="id"
@@ -124,14 +134,28 @@ function TrusterDropdown({
}
className="q__member-adder__input"
>
<option value="">{t("q:looking.groups.adder.selectUser")}</option>
{trustersNotInGroup.map((player) => {
{teams?.map((team) => {
return (
<option key={player.id} value={player.id}>
{player.username}
</option>
<optgroup label={team.name} key={team.id}>
{trustersNotInGroup
.filter((player) => player.teamId === team.id)
.map((player) => {
return (
<option key={player.id} value={player.id}>
{player.username}
</option>
);
})}
</optgroup>
);
})}
{teams && teams.length > 0 ? (
<optgroup label={t("q:looking.groups.adder.others")}>
{othersOptions}
</optgroup>
) : (
othersOptions
)}
</select>
);
}

View File

@@ -88,7 +88,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
}
validate(
(await QRepository.usersThatTrusted(user.id)).some(
(await QRepository.usersThatTrusted(user.id)).trusters.some(
(trusterUser) => trusterUser.id === data.id,
),
"Not trusted",

View File

@@ -200,7 +200,7 @@ export const action: ActionFunction = async ({ request, params }) => {
);
validate(ownTeam);
validate(
(await QRepository.usersThatTrusted(user.id)).some(
(await QRepository.usersThatTrusted(user.id)).trusters.some(
(trusterPlayer) => trusterPlayer.id === data.userId,
),
"No trust given from this user",

View File

@@ -903,7 +903,7 @@ function FillRoster({
(ownTeamCheckedIn && ownTeamMembers.length > tournament.minMembersPerTeam);
const playersAvailableToDirectlyAdd = (() => {
return (data!.trusterPlayers ?? []).filter((user) => {
return (data!.trusterPlayers?.trusters ?? []).filter((user) => {
const isNotInTeam = tournament.ctx.teams.every((team) =>
team.members.every((member) => member.userId !== user.id),
);
@@ -926,7 +926,10 @@ function FillRoster({
<section className="tournament__section stack lg items-center">
{playersAvailableToDirectlyAdd.length > 0 && canAddMembers ? (
<>
<DirectlyAddPlayerSelect players={playersAvailableToDirectlyAdd} />
<DirectlyAddPlayerSelect
players={playersAvailableToDirectlyAdd}
teams={data!.trusterPlayers?.teams ?? []}
/>
<Divider className="text-uppercase">{t("common:or")}</Divider>
</>
) : null}
@@ -1015,13 +1018,25 @@ function FillRoster({
function DirectlyAddPlayerSelect({
players,
teams,
}: {
players: { id: number; username: string }[];
players: { id: number; username: string; teamId?: number }[];
teams: { id: number; name: string }[];
}) {
const { t } = useTranslation(["tournament", "common"]);
const fetcher = useFetcher();
const id = React.useId();
const othersOptions = players
.filter((player) => !player.teamId)
.map((player) => {
return (
<option key={player.id} value={player.id}>
{player.username}
</option>
);
});
return (
<fetcher.Form method="post" className="stack horizontal sm items-end">
<div>
@@ -1029,13 +1044,26 @@ function DirectlyAddPlayerSelect({
{t("tournament:pre.roster.addTrusted.header")}
</Label>
<select id={id} name="userId">
{players.map((player) => {
{teams.map((team) => {
return (
<option key={player.id} value={player.id}>
{player.username}
</option>
<optgroup label={team.name} key={team.id}>
{players
.filter((player) => player.teamId === team.id)
.map((player) => {
return (
<option key={player.id} value={player.id}>
{player.username}
</option>
);
})}
</optgroup>
);
})}
{teams && teams.length > 0 ? (
<optgroup label="Others">{othersOptions}</optgroup>
) : (
othersOptions
)}
</select>
</div>
<SubmitButton

View File

@@ -60,7 +60,8 @@ export function useTrusted() {
);
return {
trusters: data?.trusters,
trusters: data?.trusters.trusters,
teams: data?.trusters.teams,
isLoading: !error && !data,
isError: error,
};

View File

@@ -108,7 +108,7 @@
"looking.groups.actions.goBack": "Go back",
"looking.groups.adder.quickAdd": "Quick add",
"looking.groups.adder.error": "That user is already in a group",
"looking.groups.adder.selectUser": "Select user",
"looking.groups.adder.others": "Others",
"looking.groups.adder.inviteLink": "Invite link",
"looking.groups.editNote": "Edit note",
"looking.groups.addNote": "Add note",

View File

@@ -109,7 +109,6 @@
"looking.groups.actions.goBack": "Regresar",
"looking.groups.adder.quickAdd": "Añadir rapido",
"looking.groups.adder.error": "Ese usuario ya está en un grupo",
"looking.groups.adder.selectUser": "Elegir usuario",
"looking.groups.adder.inviteLink": "Enlace de invitación",
"looking.groups.editNote": "Editar nota",
"looking.groups.addNote": "Añadir nota",

View File

@@ -109,7 +109,6 @@
"looking.groups.actions.goBack": "Regresar",
"looking.groups.adder.quickAdd": "Añadir rápido",
"looking.groups.adder.error": "Ese usuario ya está en un grupo",
"looking.groups.adder.selectUser": "Elegir usuario",
"looking.groups.adder.inviteLink": "Enlace de invitación",
"looking.groups.editNote": "Editar nota",
"looking.groups.addNote": "Añadir nota",

View File

@@ -108,7 +108,6 @@
"looking.groups.actions.goBack": "戻る",
"looking.groups.adder.quickAdd": "クイック追加",
"looking.groups.adder.error": "そのユーザーはグループに入っています",
"looking.groups.adder.selectUser": "ユーザーを選択",
"looking.groups.adder.inviteLink": "インバイトリンク",
"looking.groups.editNote": "メモを編集する",
"looking.groups.addNote": "メモを追加する",

View File

@@ -107,7 +107,6 @@
"looking.groups.actions.goBack": "Voltar",
"looking.groups.adder.quickAdd": "Adicionar rapidamente (quick add)",
"looking.groups.adder.error": "Esse usuário já está em um grupo",
"looking.groups.adder.selectUser": "Selecionar usuário",
"looking.groups.adder.inviteLink": "Link de convite",
"looking.groups.editNote": "Editar nota",
"looking.groups.addNote": "Adicionar nota",

View File

@@ -94,7 +94,6 @@
"looking.groups.actions.leaveQ": "Покинуть очередь",
"looking.groups.actions.goBack": "Вернуться назад",
"looking.groups.adder.quickAdd": "Быстро добавить",
"looking.groups.adder.selectUser": "Выбрать пользователя",
"looking.groups.adder.inviteLink": "Ссылка на приглашение",
"looking.groups.editNote": "Изменить заметку",
"looking.groups.addNote": "Добавить заметку",

View File

@@ -108,7 +108,6 @@
"looking.groups.actions.goBack": "返回",
"looking.groups.adder.quickAdd": "快速添加",
"looking.groups.adder.error": "该用户已加入其他小队",
"looking.groups.adder.selectUser": "选择用户",
"looking.groups.adder.inviteLink": "邀请链接",
"looking.groups.editNote": "编辑笔记",
"looking.groups.addNote": "添加笔记",