groups functional & bugfixes
This commit is contained in:
@ -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"
|
||||
|
||||
@ -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 />}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -122,4 +122,5 @@
|
||||
border-radius: 10px;
|
||||
padding: 1rem;
|
||||
border: 2px solid #00d8a4;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -17,6 +17,9 @@ export const darkTheme: ThemeConfig = {
|
||||
activeBorderColor: "#001529",
|
||||
colorTextPlaceholder: "grey",
|
||||
},
|
||||
Select: {
|
||||
colorTextPlaceholder: "grey",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -30,6 +30,7 @@ export type QueueUser = {
|
||||
position: number;
|
||||
passed: boolean;
|
||||
user: AnonUser;
|
||||
group_id: string | undefined;
|
||||
};
|
||||
|
||||
export type AnonUser = {
|
||||
|
||||
@ -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) => ({
|
||||
|
||||
Reference in New Issue
Block a user