Implemented calendar

Fixed bugs with reservations and rooms

Created Dockerfile
This commit is contained in:
Sviatoslav Tsariov Yurievich 2025-03-21 00:26:39 +03:00
parent ffa96152f0
commit dd8057fdf4
27 changed files with 610 additions and 267 deletions

7
Dockerfile Normal file
View File

@ -0,0 +1,7 @@
FROM python:latest
COPY build/ .
EXPOSE 8000
CMD python -m http.server 8000

View File

@ -98,7 +98,7 @@ body {
<script src="Talkpal.js"></script>
<script>
const GODOT_CONFIG = {"args":[],"canvasResizePolicy":2,"ensureCrossOriginIsolationHeaders":true,"executable":"Talkpal","experimentalVK":true,"fileSizes":{"Talkpal.pck":6385104,"Talkpal.wasm":43016933},"focusCanvas":true,"gdextensionLibs":[],"serviceWorker":"Talkpal.service.worker.js"};
const GODOT_CONFIG = {"args":[],"canvasResizePolicy":2,"ensureCrossOriginIsolationHeaders":true,"executable":"Talkpal","experimentalVK":true,"fileSizes":{"Talkpal.pck":6396960,"Talkpal.wasm":43016933},"focusCanvas":true,"gdextensionLibs":[],"serviceWorker":"Talkpal.service.worker.js"};
const GODOT_THREADS_ENABLED = false;
const engine = new Engine(GODOT_CONFIG);

Binary file not shown.

View File

@ -4,7 +4,7 @@
// Incrementing CACHE_VERSION will kick off the install event and force
// previously cached resources to be updated from the network.
/** @type {string} */
const CACHE_VERSION = '1742239479|2606992692';
const CACHE_VERSION = '1742472564|314520779';
/** @type {string} */
const CACHE_PREFIX = 'Talkpal-sw-cache-';
const CACHE_NAME = CACHE_PREFIX + CACHE_VERSION;

28
docker-compose.yaml Normal file
View File

@ -0,0 +1,28 @@
version: '3.6'
services:
talkpal-backend:
container_name: talkpal-backend
ports:
- 5004:5000
image: talkpal-backend
build: .
network_mode: host
volumes:
- "./src:/app/src"
talkpal-db:
image: mongo:4.2.8
container_name: mongo
ports:
- 5005:27017
environment:
- MONGO_INITDB_DATABASE=test
- MONGO_INITDB_ROOT_USERNAME=admin
- MONGO_INITDB_ROOT_PASSWORD=admin
volumes:
- ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d:ro
- mongodb:/data/db
- mongoconfig:/data/configdb
volumes:
mongodb:
mongoconfig:

View File

@ -22,12 +22,17 @@ const ScheduleUpdateInterval := 1.0 # 5.0
var _date_update_timer := 0.0
var _schedule_update_timer := 0.0
var _blocked_after_loading := false
var _block_reservation_creation := false
var _rooms : Array[RoomEntity] = []
var _reservations_list : Array[ReservationEntity] = []
func _process(delta):
_process_hour_size()
_process_date(delta)
_process_fonts()
_process_schedule(delta)
func _process_hour_size():
var hour_size = get_viewport_rect().size.y/15
@ -38,11 +43,11 @@ func _process_hour_size():
func _process_date(delta):
_date_update_timer += delta
if _date_update_timer >= DateUpdateInterval:
_date.text = _main.get_date()
update_date()
_date_update_timer = 0.0
var date = Time.get_date_dict_from_system()
_date.text = "%02d.%02d.%04d" % [date.day, date.month, date.year]
func update_date():
_date.text = _main.get_selected_date()
func _process_fonts():
_process_font_size(_date, 35)
@ -65,10 +70,14 @@ func _ready():
_connect_signals()
_remove_time_slots()
_remove_reservations()
_fill_with_slots()
await get_tree().create_timer(0.1, false).timeout
_load_rooms()
update_date()
func _connect_signals():
await _main.ready
var event_handler = _main.get_event_handler()
@ -77,6 +86,8 @@ func _connect_signals():
_room.pressed.connect(_on_room_button_pressed)
_rooms_menu.item_clicked.connect(_on_room_selected)
_date.pressed.connect(_on_date_button_pressed)
func _remove_time_slots():
for time_slot in _timeline.get_children():
time_slot.queue_free()
@ -91,14 +102,42 @@ func _remove_reservations():
for reservation in _reservations.get_children():
reservation.queue_free()
func _load_rooms():
var room_repo = _main.get_room_repo()
if room_repo.get_selected_room():
return
_rooms_menu.clear()
var rooms = await room_repo.list_rooms()
for room in rooms:
_rooms.append(room)
_rooms_menu.add_item(room.title)
if len(rooms) < 0:
print('Not enough rooms')
_rooms_menu.select(0)
_on_room_selected(0, '')
func _update_schedule(reservations=null):
if not _main.is_node_ready():
await _main.ready
var repo = _main.get_reservation_repo()
var reservation_repo = _main.get_reservation_repo()
if reservations == null:
reservations = await repo.list_reservations({"date": _main.get_date()})
var room = _main.get_selected_room()
reservations = await reservation_repo.list_reservations({
"date": _main.get_selected_date(),
"room_id": room.id if room else ''
})
if reservations_are_updated(reservations):
return
_reservations_list = reservations
_remove_reservations()
for reservation in reservations:
var start_time_hours = reservation.start_time.hours
@ -114,6 +153,25 @@ func _update_schedule(reservations=null):
_compose_reservation(start_time, reservation_time, \
reservation.title, reservation.color, reservation.id)
func reservations_are_updated(new_reservations: Array[ReservationEntity]) -> bool:
if _reservations_list.size() == 0:
return false
if _reservations_list.size() != new_reservations.size():
return false
for i in range(_reservations_list.size()):
var entity_a = _reservations_list[i]
var entity_b = new_reservations[i]
if !(entity_a is ReservationEntity) || !(entity_b is ReservationEntity):
return false
if !entity_a.is_identical(entity_b):
return false
return true
func _compose_reservation(start_time, duration_time, title, color, id):
if duration_time < MinimalMinutesToShowTitle:
title = ""
@ -131,7 +189,7 @@ func _compose_reservation(start_time, duration_time, title, color, id):
reservation.set_font(reservation.Fonts.MEDIUM)
func _input(event):
if not _main.get_current_page() == Main.Pages.Board:
if _blocked_after_loading or not _main.get_current_page() == Main.Pages.Board:
return
if event is InputEventScreenTouch or (event is InputEventMouseButton and event.pressed):
@ -178,6 +236,11 @@ func _time_is_before_current_time(time):
return time_in_minutes < current_time_in_minutes
func _block_board_click_for(seconds: float):
_blocked_after_loading = true
await get_tree().create_timer(seconds, false).timeout
_blocked_after_loading = false
func _on_room_button_pressed():
if _rooms_menu.visible:
_rooms_menu.hide()
@ -186,17 +249,23 @@ func _on_room_button_pressed():
_rooms_menu.show()
func _on_room_selected(idx, text):
var room_repo = _main.get_room_repo()
room_repo.set_selected_room(_rooms[idx])
_block_reservation_creation = true
_room.text = text
_room.text = _rooms[idx].title
_rooms_menu.hide()
update()
func _on_date_button_pressed():
print("emit change date signal")
_main.load_page(Main.Pages.CalendarSetting)
func _on_reservations_updated(reservations):
await get_tree().create_timer(0.1, false).timeout
update()
func update():
_block_board_click_for(0.25)
_ready()
_update_schedule()

View File

@ -3,12 +3,19 @@ extends VBoxContainer
const WorkingDayStart = 8
const WorkingDayEnd = 20
@onready var _main: Main = get_tree().get_current_scene()
@onready var _bg := $Background
func _process(delta):
_process_current_time()
func _process_current_time():
if _main.is_current_date_selected():
show()
else:
hide()
return
var time = Time.get_time_dict_from_system()
if time.hour < WorkingDayStart or time.hour > WorkingDayEnd:

View File

@ -19,8 +19,14 @@ func _ready():
func _connect_signals():
_items.item_clicked.connect(_on_item_clicked)
func set_items(items: Array):
pass
func add_item(text: String):
_items.add_item(text)
func clear():
_items.clear()
func select(idx):
_items.select(idx)
func _on_item_clicked(index, at_position, mouse_button_index):
var text = _items.get_item_text(index)

View File

@ -0,0 +1,146 @@
extends Control
signal date_selected(date: Calendar.DateObj)
@onready var _main: Main = get_tree().get_current_scene()
@onready var _month_label: Label = $VBoxContainer/HBoxContainer/MonthLabel
@onready var _days_grid: GridContainer = $VBoxContainer/DaysGrid
@onready var _prev_month_button: Button = $VBoxContainer/HBoxContainer/PrevMonthButton
@onready var _next_month_button: Button = $VBoxContainer/HBoxContainer/NextMonthButton
@onready var _vertical_box: VBoxContainer = $VBoxContainer
@onready var _horizontal_box: HBoxContainer = $VBoxContainer/HBoxContainer
var cal: Calendar = Calendar.new()
var current_date: Calendar.DateObj = Calendar.DateObj.today()
var selected_date_label: Label
var weekdays_formatted: Array[String]
var months_formatted: Array[String]
func _process(delta):
_process_grid_size()
_process_font_size(_month_label, 32)
_process_font_size(_prev_month_button, 32)
_process_font_size(_next_month_button, 32)
func _process_grid_size():
for day_label in _days_grid.get_children():
var font_size = day_label.label_settings.font_size
var new_font_size = get_viewport_rect().size.y/32
if font_size != new_font_size:
day_label.label_settings.font_size = new_font_size
for selection in day_label.get_children():
var selection_size = selection.size.x
var new_selection_size = get_viewport_rect().size.y/32
if selection_size != new_selection_size:
selection.size.x = new_selection_size
selection.size.y = new_selection_size
func _process_font_size(obj, k):
var font_size = obj.get_theme_default_font_size()
var new_font_size = get_viewport_rect().size.y/k
if font_size != new_font_size:
obj.add_theme_font_size_override("font_size", new_font_size)
func _ready() -> void:
cal.set_calendar_locale("res://addons/calendar_library/demo/calendar_locale_RU.tres")
cal.set_first_weekday(Time.WEEKDAY_MONDAY)
cal.week_number_system = Calendar.WeekNumberSystem.WEEK_NUMBER_FOUR_DAY
weekdays_formatted = cal.get_weekdays_formatted(Calendar.WeekdayFormat.WEEKDAY_FORMAT_SHORT)
months_formatted = cal.get_months_formatted(Calendar.MonthFormat.MONTH_FORMAT_FULL)
_prev_month_button.pressed.connect(_on_prev_month_pressed)
_next_month_button.pressed.connect(_on_next_month_pressed)
update_calendar()
func update_calendar() -> void:
for child in _days_grid.get_children():
child.queue_free()
_month_label.text = "%s %d" % [months_formatted[current_date.month - 1], current_date.year]
var month_calendar = cal.get_calendar_month(current_date.year, current_date.month, true)
var today = Calendar.DateObj.today()
for weekday in weekdays_formatted:
var label = CalendarLabel.new(weekday)
_days_grid.add_child(label)
for week in month_calendar:
for date_obj in week:
var day_label = create_day_label(date_obj, today)
_days_grid.add_child(day_label)
func create_day_label(date_obj: Calendar.DateObj, today: Calendar.DateObj) -> CalendarLabel:
var label = CalendarLabel.new(str(date_obj.day), true)
if date_obj.month != current_date.month:
label.label_settings.font_color = Color("#414853")
else:
if date_obj.is_equal(today):
label.label_settings.font_color = Color("#70bafa")
else:
label.label_settings.font_color = Color("#cdced2")
if date_obj.is_equal(current_date):
set_selected_state(label)
selected_date_label = label
label.pressed.connect(_on_date_pressed.bind(date_obj, label))
return label
func set_selected_state(label: Label) -> void:
if selected_date_label and selected_date_label.get_child_count() > 0:
selected_date_label.get_child(0).queue_free()
var selection = ColorRect.new()
selection.size = Vector2(60, 60)
selection.color = Color("#414853")
selection.show_behind_parent = true
label.add_child(selection)
selected_date_label = label
func _on_date_pressed(date: Calendar.DateObj, label: Label) -> void:
current_date = date
set_selected_state(label)
date_selected.emit(date)
update_calendar()
_main.go_to_previous_page(true)
func _on_prev_month_pressed() -> void:
current_date.add_months(-1)
update_calendar()
func _on_next_month_pressed() -> void:
current_date.add_months(1)
update_calendar()
func update():
pass
class CalendarLabel:
extends Label
signal pressed()
var clickable: bool = false
func _init(p_text: String, p_clickable: bool = false):
text = p_text
horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
mouse_filter = MOUSE_FILTER_PASS if p_text.is_empty() else MOUSE_FILTER_STOP
var settings = LabelSettings.new()
settings.font_size = 46
settings.font_color = Color.WHITE
label_settings = settings
if p_clickable and not p_text.is_empty():
clickable = true
mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
func _gui_input(event: InputEvent) -> void:
if clickable and event is InputEventMouseButton:
if event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
pressed.emit()

View File

@ -0,0 +1,53 @@
[gd_scene load_steps=3 format=3 uid="uid://bc4tcq608v5rx"]
[ext_resource type="Script" path="res://scenes/common/calendar/calendar_setting.gd" id="1_k60tp"]
[ext_resource type="Theme" uid="uid://cmhwbyqu6nh38" path="res://assets/themes/big.tres" id="2_dde4w"]
[node name="Control" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_k60tp")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -92.5
offset_top = -38.0
offset_right = 92.5
offset_bottom = 38.0
grow_horizontal = 2
grow_vertical = 2
theme = ExtResource("2_dde4w")
theme_override_constants/separation = 10
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
theme_override_constants/separation = 10
alignment = 1
[node name="PrevMonthButton" type="Button" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
text = " < "
[node name="MonthLabel" type="Label" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
[node name="NextMonthButton" type="Button" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
text = " > "
[node name="DaysGrid" type="GridContainer" parent="VBoxContainer"]
layout_mode = 2
theme = ExtResource("2_dde4w")
theme_override_constants/h_separation = 15
theme_override_constants/v_separation = 15
columns = 7

View File

@ -14,7 +14,7 @@ script = ExtResource("1_2wxyg")
[node name="TimeEdit" type="LineEdit" parent="."]
visible = false
layout_mode = 2
text = "21:41 "
text = "15:04 "
placeholder_text = "hh:mm (a/p)m"
script = ExtResource("2_7d4ae")
current_time = true

View File

@ -15,9 +15,9 @@ enum State {
@export var number_flash_time := 0.5
@onready var _main: Main = get_tree().get_current_scene()
@onready var _hours_label = $VerticalBar/Time/Hours
@onready var _minutes_label = $VerticalBar/Time/Minutes
@onready var _time = $VerticalBar/Time
@onready var _hours_label = $VerticalBar/Control/Time/Hours
@onready var _minutes_label = $VerticalBar/Control/Time/Minutes
@onready var _time = $VerticalBar/Control/Time
@onready var _face = $VerticalBar/Circle/Face
@onready var _arrow = $VerticalBar/Circle/Clock/Arrow
@onready var _numbers = $VerticalBar/Circle/Clock/Numbers
@ -28,6 +28,8 @@ var _minutes : int
var _state := State.SettingHours
var _time_after_last_flash := 0.0
var _blocked := false
signal time_is_set(hours: int, minutes: int)
func _ready():
@ -85,7 +87,7 @@ func _clicked_on_time(pos: Vector2):
(pos.y > time_position.y and pos.y < _time.size.y + time_position.y)
func _input(event):
if not _main.get_current_page() == Main.Pages.TimeSetting:
if _blocked or not _main.get_current_page() == Main.Pages.TimeSetting:
return
if (event is InputEventScreenTouch and event.pressed) or \
@ -171,5 +173,11 @@ func _start_minutes_setting():
_fill_with_minutes()
_update_time()
func _block_click_for(seconds: float):
_blocked = true
await get_tree().create_timer(seconds, false).timeout
_blocked = false
func update():
_ready()
_block_click_for(0.25)

View File

@ -39,20 +39,27 @@ offset_bottom = 20.0
grow_horizontal = 2
grow_vertical = 2
[node name="Time" type="HBoxContainer" parent="VerticalBar"]
[node name="Control" type="Control" parent="VerticalBar"]
custom_minimum_size = Vector2(0, 30)
layout_mode = 2
[node name="Time" type="HBoxContainer" parent="VerticalBar/Control"]
layout_mode = 2
offset_top = 4.0
offset_right = 1024.0
offset_bottom = 141.0
theme = ExtResource("2_84auh")
alignment = 1
[node name="Hours" type="Label" parent="VerticalBar/Time"]
[node name="Hours" type="Label" parent="VerticalBar/Control/Time"]
layout_mode = 2
text = "_"
[node name="_" type="Label" parent="VerticalBar/Time"]
[node name="_" type="Label" parent="VerticalBar/Control/Time"]
layout_mode = 2
text = ":"
[node name="Minutes" type="Label" parent="VerticalBar/Time"]
[node name="Minutes" type="Label" parent="VerticalBar/Control/Time"]
layout_mode = 2
text = "00"

View File

@ -5,7 +5,8 @@ enum Pages {
Board,
ReservationCreation,
ReservationEdit,
TimeSetting
TimeSetting,
CalendarSetting
}
func get_current_page():
@ -26,5 +27,14 @@ func get_event_handler() -> EventHandler:
func get_reservation_service() -> ReservationService:
return ReservationService.new()
func get_date() -> String:
func get_room_repo() -> AbstractRoomRepo:
return AbstractRoomRepo.new()
func get_selected_date() -> String:
return ""
func get_selected_room() -> RoomEntity:
return RoomEntity.new()
func is_current_date_selected() -> bool:
return true

View File

@ -17,6 +17,7 @@ enum Status {
}
@onready var _reservation_repo : AbstractReservationRepo = $Repos/Reservation
@onready var _room_repo : AbstractRoomRepo = $Repos/Room
@onready var _event_handler : EventHandler = $Repos/EventHandler
@onready var _reservation_service : ReservationService = $Services/ReservationService
@onready var _keyboard = $Keyboard/OnscreenKeyboard
@ -30,7 +31,8 @@ enum Status {
Pages.Board: $Left/Pages/Board,
Pages.ReservationCreation: $Left/Pages/ReservationCreation,
Pages.ReservationEdit: $Left/Pages/ReservationEdit,
Pages.TimeSetting: $Left/Pages/TimeSetting
Pages.TimeSetting: $Left/Pages/TimeSetting,
Pages.CalendarSetting: $Left/Pages/CalendarSetting
}
@onready var _time_status_indent := $Left/TimeStatusContainer/Indent
@onready var _time_label := $Left/TimeStatusContainer/TimeLabel
@ -46,9 +48,12 @@ enum Status {
var _current_page := Pages.Board
var _previous_page : Pages
var _status : Status
var _status_check_timer := 0.0
var _selected_date := _get_current_date()
func _ready():
initialize_signals()
@ -60,6 +65,8 @@ func initialize_signals():
_45_min_button.pressed.connect(_on_45_min_button_pressed)
_1_hour_button.pressed.connect(_on_1_hour_button_pressed)
_pages[Pages.CalendarSetting].date_selected.connect(_on_date_selected)
func _process(delta):
_process_time_status_indent()
_process_status(delta)
@ -101,7 +108,11 @@ func _update_status(reservations=null):
var current_time_in_minutes = time.hour*60 + time.minute
if reservations == null:
reservations = await _reservation_repo.list_reservations({"date": get_date()})
var room = get_selected_room()
reservations = await _reservation_repo.list_reservations({
"date": _get_current_date(),
"room_id": room.id if room else ''
})
for reservation in reservations:
var start_time_in_minutes = reservation.start_time.hours*60 + reservation.start_time.minutes
@ -174,6 +185,9 @@ func go_to_previous_page(with_update=true):
func get_reservation_repo() -> AbstractReservationRepo:
return _reservation_repo
func get_room_repo() -> AbstractRoomRepo:
return _room_repo
func get_event_handler() -> EventHandler:
return _event_handler
@ -205,7 +219,7 @@ func _create_default_reservation(minutes_of_reservation):
dto.start_time = {"hours": datetime.hour, "minutes": datetime.minute}
dto.finish_time = {"hours": finish_time_hours, "minutes": finish_time_minutes}
dto.creator = ""
dto.room_id = "" # TODO: make it listbox
dto.room_id = _room_repo.get_selected_room().id
dto.description = ""
dto.color = randi_range(1, 3)
@ -234,9 +248,23 @@ func _on_45_min_button_pressed():
func _on_1_hour_button_pressed():
_create_default_reservation(60)
func get_date() -> String:
func get_selected_date() -> String:
return _selected_date
func _get_current_date() -> String:
var date = Time.get_date_dict_from_system()
return "%02d.%02d.%04d" % [date.day, date.month, date.year]
func is_current_date_selected() -> bool:
return get_selected_date() == _get_current_date()
func get_selected_room() -> RoomEntity:
var room_repo = get_room_repo()
return room_repo.get_selected_room()
func hide_keyboard():
_keyboard.hide()
func _on_date_selected(date: Calendar.DateObj):
_selected_date = "%02d.%02d.%04d" % [date.day, date.month, date.year]
_pages[Pages.Board].update_date()

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=16 format=3 uid="uid://bkrvh8vjpgqot"]
[gd_scene load_steps=18 format=3 uid="uid://bkrvh8vjpgqot"]
[ext_resource type="Script" path="res://scenes/main/main_tablet.gd" id="1_fr6s5"]
[ext_resource type="PackedScene" uid="uid://c431r28ef5edp" path="res://scenes/board/board.tscn" id="2_n47h4"]
@ -6,12 +6,14 @@
[ext_resource type="PackedScene" uid="uid://cu6e3hfdorwcg" path="res://scenes/reservation/reservation_edit.tscn" id="4_hyj5n"]
[ext_resource type="PackedScene" uid="uid://cxs8xe5w32jo4" path="res://scenes/common/time/time_setting.tscn" id="4_wyf5q"]
[ext_resource type="Theme" uid="uid://byopik87nb8vv" path="res://assets/themes/status_font.tres" id="5_atujq"]
[ext_resource type="PackedScene" uid="uid://bc4tcq608v5rx" path="res://scenes/common/calendar/calendar_setting.tscn" id="6_c0c0t"]
[ext_resource type="Theme" uid="uid://yn1nbokvmv6n" path="res://assets/themes/small.tres" id="6_nde4h"]
[ext_resource type="Theme" uid="uid://b8tbd62jtmgdx" path="res://assets/themes/instant_button_font.tres" id="8_bmn8p"]
[ext_resource type="Theme" uid="uid://cmhwbyqu6nh38" path="res://assets/themes/big.tres" id="9_wpf8g"]
[ext_resource type="Script" path="res://src/infra/repos/backend/reservation_http.gd" id="10_v7sup"]
[ext_resource type="Script" path="res://src/domain/services/reservation.gd" id="11_5xy2x"]
[ext_resource type="Script" path="res://src/infra/repos/backend/event_handler_ws.gd" id="11_de30t"]
[ext_resource type="Script" path="res://src/infra/repos/backend/room_http.gd" id="12_efd55"]
[ext_resource type="Script" path="res://addons/onscreenkeyboard/onscreen_keyboard.gd" id="12_mc4hf"]
[ext_resource type="PackedScene" uid="uid://dt03kci5qbkg3" path="res://scenes/common/dialog_box.tscn" id="14_cvh6c"]
@ -67,20 +69,25 @@ visible = false
layout_mode = 2
size_flags_horizontal = 3
[node name="CalendarSetting" parent="Left/Pages" instance=ExtResource("6_c0c0t")]
visible = false
layout_mode = 2
size_flags_horizontal = 3
[node name="TimeStatusContainer" type="VBoxContainer" parent="Left"]
layout_mode = 2
size_flags_horizontal = 10
size_flags_vertical = 0
[node name="Indent" type="BoxContainer" parent="Left/TimeStatusContainer"]
custom_minimum_size = Vector2(0, 195)
custom_minimum_size = Vector2(0, 0.666667)
layout_mode = 2
size_flags_vertical = 3
[node name="TimeLabel" type="Label" parent="Left/TimeStatusContainer"]
layout_mode = 2
theme = ExtResource("5_atujq")
text = "11:31"
text = "12:42"
horizontal_alignment = 1
[node name="StatusLabel" type="Label" parent="Left/TimeStatusContainer"]
@ -145,7 +152,7 @@ grow_vertical = 0
[node name="CreateReservationButton" type="Button" parent="RightBottom"]
layout_mode = 2
theme = ExtResource("9_wpf8g")
theme_override_font_sizes/font_size = 16
theme_override_font_sizes/font_size = 0
text = " Забронировать "
[node name="Repos" type="Node" parent="."]
@ -156,6 +163,9 @@ script = ExtResource("10_v7sup")
[node name="EventHandler" type="Node" parent="Repos"]
script = ExtResource("11_de30t")
[node name="Room" type="Node" parent="Repos"]
script = ExtResource("12_efd55")
[node name="Keyboard" type="Control" parent="."]
visible = false
layout_mode = 2

View File

@ -81,17 +81,25 @@ func _load_info():
_start_time_field.set_value(reservation.start_time)
_finish_time_field.set_value(reservation.finish_time)
func _load_room():
_room_field.set_value(_main.get_selected_room().title)
func _get_current_room_id():
var room_repo = _main.get_room_repo()
var room = room_repo.get_selected_room()
return room.id if room != null else null
func _create_reservation():
if not await _fields_are_correct():
return
var dto = CreateReservationDTO.new()
dto.title = _title_field.get_value()
dto.date = _main.get_date()
dto.date = _main.get_selected_date()
dto.start_time = _start_time_field.get_value()
dto.finish_time = _finish_time_field.get_value()
dto.creator = _creator_field.get_value()
dto.room_id = _room_field.get_value() # TODO: make it listbox
dto.room_id = _get_current_room_id()
dto.description = _description_field.get_value()
dto.color = randi_range(1, 3)
@ -100,7 +108,7 @@ func _create_reservation():
dto.queue_free()
_main.load_page(Main.Pages.Board)
_main.load_page(Main.Pages.Board, true)
clean()
func _update_reservation():
@ -113,7 +121,7 @@ func _update_reservation():
dto.start_time = _start_time_field.get_value()
dto.finish_time = _finish_time_field.get_value()
dto.creator = _creator_field.get_value()
dto.room_id = _room_field.get_value() # TODO: make it listbox
dto.room_id = _get_current_room_id()
dto.description = _description_field.get_value()
dto.color = randi_range(1, 3)
@ -124,7 +132,7 @@ func _update_reservation():
dto.queue_free()
_main.load_page(Main.Pages.Board)
_main.load_page(Main.Pages.Board, true)
clean()
func _delete_reservation():
@ -132,7 +140,7 @@ func _delete_reservation():
var reservation_id = repo.get_selected_reservation_id()
repo.cancel_reservation(reservation_id)
_main.load_page(Main.Pages.Board)
_main.load_page(Main.Pages.Board, true)
clean()
func _fields_are_correct():
@ -191,6 +199,8 @@ func update():
if type == Types.Edit:
_load_info()
_load_room()
func clean():
_title_field.clean()
_start_time_field.clean()

View File

@ -1,20 +1,6 @@
extends Node
class_name ReservationEntity
class_name RoomEntity
var id: String
@export var title: String
@export var description: String
@export var room_id: String
@export var creator: String
@export var date: String
@export var start_time: Dictionary # TODO: get rid of dictionaries if can
@export var finish_time: Dictionary
@export var color: int

View File

@ -0,0 +1,40 @@
extends Node
class_name ReservationEntity
var id: String
@export var title: String
@export var description: String
@export var room_id: String
@export var creator: String
@export var date: String
@export var start_time: Dictionary # TODO: get rid of dictionaries if can
@export var finish_time: Dictionary
@export var color: int
func is_identical(other: ReservationEntity) -> bool:
if !(other is ReservationEntity):
return false
return (
id == other.id &&
title == other.title &&
description == other.description &&
room_id == other.room_id &&
creator == other.creator &&
date == other.date &&
_compare_time(start_time, other.start_time) &&
_compare_time(finish_time, other.finish_time) &&
color == other.color
)
static func _compare_time(a: Dictionary, b: Dictionary) -> bool:
return a.get("hours", -1) == b.get("hours", -2) and \
a.get("minutes", -1) == b.get("minutes", -2)

View File

@ -4,9 +4,14 @@ class_name ReservationService
@onready var _main: Main = get_tree().get_current_scene()
func is_time_busy(new_start_time_minutes, new_finish_time_minutes):
var repo = _main.get_reservation_repo()
var reservations = await repo.list_reservations({"date": _main.get_date()})
var selected_reservation_id = repo.get_selected_reservation_id()
var selected_room = _main.get_selected_room()
var reservation_repo = _main.get_reservation_repo()
var reservations = await reservation_repo.list_reservations({
"date": _main.get_selected_date(),
"room_id": selected_room.id
})
var selected_reservation_id = reservation_repo.get_selected_reservation_id()
for reservation in reservations:
if reservation.id == selected_reservation_id:

View File

@ -20,8 +20,8 @@ func change_reservation(reservation_id, dto: UpdateReservationDTO):
func get_reservation(reservation_id):
pass
func list_reservations(filters: Dictionary = {}):
pass
func list_reservations(filters: Dictionary = {}) -> Array[ReservationEntity]:
return []
func set_selected_reservation_id(value):
_selected_reservation_id = value

View File

@ -5,5 +5,13 @@ class_name AbstractRoomRepo
signal connected
signal not_connected
func list_rooms():
pass
var _selected_room = null
func list_rooms() -> Array[RoomEntity]:
return []
func set_selected_room(value):
_selected_room = value
func get_selected_room():
return _selected_room

View File

@ -2,7 +2,7 @@
extends AbstractReservationRepo
class_name ReservationRepoHTTP
const BASE_URL = "http://192.168.0.147:5000"
const BASE_URL = "http://192.168.1.178:5000"
const RESERVATION_ENDPOINT = "/reservation/"
const HEALTH_ENDPOINT = "/health/"
@ -10,6 +10,11 @@ signal request_failed(error_message: String)
var _jwt_token: String = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MDA0MDQzNywianRpIjoiOTM4NTUyMjMtMjhiNC00OWVhLWI3ZjUtZmYxMTg4YzI1Mjg2IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6InRlc3RfdXNlciIsIm5iZiI6MTc0MDA0MDQzNywiY3NyZiI6ImMwYzI2MTU0LTdkNjItNGYyZi04ZjBhLWI0MjA0ODJlMmEzZCIsImV4cCI6MTc0MDA0MTMzN30.esPRTXNxrtziuOc2dUsc9XqbedErjcyvPlL3aUZIaaA"
var _timed_out := false
var _http_client := HTTPTalkpalClient.new(BASE_URL)
func _init():
add_child(_http_client)
_http_client.request_failed.connect(_on_request_failed)
func _ready():
if await _backend_is_accessible():
@ -18,7 +23,7 @@ func _ready():
not_connected.emit()
func _backend_is_accessible():
var result = await _make_request(HEALTH_ENDPOINT, HTTPClient.METHOD_GET)
var result = await _http_client.request(HEALTH_ENDPOINT, HTTPClient.METHOD_GET)
if not result:
push_error("Server initialization failed")
@ -31,26 +36,26 @@ func _backend_is_accessible():
#region Public Methods
func create_reservation(dto: CreateReservationDTO) -> void:
_make_request(RESERVATION_ENDPOINT, HTTPClient.METHOD_POST, _dto_to_json(dto))
_http_client.request(RESERVATION_ENDPOINT, HTTPClient.METHOD_POST, _dto_to_json(dto))
func cancel_reservation(reservation_id: String) -> void:
_make_request(RESERVATION_ENDPOINT + "%s" % reservation_id, HTTPClient.METHOD_DELETE)
_http_client.request(RESERVATION_ENDPOINT + "%s" % reservation_id, HTTPClient.METHOD_DELETE)
func change_reservation(reservation_id: String, dto: UpdateReservationDTO) -> void:
_make_request(RESERVATION_ENDPOINT + "%s" % reservation_id, HTTPClient.METHOD_PUT, _dto_to_json(dto))
_http_client.request(RESERVATION_ENDPOINT + "%s" % reservation_id, HTTPClient.METHOD_PUT, _dto_to_json(dto))
func get_reservation(reservation_id: String) -> ReservationEntity:
var result = await _make_request(RESERVATION_ENDPOINT + "%s" % reservation_id, HTTPClient.METHOD_GET)
var result = await _http_client.request(RESERVATION_ENDPOINT + "%s" % reservation_id, HTTPClient.METHOD_GET)
return _parse_reservation_entity(result) if result is Dictionary else null
func list_reservations(filters: Dictionary = {}) -> Array:
func list_reservations(filters: Dictionary = {}) -> Array[ReservationEntity]:
var query = "?"
for key in filters:
query += "%s=%s&" % [key, filters[key]]
query = query.rstrip("&") if filters else ""
var endpoint = RESERVATION_ENDPOINT + query
var result = await _make_request(endpoint, HTTPClient.METHOD_GET)
var result = await _http_client.request(endpoint, HTTPClient.METHOD_GET)
var reservations = _parse_reservation_list(result) if result is Array else []
_set_current_reservation_id_from_reservations(reservations)
@ -207,3 +212,6 @@ func _compare_reservations_by_start_time(a: ReservationEntity, b: ReservationEnt
var time_b = b.start_time.get("hours", 0) * 60 + b.start_time.get("minutes", 0)
return time_a < time_b
#endregion
func _on_request_failed(error_message: String):
request_failed.emit(error_message)

View File

@ -1,209 +1,36 @@
@tool
extends AbstractRoomRepo
class_name RoomRepoHTTP
const BASE_URL = "http://192.168.1.178:5000"
const RESERVATION_ENDPOINT = "/reservation/"
const HEALTH_ENDPOINT = "/health/"
signal rooms_loaded(rooms: Array[RoomEntity])
signal request_failed(error_message: String)
var _jwt_token: String = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MDA0MDQzNywianRpIjoiOTM4NTUyMjMtMjhiNC00OWVhLWI3ZjUtZmYxMTg4YzI1Mjg2IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6InRlc3RfdXNlciIsIm5iZiI6MTc0MDA0MDQzNywiY3NyZiI6ImMwYzI2MTU0LTdkNjItNGYyZi04ZjBhLWI0MjA0ODJlMmEzZCIsImV4cCI6MTc0MDA0MTMzN30.esPRTXNxrtziuOc2dUsc9XqbedErjcyvPlL3aUZIaaA"
var _timed_out := false
const ROOM_ENDPOINT = "/room/"
func _ready():
if await _backend_is_accessible():
connected.emit()
var _http_client := HTTPTalkpalClient.new(BASE_URL)
func _init():
add_child(_http_client)
_http_client.request_failed.connect(_on_request_failed)
func list_rooms() -> Array[RoomEntity]:
var result = await _http_client.request(ROOM_ENDPOINT, HTTPClient.METHOD_GET)
if result is Array:
var rooms = _parse_room_list(result)
return rooms
else:
not_connected.emit()
emit_signal("request_failed", "Failed to load rooms")
return []
func _backend_is_accessible():
var result = await _make_request(HEALTH_ENDPOINT, HTTPClient.METHOD_GET)
if not result:
push_error("Server initialization failed")
return false
if result.has("error"):
push_error("Server initialization failed: " + result.error)
return false
return true
#region Public Methods
func create_reservation(dto: CreateReservationDTO) -> void:
_make_request(RESERVATION_ENDPOINT, HTTPClient.METHOD_POST, _dto_to_json(dto))
func cancel_reservation(reservation_id: String) -> void:
_make_request(RESERVATION_ENDPOINT + "%s" % reservation_id, HTTPClient.METHOD_DELETE)
func change_reservation(reservation_id: String, dto: UpdateReservationDTO) -> void:
_make_request(RESERVATION_ENDPOINT + "%s" % reservation_id, HTTPClient.METHOD_PUT, _dto_to_json(dto))
func get_reservation(reservation_id: String) -> ReservationEntity:
var result = await _make_request(RESERVATION_ENDPOINT + "%s" % reservation_id, HTTPClient.METHOD_GET)
return _parse_reservation_entity(result) if result is Dictionary else null
func list_reservations(filters: Dictionary = {}) -> Array:
var query = "?"
for key in filters:
query += "%s=%s&" % [key, filters[key]]
query = query.rstrip("&") if filters else ""
var endpoint = RESERVATION_ENDPOINT + query
var result = await _make_request(endpoint, HTTPClient.METHOD_GET)
var reservations = _parse_reservation_list(result) if result is Array else []
_set_current_reservation_id_from_reservations(reservations)
return reservations
func _set_current_reservation_id_from_reservations(reservations):
var time = Time.get_time_dict_from_system()
var current_time_in_minutes = time.hour*60 + time.minute
for reservation in reservations:
var start_time_in_minutes = reservation.start_time.hours*60 + reservation.start_time.minutes
var finish_time_in_minutes = reservation.finish_time.hours*60 + reservation.finish_time.minutes
if current_time_in_minutes >= start_time_in_minutes \
and current_time_in_minutes < finish_time_in_minutes:
_current_reservation = reservation
return
_current_reservation = null
func set_selected_reservation_id(value):
_selected_reservation_id = value
func get_selected_reservation_id():
return _selected_reservation_id
func set_jwt_token(token: String):
_jwt_token = token
#endregion
#region Private Methods
func _get_headers() -> PackedStringArray:
return PackedStringArray([
"Content-Type: application/json",
"Authorization: Bearer %s" % _jwt_token
])
func _dto_to_json(dto) -> String:
var dict = {}
match typeof(dto):
TYPE_OBJECT when dto is CreateReservationDTO:
dict = {
"title": dto.title,
"description": dto.description,
"room_id": dto.room_id,
"date": dto.date,
"start_time": dto.start_time,
"finish_time": dto.finish_time,
"color": dto.color
}
TYPE_OBJECT when dto is UpdateReservationDTO:
if dto.title: dict["title"] = dto.title
if dto.description: dict["description"] = dto.description
if dto.room_id: dict["room_id"] = dto.room_id
if dto.date: dict["date"] = dto.date
if dto.start_time: dict["start_time"] = dto.start_time
if dto.finish_time: dict["finish_time"] = dto.finish_time
if dto.color: dict["color"] = dto.color
return JSON.stringify(dict)
func _time_dto_to_dict(time_dto: TimeDTO) -> Dictionary:
return {"hours": time_dto.hours, "minutes": time_dto.minutes}
func _make_request(endpoint: String, method: int, body: String = "") -> Variant:
var request = HTTPRequest.new()
add_child(request)
var url = BASE_URL + endpoint
var error = request.request(url, _get_headers(), method, body)
if error != OK:
request.queue_free()
emit_signal("request_failed", "Request creation failed: %d" % error)
return null
var response = await _wait_for_request_or_timeout(request)
request.queue_free()
var result = response[0]
var response_code = response[1]
var response_body = response[3]
if result != HTTPRequest.RESULT_SUCCESS:
emit_signal("request_failed", "HTTP request failed: %d" % result)
return null
var json = JSON.new()
var parse_error = json.parse(response_body.get_string_from_utf8())
if parse_error != OK:
emit_signal("request_failed", "JSON parse error: %d" % parse_error)
return null
var response_data = json.get_data()
if response_code >= 400:
emit_signal("request_failed", response_data.get("error", "Unknown error"))
return null
return response_data
func _wait_for_request_or_timeout(request: HTTPRequest) -> Array:
var timer = Timer.new()
add_child(timer)
timer.wait_time = 5.0
timer.one_shot = true
timer.timeout.connect(func():
_timed_out = true
if request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED:
request.cancel_request()
)
timer.start()
while true:
if _timed_out:
_timed_out = false
timer.queue_free()
return [HTTPRequest.RESULT_REQUEST_FAILED, 408, [], PackedByteArray()]
if request.get_http_client_status() == HTTPClient.STATUS_REQUESTING:
break
await get_tree().process_frame
timer.queue_free()
return await request.request_completed
func _parse_reservation_entity(data: Dictionary) -> ReservationEntity:
var entity = ReservationEntity.new()
entity.id = data.get("id", "")
entity.title = data.get("title", "")
entity.description = data.get("description", "")
entity.room_id = data.get("room_id", "")
entity.creator = data.get("creator", "")
entity.date = data.get("date", "")
entity.color = data.get("color", 0)
entity.start_time = data.get("start_time", {})
entity.finish_time = data.get("finish_time", {})
return entity
func _parse_reservation_list(data: Array) -> Array[ReservationEntity]:
var entities: Array[ReservationEntity] = []
func _parse_room_list(data: Array) -> Array[RoomEntity]:
var rooms: Array[RoomEntity] = []
for item in data:
entities.append(_parse_reservation_entity(item))
var room = RoomEntity.new()
room.id = item.get("id")
room.title = item.get("title")
rooms.append(room)
return rooms
entities.sort_custom(_compare_reservations_by_start_time)
return entities
func _compare_reservations_by_start_time(a: ReservationEntity, b: ReservationEntity) -> int:
var time_a = a.start_time.get("hours", 0) * 60 + a.start_time.get("minutes", 0)
var time_b = b.start_time.get("hours", 0) * 60 + b.start_time.get("minutes", 0)
return time_a < time_b
#endregion
func _on_request_failed(error_message: String):
emit_signal("request_failed", error_message)

View File

@ -41,7 +41,7 @@ func change_reservation(reservation_id, dto: UpdateReservationDTO):
func get_reservation(reservation_id):
return _reservations[reservation_id]
func list_reservations(filters: Dictionary = {}):
func list_reservations(filters: Dictionary = {}) -> Array[ReservationEntity]:
var reservations = []
for key in _reservations:

81
src/libs/http_client.gd Normal file
View File

@ -0,0 +1,81 @@
class_name HTTPTalkpalClient
extends Node
signal request_failed(error_message: String)
var _base_url: String
var _jwt_token: String = ""
var _timed_out := false
func _init(_base_url: String):
self._base_url = _base_url
func set__jwt_token(token: String):
_jwt_token = token
func request(endpoint: String, method: int, body: String = "") -> Variant:
var request = HTTPRequest.new()
add_child(request)
var url = _base_url + endpoint
var headers = PackedStringArray([
"Content-Type: application/json",
"Authorization: Bearer %s" % _jwt_token
])
var error = request.request(url, headers, method, body)
if error != OK:
request.queue_free()
emit_signal("request_failed", "Request creation failed: %d" % error)
print("request_failed", "Request creation failed: %d" % error)
return null
var response = await _wait_for_request_or_timeout(request)
request.queue_free()
var result = response[0]
var response_code = response[1]
var response_body = response[3]
if result != HTTPRequest.RESULT_SUCCESS:
emit_signal("request_failed", "HTTP request failed: %d" % result)
return null
var json = JSON.new()
var parse_error = json.parse(response_body.get_string_from_utf8())
if parse_error != OK:
emit_signal("request_failed", "JSON parse error: %d" % parse_error)
return null
var response_data = json.get_data()
if response_code >= 400:
emit_signal("request_failed", response_data.get("error", "Unknown error"))
return null
return response_data
func _wait_for_request_or_timeout(request: HTTPRequest) -> Array:
var timer = Timer.new()
add_child(timer)
timer.wait_time = 5.0
timer.one_shot = true
timer.timeout.connect(func():
_timed_out = true
if request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED:
request.cancel_request()
)
timer.start()
while true:
if _timed_out:
_timed_out = false
timer.queue_free()
return [HTTPRequest.RESULT_REQUEST_FAILED, 408, [], PackedByteArray()]
if request.get_http_client_status() == HTTPClient.STATUS_REQUESTING:
break
await get_tree().process_frame
timer.queue_free()
return await request.request_completed

View File

@ -230,7 +230,6 @@ func socketio_connect(name_space: String="/"):
# disconnect from socket.io server by namespace
func socketio_disconnect(name_space: String="/"):
print("FUCK")
if _connection_state == ConnectionState.CONNECTED:
# We should ONLY send disconnect packet when we're connected
_socketio_send_packet(SocketIOPacketType.DISCONNECT, name_space)