groups functional & bugfixes
This commit is contained in:
@ -76,6 +76,7 @@ class Queue(Base):
|
||||
|
||||
users = relationship("QueueUser", backref="queue", lazy="dynamic")
|
||||
logs = relationship("QueueLog", backref="queue", lazy="dynamic")
|
||||
groups = relationship("QueueGroup", backref="queue", lazy="dynamic")
|
||||
|
||||
|
||||
class QueueUser(Base):
|
||||
@ -86,6 +87,7 @@ class QueueUser(Base):
|
||||
queue_id = Column(UUID(as_uuid=True), ForeignKey("queues.id"))
|
||||
position = Column(Integer)
|
||||
passed = Column(Boolean, default=False)
|
||||
group_id = Column(UUID(as_uuid=True), ForeignKey("queuegroup.id"), nullable=True)
|
||||
|
||||
|
||||
class QueueLog(Base):
|
||||
@ -97,6 +99,17 @@ class QueueLog(Base):
|
||||
created = Column(DateTime, default=datetime.datetime.utcnow)
|
||||
|
||||
|
||||
class QueueGroup(Base):
|
||||
__tablename__ = "queuegroup"
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
name = Column(String)
|
||||
priority = Column(Integer)
|
||||
queue_id = Column(UUID(as_uuid=True), ForeignKey("queues.id"))
|
||||
|
||||
users = relationship("QueueUser", backref="group", lazy="dynamic")
|
||||
|
||||
|
||||
class Captcha(Base):
|
||||
__tablename__ = "captcha"
|
||||
|
||||
|
||||
@ -96,17 +96,21 @@ def get_current_user(
|
||||
|
||||
|
||||
def get_current_user_or_none(
|
||||
token: Annotated[str, Depends(oauth2_scheme)],
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
authorization: Annotated[Union[str, None], Header()] = None,
|
||||
) -> Union[schemas.UserInDB, None]:
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
token, jwt_config.SECRET_KEY, algorithms=[jwt_config.ALGORITHM]
|
||||
)
|
||||
username: str = payload.get("sub")
|
||||
if username is None:
|
||||
raise credentials_exception
|
||||
token_data = schemas.TokenData(username=username)
|
||||
if authorization:
|
||||
token = authorization.split()[1]
|
||||
payload = jwt.decode(
|
||||
token, jwt_config.SECRET_KEY, algorithms=[jwt_config.ALGORITHM]
|
||||
)
|
||||
username: str = payload.get("sub")
|
||||
if username is None:
|
||||
raise credentials_exception
|
||||
token_data = schemas.TokenData(username=username)
|
||||
else:
|
||||
return None
|
||||
except JWTError:
|
||||
return None
|
||||
user = get_user_by_username(db, username=token_data.username)
|
||||
|
||||
@ -4,10 +4,23 @@ from uuid import UUID
|
||||
from ..auth import schemas as auth_schemas
|
||||
|
||||
|
||||
class QueueGroup(BaseModel):
|
||||
name: str
|
||||
priority: int
|
||||
|
||||
|
||||
class QueueGroupDetail(QueueGroup):
|
||||
id: UUID
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class QueueUser(BaseModel):
|
||||
id: UUID
|
||||
position: int
|
||||
passed: bool
|
||||
group_id: UUID | None = None
|
||||
user: auth_schemas.AnonUser
|
||||
|
||||
class Config:
|
||||
@ -23,6 +36,7 @@ class ParticipantInfo(BaseModel):
|
||||
class Queue(BaseModel):
|
||||
name: str
|
||||
description: Union[str, None] = None
|
||||
groups: List[QueueGroup] | None = None
|
||||
|
||||
|
||||
class QueueInList(Queue):
|
||||
@ -32,8 +46,10 @@ class QueueInList(Queue):
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class QueueInDb(Queue):
|
||||
class QueueInDb(BaseModel):
|
||||
id: UUID
|
||||
name: str
|
||||
description: Union[str, None] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@ -44,6 +60,7 @@ class QueueDetail(Queue):
|
||||
status: str
|
||||
owner_id: UUID
|
||||
participants: ParticipantInfo
|
||||
groups: List[QueueGroupDetail] | None
|
||||
|
||||
|
||||
class ActionResult(BaseModel):
|
||||
@ -52,3 +69,7 @@ class ActionResult(BaseModel):
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class JoinRequest(BaseModel):
|
||||
group_id: UUID | None = None
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from typing import Annotated
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.orm import Session, joinedload
|
||||
from sqlalchemy import func
|
||||
from uuid import UUID
|
||||
import redis
|
||||
import asyncio
|
||||
@ -49,6 +50,14 @@ def create_queue(
|
||||
)
|
||||
db.add(q)
|
||||
db.commit()
|
||||
if new_queue.groups:
|
||||
db.add_all(
|
||||
instances=[
|
||||
models.QueueGroup(name=qg.name, priority=qg.priority, queue_id=q.id)
|
||||
for qg in new_queue.groups
|
||||
]
|
||||
)
|
||||
db.commit()
|
||||
return schemas.QueueInDb.model_validate(q)
|
||||
|
||||
|
||||
@ -64,6 +73,7 @@ def get_detailed_queue(
|
||||
description=q.description,
|
||||
status=q.status,
|
||||
owner_id=q.owner_id,
|
||||
groups=q.groups.order_by(models.QueueGroup.priority.asc()),
|
||||
participants=schemas.ParticipantInfo(
|
||||
total=q.users.count(),
|
||||
remaining=q.users.filter(models.QueueUser.passed == False).count(),
|
||||
@ -80,6 +90,7 @@ def get_detailed_queue(
|
||||
|
||||
async def join_queue(
|
||||
queue_id: UUID,
|
||||
join_request: schemas.JoinRequest | None,
|
||||
client: Annotated[auth_schemas.AnonUser, Depends(auth_services.get_anon_user)],
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
r: Annotated[redis.client.Redis, Depends(get_redis)],
|
||||
@ -90,10 +101,18 @@ async def join_queue(
|
||||
last_qu = q.users.order_by(models.QueueUser.position.desc()).first()
|
||||
position = last_qu.position + 1 if last_qu else 0
|
||||
new_qu = models.QueueUser(
|
||||
user_id=client.id, queue_id=q.id, position=position
|
||||
user_id=client.id,
|
||||
queue_id=q.id,
|
||||
position=position,
|
||||
group_id=(
|
||||
join_request.group_id
|
||||
if join_request and join_request.group_id
|
||||
else None
|
||||
),
|
||||
)
|
||||
db.add(new_qu)
|
||||
db.commit()
|
||||
await rebuild_queue(queue=q, db=db)
|
||||
await r.publish(str(queue_id), "updated")
|
||||
return new_qu
|
||||
raise HTTPException(
|
||||
@ -129,7 +148,7 @@ async def get_queue_owner(
|
||||
async def verify_queue_owner(
|
||||
queue_owner: Annotated[auth_schemas.UserInDB, Depends(get_queue_owner)],
|
||||
current_user: Annotated[
|
||||
auth_schemas.UserInDB, Depends(auth_services.get_current_user)
|
||||
auth_schemas.UserInDB, Depends(auth_services.get_current_user_or_none)
|
||||
],
|
||||
) -> bool:
|
||||
return queue_owner.id == current_user.id if queue_owner and current_user else False
|
||||
@ -139,11 +158,32 @@ async def rebuild_queue(
|
||||
queue: Annotated[models.Queue, Depends(get_queue_by_id)],
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
):
|
||||
for i, qu in enumerate(
|
||||
queue.users.filter(models.QueueUser.passed == False).order_by(
|
||||
models.QueueUser.position.asc()
|
||||
query = (
|
||||
db.query(models.QueueUser)
|
||||
.join(
|
||||
models.QueueGroup,
|
||||
models.QueueUser.group_id == models.QueueGroup.id,
|
||||
isouter=True,
|
||||
)
|
||||
):
|
||||
.filter(models.QueueUser.passed == False, models.QueueUser.queue_id == queue.id)
|
||||
.order_by(
|
||||
func.coalesce(models.QueueGroup.priority, 0).asc(),
|
||||
models.QueueUser.position.asc(),
|
||||
)
|
||||
.options(joinedload(models.QueueUser.group))
|
||||
)
|
||||
queueusers = query.all()
|
||||
first_qu_found_and_queue_in_process = False
|
||||
if queue.status == "active":
|
||||
for i, qu in enumerate(queueusers):
|
||||
if qu.position == 0:
|
||||
first_qu_found_and_queue_in_process = True
|
||||
del queueusers[i]
|
||||
break
|
||||
for i, qu in enumerate(queueusers):
|
||||
if first_qu_found_and_queue_in_process:
|
||||
setattr(qu, "position", i + 1)
|
||||
continue
|
||||
setattr(qu, "position", i)
|
||||
db.commit()
|
||||
|
||||
|
||||
@ -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