groups functional & bugfixes

This commit is contained in:
2024-06-15 22:36:45 +03:00
parent 480d2fe141
commit e51140583b
12 changed files with 399 additions and 77 deletions

View File

@ -1,6 +1,6 @@
import React, { useContext, useState } from "react";
import "../styles.css";
import { Button, Input, Spin } from "antd";
import { Button, Input, Select, Spin } from "antd";
import {
ArrowLeftOutlined,
FileTextOutlined,
@ -30,9 +30,10 @@ const ApproveQueueJoinCard = (props: { id: string }): JSX.Element => {
const [joinQueue, { isLoading }] = useJoinQueueMutation();
const [patchAnon] = usePatchAnonMutation();
const [newName, setNewName] = useState("");
const [selectedGroup, setSelectedGroup] = useState<string>();
const onJoinButtonClick = () => {
joinQueue(props.id)
joinQueue({ queueId: props.id, data: { group_id: selectedGroup } })
.unwrap()
.then(() => navigate(`/queue/${props.id}`))
.then(() => refetch())
@ -63,20 +64,20 @@ const ApproveQueueJoinCard = (props: { id: string }): JSX.Element => {
</>
) : (
<div className="queue-info">
<Title level={3} style={{ textAlign: "left" }}>
{data?.name}
</Title>
<p>
<FileTextOutlined />
{" "}
{data?.description}
</p>
<p>
<UserOutlined />
{" "}
{data?.participants?.remaining} / {data?.participants?.total}
</p>
<Spin spinning={isLoading}>
<Title level={3} style={{ textAlign: "left" }}>
{data?.name}
</Title>
<p>
<FileTextOutlined />
{" "}
{data?.description}
</p>
<p>
<UserOutlined />
{" "}
{data?.participants?.remaining} / {data?.participants?.total}
</p>
<Title level={4}>{tr("Update your name")}</Title>
<div style={{ display: "flex", flexFlow: "row", width: "30vw" }}>
<Input
@ -88,6 +89,22 @@ const ApproveQueueJoinCard = (props: { id: string }): JSX.Element => {
{tr("Update")}
</Button>
</div>
{data?.groups.length !== undefined && data?.groups.length > 0 && (
<>
<Title level={4}>{tr("Select group in queue")}</Title>
<Select
options={data?.groups.map((obj) => ({
value: obj.id,
label: obj.name,
}))}
placeholder={tr("Select group...")}
value={selectedGroup}
style={{ width: "30vw", marginTop: "1rem" }}
onChange={setSelectedGroup}
/>
</>
)}
<Button
style={{ width: "100%", marginTop: "2rem" }}
type="primary"

View File

@ -1,16 +1,23 @@
import React, { useContext } from "react";
import React, { useContext, useReducer, useState } from "react";
import {
CreateQueue,
CreateQueueRequest,
Queue,
useCreateQueueMutation,
} from "../../slice/QueueApi";
import "../styles.css";
import { Button, Form, Input, Spin } from "antd";
import { Button, Form, Input, List, Spin, Tooltip, Typography } from "antd";
import Title from "antd/es/typography/Title";
import tr from "../../config/translation";
import { MessageContext } from "../../App";
import { useNavigate } from "react-router-dom";
import { PlusCircleOutlined } from "@ant-design/icons";
import {
DeleteOutlined,
DownOutlined,
PlusCircleOutlined,
QuestionCircleOutlined,
UpOutlined,
} from "@ant-design/icons";
const CreateQueueCard = (): JSX.Element => {
const messageApi = useContext(MessageContext);
@ -19,14 +26,95 @@ const CreateQueueCard = (): JSX.Element => {
const [form] = Form.useForm();
const [createQueue, { isLoading }] = useCreateQueueMutation();
const submit = (formData: CreateQueueRequest) => {
createQueue(formData)
const [newGroupValue, setNewGroupValue] = useState("");
const [groupsList, setGroupsList] = useState<[{ name: string; id: string }]>([
{ name: tr("Default"), id: "default" },
]);
const [, forceUpdate] = useReducer((x) => x + 1, 0);
const pushGroupUp = (element: { name: string; id: string }) => {
const index = groupsList.indexOf(element);
if (index > 0) {
const newArr = groupsList;
const temp = newArr[index - 1];
newArr[index - 1] = element;
newArr[index] = temp;
setGroupsList(newArr);
forceUpdate();
}
};
const pushGroupDown = (element: { name: string; id: string }) => {
const index = groupsList.indexOf(element);
if (index < groupsList.length - 1) {
const newArr = groupsList;
const temp = newArr[index + 1];
newArr[index + 1] = element;
newArr[index] = temp;
setGroupsList(newArr);
forceUpdate();
}
};
const deleteGroup = (element: { name: string; id: string }) => {
if (groupsList.includes(element)) {
const newArr = groupsList;
newArr.splice(groupsList.indexOf(element), 1);
setGroupsList(newArr);
forceUpdate();
}
};
type OrderedGroup = { name: string; priority: number };
const getOrderedGroups = () => {
const divId = groupsList.findIndex((ele) => ele.id === "default");
if (divId || divId === 0) {
const arr1: OrderedGroup[] = [];
groupsList
.slice(0, divId)
.reverse()
.forEach((ele, i) => arr1.push({ name: ele.name, priority: -(i + 1) }));
const arr2: OrderedGroup[] = [];
groupsList
.slice(divId + 1)
.forEach((ele, i) => arr2.push({ name: ele.name, priority: i + 1 }));
return [...arr1, ...arr2];
}
return [];
};
getOrderedGroups();
const submit = (formData: CreateQueue) => {
createQueue({ ...formData, groups: getOrderedGroups() })
.unwrap()
.then((data: Queue) => navigate(`/queue/${data.id}`))
.then(() => messageApi.success(tr("Queue created")))
.catch(() => messageApi.error(tr("Failed to create queue")));
};
const addGroupToList = () => {
if (newGroupValue) {
const tmp = groupsList;
if (tmp) {
tmp.push({
name: newGroupValue,
id: tmp.length.toString(),
});
setGroupsList(tmp);
} else {
setGroupsList((v) => [
{
name: newGroupValue,
id: v.length.toString(),
},
]);
}
setNewGroupValue("");
}
};
return (
<div className="card">
<Spin spinning={isLoading}>
@ -55,6 +143,77 @@ const CreateQueueCard = (): JSX.Element => {
>
<Input />
</Form.Item>
<Form.Item>
<List
dataSource={groupsList}
header={
<div style={{ display: "flex", gap: "1rem" }}>
<Title level={4}>
{tr("Queue groups") + " " + tr("(optional)")}
</Title>
<Tooltip
title={tr(
"Queue's groups function allows you to define groups inside this particular queue. Groups have name and priority. Every participant selects theirs group for this queue, and their position is calculated by group's priority. Groups higher in the list will have higher priority."
)}
>
<QuestionCircleOutlined />
</Tooltip>
</div>
}
footer={
<div style={{ display: "flex", gap: "1rem" }}>
<Input
placeholder={tr("Group's name")}
value={newGroupValue}
onChange={(e) => setNewGroupValue(e.target.value)}
onPressEnter={addGroupToList}
/>
<Button onClick={addGroupToList}>{tr("Add group")}</Button>
</div>
}
renderItem={(item, index) => (
<List.Item
style={{
display: "flex",
alignContent: "space-between",
gap: "1rem",
}}
>
<div
style={{
display: "flex",
alignContent: "space-between",
gap: "1rem",
}}
>
<Typography.Text>#{index + 1}</Typography.Text>
<Typography.Text>{item.name}</Typography.Text>
</div>
{item.id !== "default" && (
<div style={{ display: "flex" }}>
<Button
onClick={() => pushGroupUp(item)}
icon={<UpOutlined />}
disabled={groupsList.indexOf(item) === 0}
/>
<Button
icon={<DownOutlined />}
onClick={() => pushGroupDown(item)}
disabled={
groupsList.indexOf(item) === groupsList.length - 1
}
/>
<Button
icon={<DeleteOutlined />}
onClick={() => deleteGroup(item)}
/>
</div>
)}
</List.Item>
)}
></List>
</Form.Item>
<Button
style={{ width: "100%" }}
icon={<PlusCircleOutlined />}

View File

@ -182,7 +182,14 @@ const QueueCard = (): JSX.Element => {
{tr("Queue participants")}
</Title>
{data?.participants.users_list.map((v) => {
return <AnonUserCard key={v.id} queueUser={v} queue={data} />;
return (
<AnonUserCard
key={v.id}
queueUser={v}
queue={data}
refetch={refetch}
/>
);
})}
</div>
</Spin>

View File

@ -122,4 +122,5 @@
border-radius: 10px;
padding: 1rem;
border: 2px solid #00d8a4;
justify-content: space-between;
}

View File

@ -25,6 +25,7 @@ const getProfileText = (name: string): string => {
const AnonUserCard = (props: {
queueUser: QueueUser;
queue: QueueDetail | undefined;
refetch: () => void;
}): JSX.Element => {
const messageApi = useContext(MessageContext);
@ -35,47 +36,49 @@ const AnonUserCard = (props: {
if (props.queue) {
passAction(props.queue.id)
.unwrap()
.then(() => messageApi.success(tr("You passed")))
.catch(() => messageApi.error(tr("Failed to pass")));
.then(props.refetch)
.then(() => messageApi.success(tr("You left the queue")))
.catch(() => messageApi.error(tr("Failed to left")));
}
};
return (
<div className="anon-card">
<span style={{ marginRight: "1rem" }}>#{props.queueUser.position}</span>
<div
className="anon-circle"
style={{ background: UUIDToColor(props.queueUser.user.id) }}
style={{
display: "flex",
gap: "0.5rem",
alignItems: "center",
flexFlow: "row wrap",
}}
>
{props.queueUser.user.name
? getProfileText(props.queueUser.user.name)
: props.queueUser.id.substring(0, 2)}
</div>
<p color="white" style={{ marginLeft: "10px" }}>
{props.queueUser.user.name
? props.queueUser.user.name
: tr("Anonymous") + " #" + props.queueUser.id.substring(0, 4)}
</p>
{props.queueUser && clientId === props.queueUser.user.id && (
<span
style={{
background: "#00d8a4",
marginLeft: "1rem",
marginRight: "1rem",
marginBottom: "0",
borderRadius: "5px",
padding: "2px",
}}
<span style={{ marginRight: "1rem" }}>#{props.queueUser.position}</span>
<div
className="anon-circle"
style={{ background: UUIDToColor(props.queueUser.user.id) }}
>
{tr("YOU")}
</span>
)}
{props.queueUser &&
clientId === props.queueUser.user.id &&
props.queueUser.position === 0 &&
props.queue?.status === "active" && (
{props.queueUser.user.name
? getProfileText(props.queueUser.user.name)
: props.queueUser.id.substring(0, 2)}
</div>
<p color="white" style={{ marginLeft: "10px" }}>
{props.queueUser.user.name
? props.queueUser.user.name
: tr("Anonymous") + " #" + props.queueUser.id.substring(0, 4)}
</p>
{props.queueUser.group_id && (
<p color="white" style={{ marginLeft: "10px" }}>
{tr("Group") +
": " +
props.queue?.groups.find(
(ele) => ele.id === props.queueUser.group_id
)?.name}
</p>
)}
{props.queueUser && clientId === props.queueUser.user.id && (
<span
style={{
background: "#00d8a4",
marginLeft: "1rem",
marginRight: "1rem",
marginBottom: "0",
@ -83,16 +86,43 @@ const AnonUserCard = (props: {
padding: "2px",
}}
>
{tr("It is your turn!")}
{tr("YOU")}
</span>
)}
{props.queueUser &&
clientId === props.queueUser.user.id &&
(props.queueUser.position === 0 && props.queue?.status === "active" ? (
<Button onClick={() => passQueue()}>{tr("Pass")}</Button>
) : (
<Button onClick={() => passQueue()}>{tr("Leave")}</Button>
))}
{props.queueUser &&
clientId === props.queueUser.user.id &&
props.queueUser.position === 0 &&
props.queue?.status === "active" && (
<span
style={{
marginLeft: "1rem",
marginRight: "1rem",
marginBottom: "0",
borderRadius: "5px",
padding: "2px",
}}
>
{tr("It is your turn!")}
</span>
)}
</div>
<div
style={{
display: "flex",
gap: "0.5rem",
alignItems: "center",
flexFlow: "row wrap",
}}
>
{props.queueUser &&
clientId === props.queueUser.user.id &&
(props.queueUser.position === 0 &&
props.queue?.status === "active" ? (
<Button onClick={() => passQueue()}>{tr("Pass")}</Button>
) : (
<Button onClick={() => passQueue()}>{tr("Leave")}</Button>
))}
</div>
</div>
);
};

View File

@ -17,6 +17,9 @@ export const darkTheme: ThemeConfig = {
activeBorderColor: "#001529",
colorTextPlaceholder: "grey",
},
Select: {
colorTextPlaceholder: "grey",
},
},
};

View File

@ -30,6 +30,7 @@ export type QueueUser = {
position: number;
passed: boolean;
user: AnonUser;
group_id: string | undefined;
};
export type AnonUser = {

View File

@ -3,9 +3,26 @@ import { baseUrl } from "../config/baseUrl";
import { RootState } from "../config/store";
import { QueueUser } from "./AuthApi";
type OrderedGroup = {
name: string;
priority: number;
};
type ResponseGroup = {
id: string;
name: string;
priority: number;
};
export type CreateQueueRequest = {
name: string;
description: string | null;
groups: OrderedGroup[];
};
export type CreateQueue = {
name: string;
description: string | null;
};
export type Queue = {
@ -20,6 +37,7 @@ export type QueueDetail = {
description: string | null;
status: string;
owner_id: string;
groups: ResponseGroup[];
participants: {
total: number;
remaining: number;
@ -27,6 +45,10 @@ export type QueueDetail = {
};
};
export type JoinRequest = {
group_id: string | undefined;
};
export const QueueApi = createApi({
reducerPath: "QueueApi",
baseQuery: fetchBaseQuery({
@ -54,7 +76,11 @@ export const QueueApi = createApi({
query: (queueId: string | undefined) => `/${queueId}`,
}),
joinQueue: builder.mutation({
query: (queueId: string) => ({ url: `/${queueId}/join`, method: "POST" }),
query: (args: { queueId: string; data: JoinRequest }) => ({
url: `/${args.queueId}/join`,
method: "POST",
body: args.data,
}),
}),
createQueue: builder.mutation({
query: (data: CreateQueueRequest) => ({