From 306808cbc710221f9c62b50e2e3e4fff81187133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Szczeci=C5=84ski?= Date: Wed, 13 Nov 2024 14:33:54 +0100 Subject: [PATCH 01/21] Change Rooms sort select to a custom list box --- .../components/forms/widgets/BasicListbox.tsx | 4 ++-- app/client/components/rooms/RoomsBar.tsx | 21 +++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/app/client/components/forms/widgets/BasicListbox.tsx b/app/client/components/forms/widgets/BasicListbox.tsx index f8a3fd2d..762ce86d 100644 --- a/app/client/components/forms/widgets/BasicListbox.tsx +++ b/app/client/components/forms/widgets/BasicListbox.tsx @@ -70,10 +70,10 @@ export const BasicListbox = ({ {currentOption.label} - + ); })} diff --git a/app/client/components/rooms/RoomsBar.tsx b/app/client/components/rooms/RoomsBar.tsx index 95ce3850..94409eee 100644 --- a/app/client/components/rooms/RoomsBar.tsx +++ b/app/client/components/rooms/RoomsBar.tsx @@ -1,6 +1,7 @@ -import { Checkbox, Field, Input, Label, Select } from "@headlessui/react"; +import { Checkbox, Field, Input, Label } from "@headlessui/react"; import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"; import React from "react"; +import { BasicListbox } from "../forms/widgets/BasicListbox"; interface RoomsBarProps { searchText: string; @@ -46,15 +47,17 @@ export const RoomsBar = ({ /> - - + optgroups={[ + { value: "roomNumber", label: "Room number" }, + { value: "fullness", label: "Fullness" }, + ]} + onChange={(newValue) => onSortRoomsByChange(newValue as RoomsSortBy)} + multiple={false} + /> ); From e93afde084d193bde6ddae5a62887f9938379f5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Szczeci=C5=84ski?= Date: Wed, 13 Nov 2024 15:07:32 +0100 Subject: [PATCH 02/21] Add initial admin rooms view --- app/client/components/admin/AdminNavBar.tsx | 5 ++- app/client/components/rooms/Rooms.tsx | 34 +++++++++++++++ app/client/templates/AdminRoomsList.tsx | 18 ++++++++ app/client/templates/Rooms.tsx | 24 ++--------- app/client/utils/roomData.ts | 5 ++- app/server/rooms/templates.py | 47 ++++++++++++--------- app/server/rooms/urls.py | 1 + app/server/rooms/views.py | 11 ++++- 8 files changed, 101 insertions(+), 44 deletions(-) create mode 100644 app/client/components/rooms/Rooms.tsx create mode 100644 app/client/templates/AdminRoomsList.tsx diff --git a/app/client/components/admin/AdminNavBar.tsx b/app/client/components/admin/AdminNavBar.tsx index 66a5d516..352114e2 100644 --- a/app/client/components/admin/AdminNavBar.tsx +++ b/app/client/components/admin/AdminNavBar.tsx @@ -64,7 +64,10 @@ const navbarSections: AdminNavBarSection[] = [ }, { sectionTitle: "Rooms", - links: [{ linkTitle: "Upload rooms", href: reverse("rooms_import") }], + links: [ + { linkTitle: "List rooms", href: reverse("admin_rooms_list") }, + { linkTitle: "Upload rooms", href: reverse("rooms_import") }, + ], }, { sectionTitle: "Boardgames", diff --git a/app/client/components/rooms/Rooms.tsx b/app/client/components/rooms/Rooms.tsx new file mode 100644 index 00000000..0ca95b18 --- /dev/null +++ b/app/client/components/rooms/Rooms.tsx @@ -0,0 +1,34 @@ +import { RoomData } from "@client/utils/roomData"; +import React, { useState } from "react"; +import { RoomCards } from "./RoomCards"; +import { RoomsBar, RoomsSortBy } from "./RoomsBar"; + +interface RoomsProps { + initialRoomsData: RoomData[]; + isAdmin?: boolean; +} + +export const Rooms = ({ initialRoomsData, isAdmin }: RoomsProps) => { + const [searchText, setSearchText] = useState(""); + const [hideFullRooms, setHideFullRooms] = useState(false); + const [sortRoomsBy, setSortRoomsBy] = useState("roomNumber"); + + return ( + <> + + + + ); +}; diff --git a/app/client/templates/AdminRoomsList.tsx b/app/client/templates/AdminRoomsList.tsx new file mode 100644 index 00000000..8bc6bf48 --- /dev/null +++ b/app/client/templates/AdminRoomsList.tsx @@ -0,0 +1,18 @@ +import { AdminCenteredContainer } from "@client/components/admin/layout/AdminCenteredContainer"; +import { AdminLayout } from "@client/components/admin/layout/AdminLayout"; +import { PageTitle } from "@client/components/PageTitle"; +import { Rooms } from "@client/components/rooms/Rooms"; +import { createRoomDataFromTemplateProps } from "@client/utils/roomData"; +import { templates } from "@reactivated"; +import React from "react"; + +export const Template = (props: templates.AdminRoomsList) => { + return ( + + Rooms + + + + + ); +}; diff --git a/app/client/templates/Rooms.tsx b/app/client/templates/Rooms.tsx index e30ad5d6..6fa20e2f 100644 --- a/app/client/templates/Rooms.tsx +++ b/app/client/templates/Rooms.tsx @@ -1,35 +1,17 @@ import { CenteredContainer } from "@client/components/containers/CenteredContainer"; import { Layout } from "@client/components/Layout"; import { PageTitle } from "@client/components/PageTitle"; -import { RoomCards } from "@client/components/rooms/RoomCards"; -import { RoomsBar, RoomsSortBy } from "@client/components/rooms/RoomsBar"; +import { Rooms } from "@client/components/rooms/Rooms"; import { createRoomDataFromTemplateProps } from "@client/utils/roomData"; import { templates } from "@reactivated"; -import React, { useState } from "react"; +import React from "react"; export const Template = (props: templates.Rooms) => { - const [searchText, setSearchText] = useState(""); - const [hideFullRooms, setHideFullRooms] = useState(false); - const [sortRoomsBy, setSortRoomsBy] = useState("roomNumber"); - return ( Rooms - - + ); diff --git a/app/client/utils/roomData.ts b/app/client/utils/roomData.ts index 16382693..943aaef1 100644 --- a/app/client/utils/roomData.ts +++ b/app/client/utils/roomData.ts @@ -74,7 +74,9 @@ export const convertRoomAPIDataToRoomData = (room: RoomAPIData): RoomData => { }; }; -export const createRoomDataFromTemplateProps = (props: templates.Rooms) => { +export const createRoomDataFromTemplateProps = ( + props: templates.Rooms | templates.AdminRoomsList, +) => { return props.rooms.map((room) => ({ id: room.id, name: room.name, @@ -92,6 +94,7 @@ export const createRoomDataFromTemplateProps = (props: templates.Rooms) => { lastName: room.lock.user.last_name, }, password: + "user_room_lock" in props && props.user_room_lock?.id === room.lock.id ? props.user_room_lock.password : undefined, diff --git a/app/server/rooms/templates.py b/app/server/rooms/templates.py index e70d923a..be71b4d7 100644 --- a/app/server/rooms/templates.py +++ b/app/server/rooms/templates.py @@ -3,27 +3,34 @@ from .models import Room, RoomLock +ROOM_DATA = List[ + Pick[ + Room, + Literal[ + "id", + "name", + "description", + "available_beds_single", + "available_beds_double", + "members.id", + "members.first_name", + "members.last_name", + "lock.id", + "lock.user.id", + "lock.user.first_name", + "lock.user.last_name", + "lock.expiration_date", + ], + ] +] + @template class Rooms(NamedTuple): - rooms: List[ - Pick[ - Room, - Literal[ - "id", - "name", - "description", - "available_beds_single", - "available_beds_double", - "members.id", - "members.first_name", - "members.last_name", - "lock.id", - "lock.user.id", - "lock.user.first_name", - "lock.user.last_name", - "lock.expiration_date", - ], - ] - ] + rooms: ROOM_DATA user_room_lock: Pick[RoomLock, Literal["id", "password"]] + + +@template +class AdminRoomsList(NamedTuple): + rooms: ROOM_DATA diff --git a/app/server/rooms/urls.py b/app/server/rooms/urls.py index 6790762d..fcdba5a4 100644 --- a/app/server/rooms/urls.py +++ b/app/server/rooms/urls.py @@ -4,6 +4,7 @@ urlpatterns = [ path('', views.index, name='rooms_index'), + path('admin', views.admin_rooms_list, name='admin_rooms_list'), path('list/room_by_user', views.list_csv_room_by_user, name='list_csv_room_by_user'), path('list/room_by_member', views.list_csv_room_by_member, name='list_csv_room_by_member'), path('list/members_by_room', views.list_csv_members_by_room, diff --git a/app/server/rooms/views.py b/app/server/rooms/views.py index bcf670a6..cd9aa922 100644 --- a/app/server/rooms/views.py +++ b/app/server/rooms/views.py @@ -16,7 +16,7 @@ from server.conferences.models import Zosia from .forms import UploadFileForm from .models import Room -from .templates import Rooms +from .templates import AdminRoomsList, Rooms from server.users.models import UserPreferences from server.utils.views import csv_response, validation_format @@ -122,6 +122,15 @@ def handle_uploaded_file(csvfile): code="invalid") +@staff_member_required +@require_http_methods(['GET']) +def admin_rooms_list(request): + rooms = Room.objects.all_visible().prefetch_related('members').all() + rooms = sorted(rooms, key=lambda x: x.pk) + + return AdminRoomsList(rooms=rooms).render(request) + + @staff_member_required @require_http_methods(['GET', 'POST']) def import_room(request): From 521fb0cb47a996f859d4b2ce707b66e5811363ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Szczeci=C5=84ski?= Date: Wed, 13 Nov 2024 17:50:42 +0100 Subject: [PATCH 03/21] Add create room form ui --- app/client/components/rooms/Rooms.tsx | 1 + app/client/components/rooms/RoomsBar.tsx | 104 ++++++++++++------ .../rooms/admin/RoomPropertiesDialog.tsx | 82 ++++++++++++++ .../admin/RoomPropertiesFormFieldCheckbox.tsx | 26 +++++ .../admin/RoomPropertiesFormFieldInput.tsx | 32 ++++++ .../admin/RoomPropertiesFormFieldLabel.tsx | 16 +++ app/client/templates/AdminRoomsList.tsx | 7 +- 7 files changed, 230 insertions(+), 38 deletions(-) create mode 100644 app/client/components/rooms/admin/RoomPropertiesDialog.tsx create mode 100644 app/client/components/rooms/admin/RoomPropertiesFormFieldCheckbox.tsx create mode 100644 app/client/components/rooms/admin/RoomPropertiesFormFieldInput.tsx create mode 100644 app/client/components/rooms/admin/RoomPropertiesFormFieldLabel.tsx diff --git a/app/client/components/rooms/Rooms.tsx b/app/client/components/rooms/Rooms.tsx index 0ca95b18..a9a74230 100644 --- a/app/client/components/rooms/Rooms.tsx +++ b/app/client/components/rooms/Rooms.tsx @@ -22,6 +22,7 @@ export const Rooms = ({ initialRoomsData, isAdmin }: RoomsProps) => { onHideFullRoomsChange={setHideFullRooms} sortRoomsBy={sortRoomsBy} onSortRoomsByChange={setSortRoomsBy} + isAdmin={isAdmin} /> void; @@ -10,6 +14,8 @@ interface RoomsBarProps { onHideFullRoomsChange: (hide: boolean) => void; sortRoomsBy: RoomsSortBy; onSortRoomsByChange: (sortBy: RoomsSortBy) => void; + + isAdmin?: boolean; } export type RoomsSortBy = "roomNumber" | "fullness"; @@ -21,44 +27,70 @@ export const RoomsBar = ({ onHideFullRoomsChange, sortRoomsBy, onSortRoomsByChange, + isAdmin, }: RoomsBarProps) => { + const [addRoomDialogOpen, setAddRoomDialogOpen] = useState(false); + return ( -
- - onSearchTextChange(e.target.value)} - /> - - +
+
+ + onSearchTextChange(e.target.value)} + /> + + + + + + + - - - - - - - onSortRoomsByChange(newValue as RoomsSortBy)} - multiple={false} - /> - + + + + onSortRoomsByChange(newValue as RoomsSortBy) + } + multiple={false} + /> + +
+ {isAdmin && ( +
+ + + + Import rooms + + setAddRoomDialogOpen(false)} + /> +
+ )}
); }; diff --git a/app/client/components/rooms/admin/RoomPropertiesDialog.tsx b/app/client/components/rooms/admin/RoomPropertiesDialog.tsx new file mode 100644 index 00000000..ad901eeb --- /dev/null +++ b/app/client/components/rooms/admin/RoomPropertiesDialog.tsx @@ -0,0 +1,82 @@ +import React, { useState } from "react"; +import { CustomDialog } from "../../CustomDialog"; +import { RoomPropertiesFormFieldCheckbox } from "./RoomPropertiesFormFieldCheckbox"; +import { RoomPropertiesFormFieldInput } from "./RoomPropertiesFormFieldInput"; + +interface RoomPropertiesDialogProps { + dialogOpen: boolean; + onClose: () => void; +} + +export const RoomPropertiesDialog = ({ + dialogOpen, + onClose, +}: RoomPropertiesDialogProps) => { + const [roomName, setRoomName] = useState(""); + const [roomDescription, setRoomDescription] = useState(""); + + const [availableBedsSingle, setAvailableBedsSingle] = useState(0); + const [availableBedsDouble, setAvailableBedsDouble] = useState(0); + const [bedsSingle, setBedsSingle] = useState(0); + const [bedsDouble, setBedsDouble] = useState(0); + + const [roomHidden, setRoomHidden] = useState(false); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + alert("TODO: Add room"); + onClose(); + }; + + return ( + +
+ + + setAvailableBedsSingle(parseInt(newValue))} + /> + setAvailableBedsDouble(parseInt(newValue))} + /> + setBedsSingle(parseInt(newValue))} + /> + setBedsDouble(parseInt(newValue))} + /> + + + +
+ ); +}; diff --git a/app/client/components/rooms/admin/RoomPropertiesFormFieldCheckbox.tsx b/app/client/components/rooms/admin/RoomPropertiesFormFieldCheckbox.tsx new file mode 100644 index 00000000..19286b2f --- /dev/null +++ b/app/client/components/rooms/admin/RoomPropertiesFormFieldCheckbox.tsx @@ -0,0 +1,26 @@ +import { Checkbox, Field } from "@headlessui/react"; +import React from "react"; +import { RoomPropertiesFormFieldLabel } from "./RoomPropertiesFormFieldLabel"; + +interface RoomPropertiesFormFieldCheckboxProps { + label: string; + checked: boolean; + onChange: (newValue: boolean) => void; +} + +export const RoomPropertiesFormFieldCheckbox = ({ + label, + checked, + onChange, +}: RoomPropertiesFormFieldCheckboxProps) => { + return ( + + + + + ); +}; diff --git a/app/client/components/rooms/admin/RoomPropertiesFormFieldInput.tsx b/app/client/components/rooms/admin/RoomPropertiesFormFieldInput.tsx new file mode 100644 index 00000000..691c6742 --- /dev/null +++ b/app/client/components/rooms/admin/RoomPropertiesFormFieldInput.tsx @@ -0,0 +1,32 @@ +import { Field, Input } from "@headlessui/react"; +import React from "react"; +import { RoomPropertiesFormFieldLabel } from "./RoomPropertiesFormFieldLabel"; + +interface RoomPropertiesFormFieldInputProps { + value: string; + onChange: (newValue: string) => void; + type: React.HTMLInputTypeAttribute; + label: string; + required?: boolean; +} + +export const RoomPropertiesFormFieldInput = ({ + value, + onChange, + type, + label, + required, +}: RoomPropertiesFormFieldInputProps) => { + return ( + + + onChange(e.target.value)} + /> + + ); +}; diff --git a/app/client/components/rooms/admin/RoomPropertiesFormFieldLabel.tsx b/app/client/components/rooms/admin/RoomPropertiesFormFieldLabel.tsx new file mode 100644 index 00000000..8020fc74 --- /dev/null +++ b/app/client/components/rooms/admin/RoomPropertiesFormFieldLabel.tsx @@ -0,0 +1,16 @@ +import { Label } from "@headlessui/react"; +import React from "react"; + +interface RoomPropertiesFormFieldLabel { + label: string; +} + +export const RoomPropertiesFormFieldLabel = ({ + label, +}: RoomPropertiesFormFieldLabel) => { + return ( + + ); +}; diff --git a/app/client/templates/AdminRoomsList.tsx b/app/client/templates/AdminRoomsList.tsx index 8bc6bf48..8f0a359e 100644 --- a/app/client/templates/AdminRoomsList.tsx +++ b/app/client/templates/AdminRoomsList.tsx @@ -9,9 +9,12 @@ import React from "react"; export const Template = (props: templates.AdminRoomsList) => { return ( - Rooms + Rooms Admin - + ); From a5b3e7b5fa3fd1cf897e5d618dc71d7a145e942b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Szczeci=C5=84ski?= Date: Wed, 20 Nov 2024 22:05:26 +0100 Subject: [PATCH 04/21] Rename signup_rules/ to zapisy/ --- app/server/conferences/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/server/conferences/urls.py b/app/server/conferences/urls.py index b5b12f72..da9518c4 100644 --- a/app/server/conferences/urls.py +++ b/app/server/conferences/urls.py @@ -32,5 +32,5 @@ path('place/add/', views.place_add, name='place_add'), path('place//update/', views.place_add, name='place_update'), path('statistics/', views.statistics, name='statistics'), - path('signup_rules/', views.sign_up_rules_for_invited, name='invited'), + path('zapisy/', views.sign_up_rules_for_invited, name='invited'), ] From f1ca01e9626fa7f05c6912d79ffaeb833d8151b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Hajahmadov?= <91793810+mhajah@users.noreply.github.com> Date: Thu, 21 Nov 2024 00:04:23 +0100 Subject: [PATCH 05/21] Add files via upload --- app/static/imgs/TSG_vertical_logo.svg | 36 +++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 app/static/imgs/TSG_vertical_logo.svg diff --git a/app/static/imgs/TSG_vertical_logo.svg b/app/static/imgs/TSG_vertical_logo.svg new file mode 100644 index 00000000..7cdbc2e6 --- /dev/null +++ b/app/static/imgs/TSG_vertical_logo.svg @@ -0,0 +1,36 @@ + +image/svg+xml \ No newline at end of file From 6424f05899c702b58f2cce91e61f386a1edb5d10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Hajahmadov?= <91793810+mhajah@users.noreply.github.com> Date: Thu, 21 Nov 2024 00:40:30 +0100 Subject: [PATCH 06/21] Update sign-up info (revert me later) --- app/client/templates/SignupRules.tsx | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/app/client/templates/SignupRules.tsx b/app/client/templates/SignupRules.tsx index f590117f..5e1a0fb6 100644 --- a/app/client/templates/SignupRules.tsx +++ b/app/client/templates/SignupRules.tsx @@ -6,28 +6,13 @@ import Markdown from "react-markdown"; const signupRulesMarkdown = ` ## Instrukcja zapisów -1. Stwórz konto za pomocą przycisku "Sign up". Jako Email podaj +Stwórz konto za pomocą przycisku "Sign up". Jako Email podaj adres w domenie **cs.uni.wroc.pl** lub dowolnej innej*. Konta zakładane na naszej stronie w przeszłości zostały usunięte. -2. By zarejestrować się na najbliższą edycję ZOSI na stronie głównej -lub w swoim profilu w zakładce ZOSIA kliknij przycisk **REGISTER**. - -3. Uzupełnij kwestionariusz. W polu **Information** koniecznie wpisz -swoje preferencje przy wyborze pokoju. Potrzebujemy wiedzieć czy -wolisz pokój z łóżkiem podwójnym, a może pokój jednoosobowy, czy -wolisz pokój w cichym zakątku hotelu, czy masz już wybraną osobę -współlokatorską, wolisz wybrać ją później, czy może jest Ci to -obojętne. Prosimy o uzupełnienie tego pola. - -4. Wyślij formularz i gotowe! - Jako osoba zaproszona posiadasz specjalny priorytet i możesz wybrać -pokój przed resztą uczestników. Do 31 grudnia mamy najwięcej -możliwości, by pomóc Ci w wyborze pokoju. Po podanej dacie dalej -jesteśmy do Twojej dyspozji i zawsze służymy pomocą. Natomiast im -bliżej do daty rozpoczęcia Konferencji, tym nasz zasób pokoi będzie -coraz bardziej ograniczony. +pokój przed resztą uczestników. Wcześniejsze zapisy startują +na początku grudnia. O ich początku poinformujemy mailowo. W razie jakichkolwiek pytań lub problemów, zachęcamy do kontaktu: [ksi@cs.uni.wroc.pl](mailto:ksi@cs.uni.wroc.pl) From 6595e50aaac3afbd010b33c9c98af208ae48932d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Szczeci=C5=84ski?= Date: Fri, 22 Nov 2024 15:40:34 +0100 Subject: [PATCH 07/21] Update comments in createsuperuser.sh script --- app/scripts/createsuperuser.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/scripts/createsuperuser.sh b/app/scripts/createsuperuser.sh index 6f527fa4..2401bdee 100755 --- a/app/scripts/createsuperuser.sh +++ b/app/scripts/createsuperuser.sh @@ -5,5 +5,7 @@ set -eu # DJANGO_SUPERUSER_USERNAME # DJANGO_SUPERUSER_EMAIL # DJANGO_SUPERUSER_PASSWORD +# DJANGO_SUPERUSER_FIRST_NAME +# DJANGO_SUPERUSER_LAST_NAME python manage.py createsuperuser --noinput \ No newline at end of file From a409658abec2bae17bf8bc92596d96cda6cbaddc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Szczeci=C5=84ski?= Date: Fri, 22 Nov 2024 16:03:02 +0100 Subject: [PATCH 08/21] Fix event info not centered in Home Page on mobile --- .../components/homePage/conferenceInfo/ConferenceInfo.tsx | 2 +- app/client/components/homePage/conferenceInfo/EventDates.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/client/components/homePage/conferenceInfo/ConferenceInfo.tsx b/app/client/components/homePage/conferenceInfo/ConferenceInfo.tsx index 90ae6850..d738850d 100644 --- a/app/client/components/homePage/conferenceInfo/ConferenceInfo.tsx +++ b/app/client/components/homePage/conferenceInfo/ConferenceInfo.tsx @@ -68,7 +68,7 @@ export const ConferenceInfo = ({ > {placeName} -

{placeAddress}

+

{placeAddress}

diff --git a/app/client/components/homePage/conferenceInfo/EventDates.tsx b/app/client/components/homePage/conferenceInfo/EventDates.tsx index f0c85a32..4cbf0613 100644 --- a/app/client/components/homePage/conferenceInfo/EventDates.tsx +++ b/app/client/components/homePage/conferenceInfo/EventDates.tsx @@ -9,7 +9,7 @@ interface EventDatesProps { export const EventDates = ({ startDate, endDate, title }: EventDatesProps) => { return ( -
+

{`${title}:`}

{`Start: ${getLocalDateTime(startDate)}`}
From 49e636a4212c02e55dba5e9c6fd9cd019445e75c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Szczeci=C5=84ski?= Date: Fri, 22 Nov 2024 17:53:19 +0100 Subject: [PATCH 09/21] Add create room form submit logic --- app/client/components/rooms/RoomCards.tsx | 2 +- app/client/components/rooms/RoomMutations.tsx | 11 ++++++++ .../rooms/admin/RoomPropertiesDialog.tsx | 25 ++++++++++++++++--- app/client/utils/roomData.ts | 11 ++++++++ app/server/rooms/views.py | 5 ++-- 5 files changed, 47 insertions(+), 7 deletions(-) diff --git a/app/client/components/rooms/RoomCards.tsx b/app/client/components/rooms/RoomCards.tsx index d6fccebc..02da9eeb 100644 --- a/app/client/components/rooms/RoomCards.tsx +++ b/app/client/components/rooms/RoomCards.tsx @@ -115,7 +115,7 @@ export const RoomCards = ({
{sortedResults.map((room) => ( diff --git a/app/client/components/rooms/RoomMutations.tsx b/app/client/components/rooms/RoomMutations.tsx index 25025690..94cc172e 100644 --- a/app/client/components/rooms/RoomMutations.tsx +++ b/app/client/components/rooms/RoomMutations.tsx @@ -2,6 +2,7 @@ import { convertRoomApiDataToRoomData, ROOM_QUERY_KEY, RoomApiData, + RoomCreateApiData, RoomData, } from "@client/utils/roomData"; import { zosiaApi, zosiaApiRoutes } from "@client/utils/zosiaApi"; @@ -103,10 +104,20 @@ export const useRoomMutations = (roomId: number, roomName: string) => { onError: onMutationError, }); + const createRoomMutation = useMutation({ + mutationFn: async (roomData: RoomCreateApiData) => { + return await zosiaApi.post(zosiaApiRoutes.rooms, roomData); + }, + onSuccess: (data) => + onMutationSuccess(data, `You've created room ${data.data.name}.`), + onError: onMutationError, + }); + return { joinRoomMutation, leaveRoomMutation, lockRoomMutation, unlockRoomMutation, + createRoomMutation, }; }; diff --git a/app/client/components/rooms/admin/RoomPropertiesDialog.tsx b/app/client/components/rooms/admin/RoomPropertiesDialog.tsx index ad901eeb..a95a4916 100644 --- a/app/client/components/rooms/admin/RoomPropertiesDialog.tsx +++ b/app/client/components/rooms/admin/RoomPropertiesDialog.tsx @@ -1,5 +1,7 @@ +import { LoadingContentSpinner } from "@client/components/LoadingContentSpinner"; import React, { useState } from "react"; import { CustomDialog } from "../../CustomDialog"; +import { useRoomMutations } from "../RoomMutations"; import { RoomPropertiesFormFieldCheckbox } from "./RoomPropertiesFormFieldCheckbox"; import { RoomPropertiesFormFieldInput } from "./RoomPropertiesFormFieldInput"; @@ -22,10 +24,19 @@ export const RoomPropertiesDialog = ({ const [roomHidden, setRoomHidden] = useState(false); + const { createRoomMutation } = useRoomMutations(0, ""); + const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - alert("TODO: Add room"); - onClose(); + createRoomMutation.mutate({ + name: roomName, + description: roomDescription, + available_beds_single: availableBedsSingle, + available_beds_double: availableBedsDouble, + beds_single: bedsSingle, + beds_double: bedsDouble, + hidden: roomHidden, + }); }; return ( @@ -73,8 +84,14 @@ export const RoomPropertiesDialog = ({ checked={roomHidden} onChange={setRoomHidden} /> - diff --git a/app/client/utils/roomData.ts b/app/client/utils/roomData.ts index c0a90016..ece04917 100644 --- a/app/client/utils/roomData.ts +++ b/app/client/utils/roomData.ts @@ -21,6 +21,17 @@ export interface RoomApiData { available_beds_double: number; } +/** Data used to create a new room */ +export interface RoomCreateApiData { + name: string; + description: string; + available_beds_single: number; + available_beds_double: number; + beds_single: number; + beds_double: number; + hidden: boolean; +} + interface RoomApiUserData { id: number; first_name: string; diff --git a/app/server/rooms/views.py b/app/server/rooms/views.py index cd9aa922..82a3a003 100644 --- a/app/server/rooms/views.py +++ b/app/server/rooms/views.py @@ -61,7 +61,8 @@ def index(request): messages.error(request, _('Room registration is over')) return redirect(reverse('accounts_profile')) - rooms = Room.objects.all_visible().prefetch_related('members').all() + rooms = Room.objects.all() if preferences.user.is_staff else Room.objects.all_visible() + rooms = rooms.prefetch_related('members').all() rooms = sorted(rooms, key=lambda x: x.pk) user_lock = request.user.locks.all().first() @@ -125,7 +126,7 @@ def handle_uploaded_file(csvfile): @staff_member_required @require_http_methods(['GET']) def admin_rooms_list(request): - rooms = Room.objects.all_visible().prefetch_related('members').all() + rooms = Room.objects.all().prefetch_related('members').all() rooms = sorted(rooms, key=lambda x: x.pk) return AdminRoomsList(rooms=rooms).render(request) From 84b5ed29b9cdbd7872cd033c6e5769f2451b9790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Szczeci=C5=84ski?= Date: Fri, 22 Nov 2024 18:13:58 +0100 Subject: [PATCH 10/21] Show hidden rooms in UI --- app/client/components/rooms/RoomCard.tsx | 5 ++++- app/client/utils/roomData.ts | 6 +++++- app/server/rooms/templates.py | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/client/components/rooms/RoomCard.tsx b/app/client/components/rooms/RoomCard.tsx index f18f5399..95201020 100644 --- a/app/client/components/rooms/RoomCard.tsx +++ b/app/client/components/rooms/RoomCard.tsx @@ -25,6 +25,7 @@ export const RoomCard = ({ lock, availableBedsSingle, availableBedsDouble, + hidden, }, userIsInSomeRoomAlready, }: RoomCardProps) => { @@ -64,12 +65,14 @@ export const RoomCard = ({ className={clsx( "card card-bordered card-compact border-base-content bg-base-100 lg:card-normal", isMyRoom && "order-first col-span-2 bg-base-300", + hidden && "glass bg-base-content text-base-100", )} >

- {isMyRoom && Your room: } {name}{" "} + {isMyRoom && Your room: }{" "} + {`${name}${hidden ? " (hidden room)" : ""} `} {isLocked && }

{ : undefined, availableBedsSingle: room.available_beds_single, availableBedsDouble: room.available_beds_double, + hidden: room.hidden, }; }; export const createRoomDataFromTemplateProps = ( props: templates.Rooms | templates.AdminRoomsList, -) => { +): RoomData[] => { return props.rooms.map((room) => ({ id: room.id, name: room.name, @@ -114,5 +117,6 @@ export const createRoomDataFromTemplateProps = ( : undefined, availableBedsSingle: room.available_beds_single, availableBedsDouble: room.available_beds_double, + hidden: room.hidden, })); }; diff --git a/app/server/rooms/templates.py b/app/server/rooms/templates.py index be71b4d7..32f89926 100644 --- a/app/server/rooms/templates.py +++ b/app/server/rooms/templates.py @@ -12,6 +12,7 @@ "description", "available_beds_single", "available_beds_double", + "hidden", "members.id", "members.first_name", "members.last_name", From 49ee4911e950370f7607258bde9e4510b379f10d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Szczeci=C5=84ski?= Date: Fri, 22 Nov 2024 18:18:32 +0100 Subject: [PATCH 11/21] Fix dialog overlay with admin panel navbar --- app/client/components/Layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/client/components/Layout.tsx b/app/client/components/Layout.tsx index ce4caca5..f0180eb6 100644 --- a/app/client/components/Layout.tsx +++ b/app/client/components/Layout.tsx @@ -70,7 +70,7 @@ export const Layout = ({ {showAdminSidebar && ( -
+
)} From 1c75a3e9a24ac8eed49a6c154b6d6b662fd67876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Szczeci=C5=84ski?= Date: Fri, 22 Nov 2024 18:28:10 +0100 Subject: [PATCH 12/21] Remove jumping scroll position in admin panel nav bar --- app/client/components/admin/AdminNavBar.tsx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/app/client/components/admin/AdminNavBar.tsx b/app/client/components/admin/AdminNavBar.tsx index 352114e2..caad0cab 100644 --- a/app/client/components/admin/AdminNavBar.tsx +++ b/app/client/components/admin/AdminNavBar.tsx @@ -1,7 +1,7 @@ import { ArrowDownTrayIcon } from "@heroicons/react/24/solid"; import { Context, reverse } from "@reactivated"; import clsx from "clsx"; -import React, { useContext, useEffect, useRef } from "react"; +import React, { useContext } from "react"; interface AdminNavBarProps { showAsSidebar?: boolean; @@ -150,17 +150,6 @@ const navbarSections: AdminNavBarSection[] = [ export const AdminNavBar = ({ showAsSidebar }: AdminNavBarProps) => { const { request } = useContext(Context); - const currentPathLinkRef = useRef(null); - - useEffect(() => { - if (currentPathLinkRef.current) { - currentPathLinkRef.current.scrollIntoView({ - behavior: "instant", - block: "center", - }); - } - }, []); - return (
diff --git a/app/client/components/rooms/Rooms.tsx b/app/client/components/rooms/Rooms.tsx index a9a74230..40a0908f 100644 --- a/app/client/components/rooms/Rooms.tsx +++ b/app/client/components/rooms/Rooms.tsx @@ -29,6 +29,7 @@ export const Rooms = ({ initialRoomsData, isAdmin }: RoomsProps) => { searchText={searchText} hideFullRooms={hideFullRooms} sortRoomsBy={sortRoomsBy} + isAdmin={isAdmin} /> ); diff --git a/app/client/components/rooms/api/RoomMutations.tsx b/app/client/components/rooms/api/RoomMutations.tsx index 99b28874..740a42f4 100644 --- a/app/client/components/rooms/api/RoomMutations.tsx +++ b/app/client/components/rooms/api/RoomMutations.tsx @@ -13,7 +13,7 @@ import React, { useContext } from "react"; import { showCustomToast } from "../../CustomToast"; import { ApiErrorMessage } from "./ApiErrorMessage"; -export const useRoomMutations = (roomId: number, roomName: string) => { +export const useRoomMutations = (roomId: number) => { const { user } = useContext(Context); const queryClient = useQueryClient(); @@ -58,7 +58,7 @@ export const useRoomMutations = (roomId: number, roomName: string) => { ); }, onSuccess: (data) => - onMutationSuccess(data, `You've joined room ${roomName}.`), + onMutationSuccess(data, `You've joined room ${data.data.name}.`), onError: onMutationError, }); @@ -72,7 +72,7 @@ export const useRoomMutations = (roomId: number, roomName: string) => { ); }, onSuccess: (data) => - onMutationSuccess(data, `You've left room ${roomName}.`), + onMutationSuccess(data, `You've left room ${data.data.name}.`), onError: onMutationError, }); @@ -85,7 +85,7 @@ export const useRoomMutations = (roomId: number, roomName: string) => { onSuccess: (data) => onMutationSuccess( data, - `You've locked room ${roomName}. Share the password with your friends.`, + `You've locked room ${data.data.name}. Share the password with your friends.`, ), onError: onMutationError, }); @@ -99,7 +99,7 @@ export const useRoomMutations = (roomId: number, roomName: string) => { onSuccess: (data) => onMutationSuccess( data, - `You've unlocked room ${roomName}. Now everybody can join it.`, + `You've unlocked room ${data.data.name}. Now everybody can join it.`, ), onError: onMutationError, }); @@ -113,11 +113,34 @@ export const useRoomMutations = (roomId: number, roomName: string) => { onError: onMutationError, }); + const deleteRoomMutation = useMutation({ + mutationFn: async () => { + return await zosiaApi.delete(zosiaApiRoutes.room(roomId)); + }, + onSuccess: (data) => + onMutationSuccess(data, `You've deleted room ${data.data.name}.`), + onError: onMutationError, + }); + + const editRoomMutation = useMutation({ + mutationFn: async (roomData: RoomCreateApiData) => { + return await zosiaApi.put( + zosiaApiRoutes.room(roomId), + roomData, + ); + }, + onSuccess: (data) => + onMutationSuccess(data, `You've edited room ${data.data.name}.`), + onError: onMutationError, + }); + return { joinRoomMutation, leaveRoomMutation, lockRoomMutation, unlockRoomMutation, createRoomMutation, + deleteRoomMutation, + editRoomMutation, }; }; diff --git a/app/client/components/rooms/card/RoomActions.tsx b/app/client/components/rooms/card/RoomActions.tsx index c26051ea..a5da7909 100644 --- a/app/client/components/rooms/card/RoomActions.tsx +++ b/app/client/components/rooms/card/RoomActions.tsx @@ -1,16 +1,13 @@ -import { - ArrowRightEndOnRectangleIcon, - ArrowRightStartOnRectangleIcon, - LockClosedIcon, - LockOpenIcon, -} from "@heroicons/react/24/outline"; -import clsx from "clsx"; +import { ArrowRightEndOnRectangleIcon } from "@heroicons/react/24/outline"; import React from "react"; import { LoadingContentSpinner } from "../../LoadingContentSpinner"; +import { RoomActionsAdmin } from "./RoomActionsAdmin"; +import { RoomActionsMyRoom } from "./RoomActionsMyRoom"; -const ICON_CSS = "size-5"; +export const ROOM_ACTION_ICON_CSS = "size-5"; interface RoomActionsProps { + isAdmin?: boolean; isMyRoom: boolean; isLocked: boolean; canUnlock: boolean; @@ -19,13 +16,19 @@ interface RoomActionsProps { leaveRoom: () => void; lockRoom: () => void; unlockRoom: () => void; + deleteRoom: () => void; + editRoom: () => void; + enterRoomPending: boolean; leaveRoomPending: boolean; lockRoomPending: boolean; unlockRoomPending: boolean; + deleteRoomPending: boolean; + editRoomPending: boolean; } export const RoomActions = ({ + isAdmin, isMyRoom, isLocked, canUnlock, @@ -34,54 +37,40 @@ export const RoomActions = ({ leaveRoom, lockRoom, unlockRoom, + deleteRoom, + editRoom, enterRoomPending, leaveRoomPending, lockRoomPending, unlockRoomPending, + deleteRoomPending, + editRoomPending, }: RoomActionsProps) => { - if (isMyRoom) { - const showUnlockButton = isLocked && canUnlock; - const showLockButton = !isLocked; + console.log(isAdmin); + if (isAdmin) { return ( -
- {showUnlockButton && ( - - )} - - {showLockButton && ( - - )} + + ); + } - -
+ if (isMyRoom) { + return ( + ); } @@ -92,7 +81,7 @@ export const RoomActions = ({ onClick={enterRoom} > - Enter + Enter ); diff --git a/app/client/components/rooms/card/RoomActionsAdmin.tsx b/app/client/components/rooms/card/RoomActionsAdmin.tsx new file mode 100644 index 00000000..00ee93ff --- /dev/null +++ b/app/client/components/rooms/card/RoomActionsAdmin.tsx @@ -0,0 +1,42 @@ +import { LoadingContentSpinner } from "@client/components/LoadingContentSpinner"; +import { PencilIcon, TrashIcon } from "@heroicons/react/24/solid"; +import React from "react"; +import { ROOM_ACTION_ICON_CSS } from "./RoomActions"; + +interface RoomActionsAdminProps { + deleteRoom: () => void; + editRoom: () => void; + deleteRoomPending: boolean; + editRoomPending: boolean; +} + +export const RoomActionsAdmin = ({ + deleteRoom, + editRoom, + deleteRoomPending, + editRoomPending, +}: RoomActionsAdminProps) => { + return ( +
+ + + +
+ ); +}; diff --git a/app/client/components/rooms/card/RoomActionsMyRoom.tsx b/app/client/components/rooms/card/RoomActionsMyRoom.tsx new file mode 100644 index 00000000..fc7b036b --- /dev/null +++ b/app/client/components/rooms/card/RoomActionsMyRoom.tsx @@ -0,0 +1,76 @@ +import { LoadingContentSpinner } from "@client/components/LoadingContentSpinner"; +import { + ArrowRightStartOnRectangleIcon, + LockClosedIcon, + LockOpenIcon, +} from "@heroicons/react/24/solid"; +import clsx from "clsx"; +import React from "react"; +import { ROOM_ACTION_ICON_CSS } from "./RoomActions"; + +interface RoomActionsMyRoomProps { + isLocked: boolean; + canUnlock: boolean; + leaveRoom: () => void; + lockRoom: () => void; + unlockRoom: () => void; + leaveRoomPending: boolean; + lockRoomPending: boolean; + unlockRoomPending: boolean; +} + +export const RoomActionsMyRoom = ({ + isLocked, + canUnlock, + leaveRoom, + lockRoom, + unlockRoom, + leaveRoomPending, + lockRoomPending, + unlockRoomPending, +}: RoomActionsMyRoomProps) => { + const showUnlockButton = isLocked && canUnlock; + const showLockButton = !isLocked; + + return ( +
+ {showUnlockButton && ( + + )} + + {showLockButton && ( + + )} + + +
+ ); +}; diff --git a/app/client/components/rooms/card/RoomCard.tsx b/app/client/components/rooms/card/RoomCard.tsx index 1f0beb8d..b74cc7e4 100644 --- a/app/client/components/rooms/card/RoomCard.tsx +++ b/app/client/components/rooms/card/RoomCard.tsx @@ -14,6 +14,7 @@ import { RoomMembersCount } from "./RoomMembersCount"; interface RoomCardProps { roomData: RoomData; userIsInSomeRoomAlready: boolean; + isAdmin?: boolean; } export const RoomCard = ({ @@ -28,6 +29,7 @@ export const RoomCard = ({ hidden, }, userIsInSomeRoomAlready, + isAdmin, }: RoomCardProps) => { const { user } = useContext(Context); @@ -36,7 +38,9 @@ export const RoomCard = ({ leaveRoomMutation, lockRoomMutation, unlockRoomMutation, - } = useRoomMutations(id, name); + deleteRoomMutation, + editRoomMutation, + } = useRoomMutations(id); const [roomPasswordDialogOpen, setRoomPasswordDialogOpen] = useState(false); @@ -59,6 +63,8 @@ export const RoomCard = ({ const leaveRoom = () => leaveRoomMutation.mutate(); const lockRoom = () => lockRoomMutation.mutate(); const unlockRoom = () => unlockRoomMutation.mutate(); + const deleteRoom = () => deleteRoomMutation.mutate(); + const editRoom = () => alert("TODO: Edit room"); return (
diff --git a/app/client/utils/zosiaApi.ts b/app/client/utils/zosiaApi.ts index a5f58fee..25dec36e 100644 --- a/app/client/utils/zosiaApi.ts +++ b/app/client/utils/zosiaApi.ts @@ -16,6 +16,7 @@ export const zosiaApiRoutes = { addLectureDurations: reverse("load_durations"), rooms: "api/v2/rooms/", + room: (id: number) => `api/v2/rooms/${id}/`, roomMember: (roomId: number) => `api/v2/rooms/${roomId}/member/`, lockRoom: (roomId: number) => `api/v2/rooms/${roomId}/lock/`, From e106d02bc620a141feb93eeb7400eafa37efe6cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Szczeci=C5=84ski?= Date: Fri, 22 Nov 2024 20:15:57 +0100 Subject: [PATCH 15/21] Fix delete room mutation --- .../components/rooms/api/RoomMutations.tsx | 38 ++++++++++--------- .../components/rooms/card/RoomActions.tsx | 2 - app/client/components/rooms/card/RoomCard.tsx | 2 +- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/app/client/components/rooms/api/RoomMutations.tsx b/app/client/components/rooms/api/RoomMutations.tsx index 740a42f4..3fda1480 100644 --- a/app/client/components/rooms/api/RoomMutations.tsx +++ b/app/client/components/rooms/api/RoomMutations.tsx @@ -13,7 +13,7 @@ import React, { useContext } from "react"; import { showCustomToast } from "../../CustomToast"; import { ApiErrorMessage } from "./ApiErrorMessage"; -export const useRoomMutations = (roomId: number) => { +export const useRoomMutations = (roomId: number, roomName: string) => { const { user } = useContext(Context); const queryClient = useQueryClient(); @@ -23,16 +23,19 @@ export const useRoomMutations = (roomId: number) => { }; const onMutationSuccess = ( - data: AxiosResponse, message: string, + data?: AxiosResponse, ) => { - // Update rooms data with this specific room right after getting the response from server. - const updatedRoom = convertRoomApiDataToRoomData(data.data); - queryClient.setQueryData([ROOM_QUERY_KEY], (oldData: RoomData[]) => { - return oldData.map((room) => - room.id === updatedRoom.id ? updatedRoom : room, - ); - }); + if (data) { + // Update rooms data with this specific room right after getting the response from server. + const updatedRoom = convertRoomApiDataToRoomData(data.data); + queryClient.setQueryData([ROOM_QUERY_KEY], (oldData: RoomData[]) => { + return oldData.map((room) => + room.id === updatedRoom.id ? updatedRoom : room, + ); + }); + } + showCustomToast("success", message); // Invalidate the rooms data to refetch it from the server and get the most recent data for other rooms. @@ -58,7 +61,7 @@ export const useRoomMutations = (roomId: number) => { ); }, onSuccess: (data) => - onMutationSuccess(data, `You've joined room ${data.data.name}.`), + onMutationSuccess(`You've joined room ${data.data.name}.`, data), onError: onMutationError, }); @@ -72,7 +75,7 @@ export const useRoomMutations = (roomId: number) => { ); }, onSuccess: (data) => - onMutationSuccess(data, `You've left room ${data.data.name}.`), + onMutationSuccess(`You've left room ${data.data.name}.`, data), onError: onMutationError, }); @@ -84,8 +87,8 @@ export const useRoomMutations = (roomId: number) => { }, onSuccess: (data) => onMutationSuccess( - data, `You've locked room ${data.data.name}. Share the password with your friends.`, + data, ), onError: onMutationError, }); @@ -98,8 +101,8 @@ export const useRoomMutations = (roomId: number) => { }, onSuccess: (data) => onMutationSuccess( - data, `You've unlocked room ${data.data.name}. Now everybody can join it.`, + data, ), onError: onMutationError, }); @@ -109,16 +112,15 @@ export const useRoomMutations = (roomId: number) => { return await zosiaApi.post(zosiaApiRoutes.rooms, roomData); }, onSuccess: (data) => - onMutationSuccess(data, `You've created room ${data.data.name}.`), + onMutationSuccess(`You've created room ${data.data.name}.`, data), onError: onMutationError, }); const deleteRoomMutation = useMutation({ mutationFn: async () => { - return await zosiaApi.delete(zosiaApiRoutes.room(roomId)); + return await zosiaApi.delete(zosiaApiRoutes.room(roomId)); }, - onSuccess: (data) => - onMutationSuccess(data, `You've deleted room ${data.data.name}.`), + onSuccess: () => onMutationSuccess(`You've deleted room ${roomName}.`), onError: onMutationError, }); @@ -130,7 +132,7 @@ export const useRoomMutations = (roomId: number) => { ); }, onSuccess: (data) => - onMutationSuccess(data, `You've edited room ${data.data.name}.`), + onMutationSuccess(`You've edited room ${data.data.name}.`, data), onError: onMutationError, }); diff --git a/app/client/components/rooms/card/RoomActions.tsx b/app/client/components/rooms/card/RoomActions.tsx index a5da7909..068bb09e 100644 --- a/app/client/components/rooms/card/RoomActions.tsx +++ b/app/client/components/rooms/card/RoomActions.tsx @@ -46,8 +46,6 @@ export const RoomActions = ({ deleteRoomPending, editRoomPending, }: RoomActionsProps) => { - console.log(isAdmin); - if (isAdmin) { return ( Date: Fri, 22 Nov 2024 20:32:56 +0100 Subject: [PATCH 16/21] Add room delete confirmation dialog --- .../admin/RoomDeleteConfirmationDialog.tsx | 44 +++++++++++++++++++ .../rooms/admin/RoomPropertiesDialog.tsx | 1 + .../components/rooms/api/RoomMutations.tsx | 5 ++- app/client/components/rooms/card/RoomCard.tsx | 13 +++++- 4 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 app/client/components/rooms/admin/RoomDeleteConfirmationDialog.tsx diff --git a/app/client/components/rooms/admin/RoomDeleteConfirmationDialog.tsx b/app/client/components/rooms/admin/RoomDeleteConfirmationDialog.tsx new file mode 100644 index 00000000..11a06516 --- /dev/null +++ b/app/client/components/rooms/admin/RoomDeleteConfirmationDialog.tsx @@ -0,0 +1,44 @@ +import { CustomDialog } from "@client/components/CustomDialog"; +import { LoadingContentSpinner } from "@client/components/LoadingContentSpinner"; +import { UseMutationResult } from "@tanstack/react-query"; +import { AxiosResponse } from "axios"; +import React from "react"; + +interface RoomDeleteConfirmationDialogProps { + roomName: string; + deleteRoomMutation: UseMutationResult< + AxiosResponse, + Error, + void, + unknown + >; + dialogOpen: boolean; + closeDialog: () => void; +} + +export const RoomDeleteConfirmationDialog = ({ + roomName, + deleteRoomMutation, + dialogOpen, + closeDialog, +}: RoomDeleteConfirmationDialogProps) => { + return ( + + + + ); +}; diff --git a/app/client/components/rooms/admin/RoomPropertiesDialog.tsx b/app/client/components/rooms/admin/RoomPropertiesDialog.tsx index c6ae2f11..6a59cc04 100644 --- a/app/client/components/rooms/admin/RoomPropertiesDialog.tsx +++ b/app/client/components/rooms/admin/RoomPropertiesDialog.tsx @@ -37,6 +37,7 @@ export const RoomPropertiesDialog = ({ beds_double: bedsDouble, hidden: roomHidden, }); + onClose(); }; return ( diff --git a/app/client/components/rooms/api/RoomMutations.tsx b/app/client/components/rooms/api/RoomMutations.tsx index 3fda1480..fcac39cc 100644 --- a/app/client/components/rooms/api/RoomMutations.tsx +++ b/app/client/components/rooms/api/RoomMutations.tsx @@ -120,7 +120,10 @@ export const useRoomMutations = (roomId: number, roomName: string) => { mutationFn: async () => { return await zosiaApi.delete(zosiaApiRoutes.room(roomId)); }, - onSuccess: () => onMutationSuccess(`You've deleted room ${roomName}.`), + onSuccess: () => + onMutationSuccess( + `You've deleted room ${roomName}. You should inform its inhabitants about this.`, + ), onError: onMutationError, }); diff --git a/app/client/components/rooms/card/RoomCard.tsx b/app/client/components/rooms/card/RoomCard.tsx index 9efd3860..3f839963 100644 --- a/app/client/components/rooms/card/RoomCard.tsx +++ b/app/client/components/rooms/card/RoomCard.tsx @@ -5,6 +5,7 @@ import { LockClosedIcon as LockClosedIconSolid } from "@heroicons/react/24/solid import { Context } from "@reactivated"; import clsx from "clsx"; import React, { useContext, useState } from "react"; +import { RoomDeleteConfirmationDialog } from "../admin/RoomDeleteConfirmationDialog"; import { useRoomMutations } from "../api/RoomMutations"; import { JoinLockedRoomDialog } from "../JoinLockedRoomDialog"; import { RoomActions } from "./RoomActions"; @@ -43,6 +44,10 @@ export const RoomCard = ({ } = useRoomMutations(id, name); const [roomPasswordDialogOpen, setRoomPasswordDialogOpen] = useState(false); + const [ + roomDeleteConfirmationDialogOpen, + setRoomDeleteConfirmationDialogOpen, + ] = useState(false); const allPlaces = availableBedsSingle + availableBedsDouble * 2; const availablePlaces = allPlaces - members.length; @@ -63,7 +68,7 @@ export const RoomCard = ({ const leaveRoom = () => leaveRoomMutation.mutate(); const lockRoom = () => lockRoomMutation.mutate(); const unlockRoom = () => unlockRoomMutation.mutate(); - const deleteRoom = () => deleteRoomMutation.mutate(); + const deleteRoom = () => setRoomDeleteConfirmationDialogOpen(true); const editRoom = () => alert("TODO: Edit room"); return ( @@ -134,6 +139,12 @@ export const RoomCard = ({ dialogOpen={roomPasswordDialogOpen} closeDialog={() => setRoomPasswordDialogOpen(false)} /> + setRoomDeleteConfirmationDialogOpen(false)} + />
); }; From 7d585246d7c1361af25fa4b6da3202090fea9bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Szczeci=C5=84ski?= Date: Fri, 22 Nov 2024 21:11:38 +0100 Subject: [PATCH 17/21] Add room edit dialog --- app/client/components/rooms/RoomsBar.tsx | 2 +- .../rooms/admin/RoomPropertiesDialog.tsx | 98 ++++++++++++++----- .../components/rooms/api/RoomMutations.tsx | 3 +- app/client/components/rooms/card/RoomCard.tsx | 30 ++++-- app/client/utils/roomData.ts | 12 +++ app/server/rooms/templates.py | 2 + 6 files changed, 114 insertions(+), 33 deletions(-) diff --git a/app/client/components/rooms/RoomsBar.tsx b/app/client/components/rooms/RoomsBar.tsx index d2acab79..f2804fbf 100644 --- a/app/client/components/rooms/RoomsBar.tsx +++ b/app/client/components/rooms/RoomsBar.tsx @@ -87,7 +87,7 @@ export const RoomsBar = ({ setAddRoomDialogOpen(false)} + closeDialog={() => setAddRoomDialogOpen(false)} />
)} diff --git a/app/client/components/rooms/admin/RoomPropertiesDialog.tsx b/app/client/components/rooms/admin/RoomPropertiesDialog.tsx index 6a59cc04..c0f890bd 100644 --- a/app/client/components/rooms/admin/RoomPropertiesDialog.tsx +++ b/app/client/components/rooms/admin/RoomPropertiesDialog.tsx @@ -1,4 +1,5 @@ import { LoadingContentSpinner } from "@client/components/LoadingContentSpinner"; +import { RoomData } from "@client/utils/roomData"; import React, { useState } from "react"; import { CustomDialog } from "../../CustomDialog"; import { useRoomMutations } from "../api/RoomMutations"; @@ -6,42 +7,95 @@ import { RoomPropertiesFormFieldCheckbox } from "./RoomPropertiesFormFieldCheckb import { RoomPropertiesFormFieldInput } from "./RoomPropertiesFormFieldInput"; interface RoomPropertiesDialogProps { + roomData?: RoomData; dialogOpen: boolean; - onClose: () => void; + closeDialog: () => void; } export const RoomPropertiesDialog = ({ + roomData, dialogOpen, - onClose, + closeDialog, }: RoomPropertiesDialogProps) => { - const [roomName, setRoomName] = useState(""); - const [roomDescription, setRoomDescription] = useState(""); + const [roomName, setRoomName] = useState(roomData ? roomData.name : ""); + const [roomDescription, setRoomDescription] = useState( + roomData ? roomData.description : "", + ); + + const [availableBedsSingle, setAvailableBedsSingle] = useState( + roomData ? roomData.availableBedsSingle : 0, + ); + const [availableBedsDouble, setAvailableBedsDouble] = useState( + roomData ? roomData.availableBedsDouble : 0, + ); + const [bedsSingle, setBedsSingle] = useState( + roomData ? roomData.bedsSingle : 0, + ); + const [bedsDouble, setBedsDouble] = useState( + roomData ? roomData.bedsDouble : 0, + ); - const [availableBedsSingle, setAvailableBedsSingle] = useState(0); - const [availableBedsDouble, setAvailableBedsDouble] = useState(0); - const [bedsSingle, setBedsSingle] = useState(0); - const [bedsDouble, setBedsDouble] = useState(0); + const [roomHidden, setRoomHidden] = useState( + roomData ? roomData.hidden : false, + ); + + const { createRoomMutation, editRoomMutation } = useRoomMutations( + roomData ? roomData.id : -1, + roomName, + ); - const [roomHidden, setRoomHidden] = useState(false); + const isInEditMode = roomData !== undefined; + const title = isInEditMode ? "Edit room" : "Add room"; - const { createRoomMutation } = useRoomMutations(0, ""); + const resetFormState = () => { + setRoomName(""); + setRoomDescription(""); + setAvailableBedsSingle(0); + setAvailableBedsDouble(0); + setBedsSingle(0); + setBedsDouble(0); + setRoomHidden(false); + }; + + const onMutationSuccess = () => { + resetFormState(); + closeDialog(); + }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - createRoomMutation.mutate({ - name: roomName, - description: roomDescription, - available_beds_single: availableBedsSingle, - available_beds_double: availableBedsDouble, - beds_single: bedsSingle, - beds_double: bedsDouble, - hidden: roomHidden, - }); - onClose(); + if (isInEditMode) { + editRoomMutation.mutate( + { + id: roomData.id, + name: roomName, + description: roomDescription, + available_beds_single: availableBedsSingle, + available_beds_double: availableBedsDouble, + beds_single: bedsSingle, + beds_double: bedsDouble, + hidden: roomHidden, + }, + { onSuccess: onMutationSuccess }, + ); + } else { + createRoomMutation.mutate( + { + name: roomName, + description: roomDescription, + available_beds_single: availableBedsSingle, + available_beds_double: availableBedsDouble, + beds_single: bedsSingle, + beds_double: bedsDouble, + hidden: roomHidden, + }, + { onSuccess: onMutationSuccess }, + ); + } }; return ( - +
- Add room + {title} diff --git a/app/client/components/rooms/api/RoomMutations.tsx b/app/client/components/rooms/api/RoomMutations.tsx index fcac39cc..f452a3da 100644 --- a/app/client/components/rooms/api/RoomMutations.tsx +++ b/app/client/components/rooms/api/RoomMutations.tsx @@ -4,6 +4,7 @@ import { RoomApiData, RoomCreateApiData, RoomData, + RoomEditApiData, } from "@client/utils/roomData"; import { zosiaApi, zosiaApiRoutes } from "@client/utils/zosiaApi"; import { Context } from "@reactivated"; @@ -128,7 +129,7 @@ export const useRoomMutations = (roomId: number, roomName: string) => { }); const editRoomMutation = useMutation({ - mutationFn: async (roomData: RoomCreateApiData) => { + mutationFn: async (roomData: RoomEditApiData) => { return await zosiaApi.put( zosiaApiRoutes.room(roomId), roomData, diff --git a/app/client/components/rooms/card/RoomCard.tsx b/app/client/components/rooms/card/RoomCard.tsx index 3f839963..93036302 100644 --- a/app/client/components/rooms/card/RoomCard.tsx +++ b/app/client/components/rooms/card/RoomCard.tsx @@ -6,6 +6,7 @@ import { Context } from "@reactivated"; import clsx from "clsx"; import React, { useContext, useState } from "react"; import { RoomDeleteConfirmationDialog } from "../admin/RoomDeleteConfirmationDialog"; +import { RoomPropertiesDialog } from "../admin/RoomPropertiesDialog"; import { useRoomMutations } from "../api/RoomMutations"; import { JoinLockedRoomDialog } from "../JoinLockedRoomDialog"; import { RoomActions } from "./RoomActions"; @@ -19,20 +20,22 @@ interface RoomCardProps { } export const RoomCard = ({ - roomData: { + roomData, + userIsInSomeRoomAlready, + isAdmin, +}: RoomCardProps) => { + const { user } = useContext(Context); + + const { id, name, - description, members, - lock, availableBedsSingle, availableBedsDouble, + description, + lock, hidden, - }, - userIsInSomeRoomAlready, - isAdmin, -}: RoomCardProps) => { - const { user } = useContext(Context); + } = roomData; const { joinRoomMutation, @@ -48,6 +51,7 @@ export const RoomCard = ({ roomDeleteConfirmationDialogOpen, setRoomDeleteConfirmationDialogOpen, ] = useState(false); + const [roomEditDialogOpen, setRoomEditDialogOpen] = useState(false); const allPlaces = availableBedsSingle + availableBedsDouble * 2; const availablePlaces = allPlaces - members.length; @@ -69,7 +73,7 @@ export const RoomCard = ({ const lockRoom = () => lockRoomMutation.mutate(); const unlockRoom = () => unlockRoomMutation.mutate(); const deleteRoom = () => setRoomDeleteConfirmationDialogOpen(true); - const editRoom = () => alert("TODO: Edit room"); + const editRoom = () => setRoomEditDialogOpen(true); return (
setRoomDeleteConfirmationDialogOpen(false)} /> + {roomEditDialogOpen && ( + // We remount the dialog every time it's opened to reset the form state + setRoomEditDialogOpen(false)} + /> + )}
); }; diff --git a/app/client/utils/roomData.ts b/app/client/utils/roomData.ts index 394d8a97..b793501f 100644 --- a/app/client/utils/roomData.ts +++ b/app/client/utils/roomData.ts @@ -19,6 +19,8 @@ export interface RoomApiData { } | null; available_beds_single: number; available_beds_double: number; + beds_single: number; + beds_double: number; hidden: boolean; } @@ -33,6 +35,10 @@ export interface RoomCreateApiData { hidden: boolean; } +export interface RoomEditApiData extends RoomCreateApiData { + id: number; +} + interface RoomApiUserData { id: number; first_name: string; @@ -52,6 +58,8 @@ export interface RoomData { }; availableBedsSingle: number; availableBedsDouble: number; + bedsSingle: number; + bedsDouble: number; hidden: boolean; } @@ -84,6 +92,8 @@ export const convertRoomApiDataToRoomData = (room: RoomApiData): RoomData => { : undefined, availableBedsSingle: room.available_beds_single, availableBedsDouble: room.available_beds_double, + bedsSingle: room.beds_single, + bedsDouble: room.beds_double, hidden: room.hidden, }; }; @@ -117,6 +127,8 @@ export const createRoomDataFromTemplateProps = ( : undefined, availableBedsSingle: room.available_beds_single, availableBedsDouble: room.available_beds_double, + bedsSingle: room.beds_single, + bedsDouble: room.beds_double, hidden: room.hidden, })); }; diff --git a/app/server/rooms/templates.py b/app/server/rooms/templates.py index 32f89926..293edeea 100644 --- a/app/server/rooms/templates.py +++ b/app/server/rooms/templates.py @@ -12,6 +12,8 @@ "description", "available_beds_single", "available_beds_double", + "beds_single", + "beds_double", "hidden", "members.id", "members.first_name", From 0f1a8302036eb2689e51070211ced1f19851c241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Szczeci=C5=84ski?= Date: Sat, 23 Nov 2024 17:12:41 +0100 Subject: [PATCH 18/21] Fix RoomInfoPopover colors for hidden room --- app/client/components/rooms/card/RoomCard.tsx | 1 + app/client/components/rooms/card/RoomInfoPopover.tsx | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/client/components/rooms/card/RoomCard.tsx b/app/client/components/rooms/card/RoomCard.tsx index 93036302..267c6653 100644 --- a/app/client/components/rooms/card/RoomCard.tsx +++ b/app/client/components/rooms/card/RoomCard.tsx @@ -115,6 +115,7 @@ export const RoomCard = ({ availableBedsSingle={availableBedsSingle} availableBedsDouble={availableBedsDouble} description={description} + hidden={hidden} /> { return ( @@ -32,12 +35,17 @@ export const RoomInfoPopover = ({ transition className="absolute bottom-0 left-0 z-50 h-full w-full origin-bottom-left transition duration-200 ease-in-out data-[closed]:scale-0 data-[closed]:opacity-0" > -
+