staging #447
| @@ -21,7 +21,7 @@ class TaskABC(commands.Cog): | ||||
|  | ||||
|     @ServiceProviderABC.inject | ||||
|     async def _wait_until_ready(self, config: ConfigurationABC, logger: TaskLogger, bot: DiscordBotServiceABC): | ||||
|         logger.debug(__name__, f"Waiting before {type(self).__name__}") | ||||
|         logger.debug(__name__, f"Waiting before ready {type(self).__name__}") | ||||
|         await bot.wait_until_ready() | ||||
|  | ||||
|         async def wait(): | ||||
|   | ||||
| @@ -27,3 +27,4 @@ class FeatureFlagsEnum(Enum): | ||||
|     short_role_name = "ShortRoleName" | ||||
|     technician_full_access = "TechnicianFullAccess" | ||||
|     steam_special_offers = "SteamSpecialOffers" | ||||
|     scheduled_events = "ScheduledEvents" | ||||
|   | ||||
| @@ -29,6 +29,7 @@ class FeatureFlagsSettings(ConfigurationModelABC): | ||||
|         FeatureFlagsEnum.short_role_name.value: False,  # 28.09.2023 #378 | ||||
|         FeatureFlagsEnum.technician_full_access.value: False,  # 03.10.2023 #393 | ||||
|         FeatureFlagsEnum.steam_special_offers.value: False,  # 11.10.2023 #188 | ||||
|         FeatureFlagsEnum.scheduled_events.value: False,  # 14.11.2023 #410 | ||||
|     } | ||||
|  | ||||
|     def __init__(self, **kwargs: dict): | ||||
|   | ||||
							
								
								
									
										35
									
								
								bot/src/bot_data/abc/scheduled_event_repository_abc.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								bot/src/bot_data/abc/scheduled_event_repository_abc.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| from abc import ABC, abstractmethod | ||||
|  | ||||
| from cpl_query.extension import List | ||||
|  | ||||
| from bot_data.model.scheduled_event import ScheduledEvent | ||||
|  | ||||
|  | ||||
| class ScheduledEventRepositoryABC(ABC): | ||||
|     @abstractmethod | ||||
|     def __init__(self): | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     def get_scheduled_events(self) -> List[ScheduledEvent]: | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     def get_scheduled_event_by_id(self, id: int) -> ScheduledEvent: | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     def get_scheduled_events_by_server_id(self, id: int) -> List[ScheduledEvent]: | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     def add_scheduled_event(self, scheduled_event: ScheduledEvent): | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     def update_scheduled_event(self, scheduled_event: ScheduledEvent): | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     def delete_scheduled_event(self, scheduled_event: ScheduledEvent): | ||||
|         pass | ||||
| @@ -14,6 +14,7 @@ from bot_data.abc.data_seeder_abc import DataSeederABC | ||||
| from bot_data.abc.game_server_repository_abc import GameServerRepositoryABC | ||||
| from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC | ||||
| from bot_data.abc.level_repository_abc import LevelRepositoryABC | ||||
| from bot_data.abc.scheduled_event_repository_abc import ScheduledEventRepositoryABC | ||||
| from bot_data.abc.server_config_repository_abc import ServerConfigRepositoryABC | ||||
| from bot_data.abc.server_repository_abc import ServerRepositoryABC | ||||
| from bot_data.abc.short_role_name_repository_abc import ShortRoleNameRepositoryABC | ||||
| @@ -45,6 +46,7 @@ from bot_data.service.client_repository_service import ClientRepositoryService | ||||
| from bot_data.service.game_server_repository_service import GameServerRepositoryService | ||||
| from bot_data.service.known_user_repository_service import KnownUserRepositoryService | ||||
| from bot_data.service.level_repository_service import LevelRepositoryService | ||||
| from bot_data.service.scheduled_event_repository_service import ScheduledEventRepositoryService | ||||
| from bot_data.service.seeder_service import SeederService | ||||
| from bot_data.service.server_config_repository_service import ( | ||||
|     ServerConfigRepositoryService, | ||||
| @@ -115,6 +117,7 @@ class DataModule(ModuleABC): | ||||
|         services.add_transient(ServerConfigRepositoryABC, ServerConfigRepositoryService) | ||||
|         services.add_transient(ShortRoleNameRepositoryABC, ShortRoleNameRepositoryService) | ||||
|         services.add_transient(SteamSpecialOfferRepositoryABC, SteamSpecialOfferRepositoryService) | ||||
|         services.add_transient(ScheduledEventRepositoryABC, ScheduledEventRepositoryService) | ||||
|  | ||||
|         services.add_transient(SeederService) | ||||
|         services.add_transient(DataSeederABC, TechnicianConfigSeeder) | ||||
|   | ||||
							
								
								
									
										188
									
								
								bot/src/bot_data/model/scheduled_event.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								bot/src/bot_data/model/scheduled_event.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,188 @@ | ||||
| from datetime import datetime | ||||
| from typing import Optional | ||||
|  | ||||
| import discord | ||||
| from cpl_core.database import TableABC | ||||
|  | ||||
| from bot_data.model.scheduled_event_interval_enum import ScheduledEventIntervalEnum | ||||
| from bot_data.model.server import Server | ||||
|  | ||||
|  | ||||
| class ScheduledEvent(TableABC): | ||||
|     def __init__( | ||||
|         self, | ||||
|         interval: ScheduledEventIntervalEnum, | ||||
|         name: str, | ||||
|         description: str, | ||||
|         channel_id: int, | ||||
|         start_time: datetime, | ||||
|         end_time: Optional[datetime], | ||||
|         entity_type: discord.EntityType, | ||||
|         location: Optional[str], | ||||
|         server: Optional[Server], | ||||
|         created_at: datetime = None, | ||||
|         modified_at: datetime = None, | ||||
|         id=0, | ||||
|     ): | ||||
|         self._id = id | ||||
|         self._interval = interval | ||||
|         self._name = name | ||||
|         self._description = description | ||||
|         self._channel_id = channel_id | ||||
|         self._start_time = start_time | ||||
|         self._end_time = end_time | ||||
|         self._entity_type = entity_type | ||||
|         self._location = location | ||||
|         self._server = server | ||||
|  | ||||
|         TableABC.__init__(self) | ||||
|         self._created_at = created_at if created_at is not None else self._created_at | ||||
|         self._modified_at = modified_at if modified_at is not None else self._modified_at | ||||
|  | ||||
|     @property | ||||
|     def id(self) -> int: | ||||
|         return self._id | ||||
|  | ||||
|     @property | ||||
|     def interval(self) -> ScheduledEventIntervalEnum: | ||||
|         return self._interval | ||||
|  | ||||
|     @interval.setter | ||||
|     def interval(self, value: ScheduledEventIntervalEnum): | ||||
|         self._interval = value | ||||
|  | ||||
|     @property | ||||
|     def name(self) -> str: | ||||
|         return self._name | ||||
|  | ||||
|     @name.setter | ||||
|     def name(self, value: str): | ||||
|         self._name = value | ||||
|  | ||||
|     @property | ||||
|     def description(self) -> str: | ||||
|         return self._description | ||||
|  | ||||
|     @description.setter | ||||
|     def description(self, value: str): | ||||
|         self._description = value | ||||
|  | ||||
|     @property | ||||
|     def channel_id(self) -> int: | ||||
|         return self._channel_id | ||||
|  | ||||
|     @channel_id.setter | ||||
|     def channel_id(self, value: int): | ||||
|         self._channel_id = value | ||||
|  | ||||
|     @property | ||||
|     def start_time(self) -> datetime: | ||||
|         return self._start_time | ||||
|  | ||||
|     @start_time.setter | ||||
|     def start_time(self, value: datetime): | ||||
|         self._start_time = value | ||||
|  | ||||
|     @property | ||||
|     def end_time(self) -> datetime: | ||||
|         return self._end_time | ||||
|  | ||||
|     @end_time.setter | ||||
|     def end_time(self, value: datetime): | ||||
|         self._end_time = value | ||||
|  | ||||
|     @property | ||||
|     def entity_type(self) -> discord.EntityType: | ||||
|         return self._entity_type | ||||
|  | ||||
|     @entity_type.setter | ||||
|     def entity_type(self, value: discord.EntityType): | ||||
|         self._entity_type = value | ||||
|  | ||||
|     @property | ||||
|     def location(self) -> str: | ||||
|         return self._location | ||||
|  | ||||
|     @location.setter | ||||
|     def location(self, value: str): | ||||
|         self._location = value | ||||
|  | ||||
|     @property | ||||
|     def server(self) -> Server: | ||||
|         return self._server | ||||
|  | ||||
|     @server.setter | ||||
|     def server(self, value: Server): | ||||
|         self._server = value | ||||
|  | ||||
|     @staticmethod | ||||
|     def get_select_all_string() -> str: | ||||
|         return str( | ||||
|             f""" | ||||
|             SELECT * FROM `ScheduledEvents`; | ||||
|         """ | ||||
|         ) | ||||
|  | ||||
|     @staticmethod | ||||
|     def get_select_by_id_string(id: int) -> str: | ||||
|         return str( | ||||
|             f""" | ||||
|             SELECT * FROM `ScheduledEvents` | ||||
|             WHERE `Id` = {id}; | ||||
|         """ | ||||
|         ) | ||||
|  | ||||
|     @staticmethod | ||||
|     def get_select_by_server_id_string(s_id: int) -> str: | ||||
|         return str( | ||||
|             f""" | ||||
|             SELECT * FROM `ScheduledEvents` | ||||
|             WHERE `ServerId` = {s_id}; | ||||
|         """ | ||||
|         ) | ||||
|  | ||||
|     @property | ||||
|     def insert_string(self) -> str: | ||||
|         return str( | ||||
|             f""" | ||||
|             INSERT INTO `ScheduledEvents` ( | ||||
|                 `Interval`, `Name`, `Description`, `ChannelId`, `StartTime`, `EndTime`, `EntityType`, `Location`, `ServerId` | ||||
|             ) VALUES ( | ||||
|                 '{self._interval.value}', | ||||
|                 '{self._name}', | ||||
|                 {"NULL" if self._description is None else f"'{self._description}'"}, | ||||
|                 {"NULL" if self._channel_id is None else f"'{self._channel_id}'"}, | ||||
|                 '{self._start_time}', | ||||
|                 {"NULL" if self._end_time is None else f"'{self._end_time}'"}, | ||||
|                 '{self._entity_type.value}', | ||||
|                 {"NULL" if self._location is None else f"'{self._location}'"}, | ||||
|                 {self._server.id} | ||||
|             ); | ||||
|         """ | ||||
|         ) | ||||
|  | ||||
|     @property | ||||
|     def udpate_string(self) -> str: | ||||
|         return str( | ||||
|             f""" | ||||
|             UPDATE `ScheduledEvents` | ||||
|             SET `Interval` = '{self._interval.value}',  | ||||
|             `Name` = '{self._name}', | ||||
|             `Description` = {"NULL" if self._description is None else f"'{self._description}'"}, | ||||
|             `ChannelId` = {"NULL" if self._channel_id is None else f"'{self._channel_id}'"}, | ||||
|             `StartTime` = '{self._start_time}', | ||||
|             `EndTime` = {"NULL" if self._end_time is None else f"'{self._end_time}'"},  | ||||
|             `EntityType` = '{self._entity_type.value}', | ||||
|             `Location` = {"NULL" if self._location is None else f"'{self._location}'"} | ||||
|             WHERE `Id` = {self._id}; | ||||
|         """ | ||||
|         ) | ||||
|  | ||||
|     @property | ||||
|     def delete_string(self) -> str: | ||||
|         return str( | ||||
|             f""" | ||||
|             DELETE FROM `ScheduledEvents` | ||||
|             WHERE `Id` = {self._id}; | ||||
|         """ | ||||
|         ) | ||||
							
								
								
									
										118
									
								
								bot/src/bot_data/model/scheduled_event_history.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								bot/src/bot_data/model/scheduled_event_history.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| from datetime import datetime | ||||
| from typing import Optional | ||||
|  | ||||
| import discord | ||||
| from cpl_core.database import TableABC | ||||
|  | ||||
| from bot_data.abc.history_table_abc import HistoryTableABC | ||||
| from bot_data.model.server import Server | ||||
|  | ||||
|  | ||||
| class ScheduledEventHistory(HistoryTableABC): | ||||
|     def __init__( | ||||
|         self, | ||||
|         interval: str, | ||||
|         name: str, | ||||
|         description: str, | ||||
|         channel_id: int, | ||||
|         start_time: datetime, | ||||
|         end_time: Optional[datetime], | ||||
|         entity_type: discord.EntityType, | ||||
|         location: Optional[str], | ||||
|         server: Optional[Server], | ||||
|         deleted: bool, | ||||
|         date_from: str, | ||||
|         date_to: str, | ||||
|         id=0, | ||||
|     ): | ||||
|         HistoryTableABC.__init__(self) | ||||
|         self._id = id | ||||
|         self._interval = interval | ||||
|         self._name = name | ||||
|         self._description = description | ||||
|         self._channel_id = channel_id | ||||
|         self._start_time = start_time | ||||
|         self._end_time = end_time | ||||
|         self._entity_type = entity_type | ||||
|         self._location = location | ||||
|         self._server = server | ||||
|  | ||||
|         self._deleted = deleted | ||||
|         self._date_from = date_from | ||||
|         self._date_to = date_to | ||||
|  | ||||
|     @property | ||||
|     def id(self) -> int: | ||||
|         return self._id | ||||
|  | ||||
|     @property | ||||
|     def interval(self) -> str: | ||||
|         return self._interval | ||||
|  | ||||
|     @interval.setter | ||||
|     def interval(self, value: str): | ||||
|         self._interval = value | ||||
|  | ||||
|     @property | ||||
|     def name(self) -> str: | ||||
|         return self._name | ||||
|  | ||||
|     @name.setter | ||||
|     def name(self, value: str): | ||||
|         self._name = value | ||||
|  | ||||
|     @property | ||||
|     def description(self) -> str: | ||||
|         return self._description | ||||
|  | ||||
|     @description.setter | ||||
|     def description(self, value: str): | ||||
|         self._description = value | ||||
|  | ||||
|     @property | ||||
|     def channel_id(self) -> int: | ||||
|         return self._channel_id | ||||
|  | ||||
|     @channel_id.setter | ||||
|     def channel_id(self, value: int): | ||||
|         self._channel_id = value | ||||
|  | ||||
|     @property | ||||
|     def start_time(self) -> datetime: | ||||
|         return self._start_time | ||||
|  | ||||
|     @start_time.setter | ||||
|     def start_time(self, value: datetime): | ||||
|         self._start_time = value | ||||
|  | ||||
|     @property | ||||
|     def end_time(self) -> datetime: | ||||
|         return self._end_time | ||||
|  | ||||
|     @end_time.setter | ||||
|     def end_time(self, value: datetime): | ||||
|         self._end_time = value | ||||
|  | ||||
|     @property | ||||
|     def entity_type(self) -> discord.EntityType: | ||||
|         return self._entity_type | ||||
|  | ||||
|     @entity_type.setter | ||||
|     def entity_type(self, value: discord.EntityType): | ||||
|         self._entity_type = value | ||||
|  | ||||
|     @property | ||||
|     def location(self) -> str: | ||||
|         return self._location | ||||
|  | ||||
|     @location.setter | ||||
|     def location(self, value: str): | ||||
|         self._location = value | ||||
|  | ||||
|     @property | ||||
|     def server(self) -> Server: | ||||
|         return self._server | ||||
|  | ||||
|     @server.setter | ||||
|     def server(self, value: Server): | ||||
|         self._server = value | ||||
							
								
								
									
										8
									
								
								bot/src/bot_data/model/scheduled_event_interval_enum.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								bot/src/bot_data/model/scheduled_event_interval_enum.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| from enum import Enum | ||||
|  | ||||
|  | ||||
| class ScheduledEventIntervalEnum(Enum): | ||||
|     daily = "daily" | ||||
|     weekly = "weekly" | ||||
|     monthly = "monthly" | ||||
|     yearly = "yearly" | ||||
| @@ -0,0 +1,3 @@ | ||||
| DROP TABLE `ShortRoleNames`; | ||||
|  | ||||
| DROP TABLE `ShortRoleNamesHistory`; | ||||
							
								
								
									
										83
									
								
								bot/src/bot_data/scripts/1.2.2/1_ScheduledEvents_up.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								bot/src/bot_data/scripts/1.2.2/1_ScheduledEvents_up.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | ||||
| CREATE TABLE IF NOT EXISTS `ScheduledEvents` | ||||
| ( | ||||
|     `Id`             BIGINT             NOT NULL AUTO_INCREMENT, | ||||
|     `Interval`    ENUM ('daily', 'weekly', 'monthly', 'yearly') NOT NULL, | ||||
|     `Name`           VARCHAR(255)       NOT NULL, | ||||
|     `Description` VARCHAR(255)                                  NULL, | ||||
|     `ChannelId`      BIGINT             NULL, | ||||
|     `StartTime`      DATETIME(6)        NOT NULL, | ||||
|     `EndTime`        DATETIME(6)        NULL, | ||||
|     `EntityType`     ENUM ('1','2','3') NOT NULL, | ||||
|     `Location`       VARCHAR(255)       NULL, | ||||
|     `ServerId`       BIGINT, | ||||
|     `CreatedAt`      DATETIME(6)        NULL DEFAULT CURRENT_TIMESTAMP(6), | ||||
|     `LastModifiedAt` DATETIME(6)        NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), | ||||
|     PRIMARY KEY (`Id`), | ||||
|     FOREIGN KEY (`ServerId`) REFERENCES `Servers` (`ServerId`) | ||||
| ); | ||||
|  | ||||
| CREATE TABLE IF NOT EXISTS `ScheduledEventsHistory` | ||||
| ( | ||||
|     `Id`          BIGINT(20)         NOT NULL, | ||||
|     `Interval`    ENUM ('daily', 'weekly', 'monthly', 'yearly') NOT NULL, | ||||
|     `Name`        VARCHAR(255)       NOT NULL, | ||||
|     `Description` VARCHAR(255)                                  NULL, | ||||
|     `ChannelId`   BIGINT             NULL, | ||||
|     `StartTime`   DATETIME(6)        NOT NULL, | ||||
|     `EndTime`     DATETIME(6)        NULL, | ||||
|     `EntityType`  ENUM ('1','2','3') NOT NULL, | ||||
|     `Location`    VARCHAR(255)       NULL, | ||||
|     `ServerId`    BIGINT(20) DEFAULT NULL, | ||||
|     `Deleted`     BOOL       DEFAULT FALSE, | ||||
|     `DateFrom`    DATETIME(6)        NOT NULL, | ||||
|     `DateTo`      DATETIME(6)        NOT NULL | ||||
| ); | ||||
|  | ||||
| DROP TRIGGER IF EXISTS `TR_ScheduledEventsUpdate`; | ||||
|  | ||||
| CREATE TRIGGER `TR_ScheduledEventsUpdate` | ||||
|     AFTER UPDATE | ||||
|     ON `ScheduledEvents` | ||||
|     FOR EACH ROW | ||||
| BEGIN | ||||
|     INSERT INTO `ScheduledEventsHistory` (`Id`, | ||||
|                                           `Interval`, | ||||
|                                           `Name`, | ||||
|                                           `Description`, | ||||
|                                           `ChannelId`, | ||||
|                                           `StartTime`, | ||||
|                                           `EndTime`, | ||||
|                                           `EntityType`, | ||||
|                                           `Location`, | ||||
|                                           `ServerId`, | ||||
|                                           `DateFrom`, | ||||
|                                           `DateTo`) | ||||
|     VALUES (OLD.Id, OLD.Interval, OLD.Name, OLD.Description, OLD.ChannelId, OLD.StartTime, OLD.EndTime, OLD.EntityType, | ||||
|             OLD.Location, OLD.ServerId, OLD.LastModifiedAt, | ||||
|             CURRENT_TIMESTAMP(6)); | ||||
| END; | ||||
|  | ||||
| DROP TRIGGER IF EXISTS `TR_ScheduledEventsDelete`; | ||||
|  | ||||
| CREATE TRIGGER `TR_ScheduledEventsDelete` | ||||
|     AFTER DELETE | ||||
|     ON `ScheduledEvents` | ||||
|     FOR EACH ROW | ||||
| BEGIN | ||||
|     INSERT INTO `ScheduledEventsHistory` (`Id`, | ||||
|                                           `Interval`, | ||||
|                                           `Name`, | ||||
|                                           `Description`, | ||||
|                                           `ChannelId`, | ||||
|                                           `StartTime`, | ||||
|                                           `EndTime`, | ||||
|                                           `EntityType`, | ||||
|                                           `Location`, | ||||
|                                           `ServerId`, | ||||
|                                           `Deleted`, | ||||
|                                           `DateFrom`, | ||||
|                                           `DateTo`) | ||||
|     VALUES (OLD.Id, OLD.Interval, OLD.Name, OLD.Description, OLD.ChannelId, OLD.StartTime, OLD.EndTime, OLD.EntityType, | ||||
|             OLD.Location, OLD.ServerId, TRUE, OLD.LastModifiedAt, | ||||
|             CURRENT_TIMESTAMP(6)); | ||||
| END; | ||||
| @@ -0,0 +1,91 @@ | ||||
| from typing import Optional | ||||
|  | ||||
| from cpl_core.database.context import DatabaseContextABC | ||||
| from cpl_query.extension import List | ||||
| from discord import EntityType | ||||
|  | ||||
| from bot_core.logging.database_logger import DatabaseLogger | ||||
| from bot_data.abc.server_repository_abc import ServerRepositoryABC | ||||
| from bot_data.abc.scheduled_event_repository_abc import ScheduledEventRepositoryABC | ||||
| from bot_data.model.scheduled_event import ScheduledEvent | ||||
| from bot_data.model.scheduled_event_interval_enum import ScheduledEventIntervalEnum | ||||
|  | ||||
|  | ||||
| class ScheduledEventRepositoryService(ScheduledEventRepositoryABC): | ||||
|     def __init__( | ||||
|         self, | ||||
|         logger: DatabaseLogger, | ||||
|         db_context: DatabaseContextABC, | ||||
|         servers: ServerRepositoryABC, | ||||
|     ): | ||||
|         self._logger = logger | ||||
|         self._context = db_context | ||||
|  | ||||
|         self._servers = servers | ||||
|  | ||||
|         ScheduledEventRepositoryABC.__init__(self) | ||||
|  | ||||
|     @staticmethod | ||||
|     def _get_value_from_result(value: any) -> Optional[any]: | ||||
|         if isinstance(value, str) and "NULL" in value: | ||||
|             return None | ||||
|  | ||||
|         return value | ||||
|  | ||||
|     def _scheduled_event_from_result(self, sql_result: tuple) -> ScheduledEvent: | ||||
|         return ScheduledEvent( | ||||
|             self._get_value_from_result(ScheduledEventIntervalEnum(sql_result[1])),  # interval | ||||
|             self._get_value_from_result(sql_result[2]),  # name | ||||
|             self._get_value_from_result(sql_result[3]),  # description | ||||
|             int(self._get_value_from_result(sql_result[4])),  # channel_id | ||||
|             self._get_value_from_result(sql_result[5]),  # start_time | ||||
|             self._get_value_from_result(sql_result[6]),  # end_time | ||||
|             EntityType(int(self._get_value_from_result(sql_result[7]))),  # entity_type | ||||
|             self._get_value_from_result(sql_result[8]),  # location | ||||
|             self._servers.get_server_by_id((sql_result[9])),  # server | ||||
|             self._get_value_from_result(sql_result[10]),  # created_at | ||||
|             self._get_value_from_result(sql_result[11]),  # modified_at | ||||
|             id=self._get_value_from_result(sql_result[0]), | ||||
|         ) | ||||
|  | ||||
|     def get_scheduled_events(self) -> List[ScheduledEvent]: | ||||
|         scheduled_events = List(ScheduledEvent) | ||||
|         self._logger.trace(__name__, f"Send SQL command: {ScheduledEvent.get_select_all_string()}") | ||||
|         results = self._context.select(ScheduledEvent.get_select_all_string()) | ||||
|         for result in results: | ||||
|             self._logger.trace(__name__, f"Get scheduled_event with id {result[0]}") | ||||
|             scheduled_events.append(self._scheduled_event_from_result(result)) | ||||
|  | ||||
|         return scheduled_events | ||||
|  | ||||
|     def get_scheduled_event_by_id(self, id: int) -> ScheduledEvent: | ||||
|         self._logger.trace(__name__, f"Send SQL command: {ScheduledEvent.get_select_by_id_string(id)}") | ||||
|         result = self._context.select(ScheduledEvent.get_select_by_id_string(id))[0] | ||||
|  | ||||
|         return self._scheduled_event_from_result(result) | ||||
|  | ||||
|     def get_scheduled_events_by_server_id(self, server_id: int) -> List[ScheduledEvent]: | ||||
|         scheduled_events = List(ScheduledEvent) | ||||
|         self._logger.trace( | ||||
|             __name__, | ||||
|             f"Send SQL command: {ScheduledEvent.get_select_by_server_id_string(server_id)}", | ||||
|         ) | ||||
|         results = self._context.select(ScheduledEvent.get_select_by_server_id_string(server_id)) | ||||
|  | ||||
|         for result in results: | ||||
|             self._logger.trace(__name__, f"Get scheduled_event with id {result[0]}") | ||||
|             scheduled_events.append(self._scheduled_event_from_result(result)) | ||||
|  | ||||
|         return scheduled_events | ||||
|  | ||||
|     def add_scheduled_event(self, scheduled_event: ScheduledEvent): | ||||
|         self._logger.trace(__name__, f"Send SQL command: {scheduled_event.insert_string}") | ||||
|         self._context.cursor.execute(scheduled_event.insert_string) | ||||
|  | ||||
|     def update_scheduled_event(self, scheduled_event: ScheduledEvent): | ||||
|         self._logger.trace(__name__, f"Send SQL command: {scheduled_event.udpate_string}") | ||||
|         self._context.cursor.execute(scheduled_event.udpate_string) | ||||
|  | ||||
|     def delete_scheduled_event(self, scheduled_event: ScheduledEvent): | ||||
|         self._logger.trace(__name__, f"Send SQL command: {scheduled_event.delete_string}") | ||||
|         self._context.cursor.execute(scheduled_event.delete_string) | ||||
| @@ -1,8 +1,10 @@ | ||||
| import functools | ||||
| from abc import ABC, abstractmethod | ||||
| from inspect import signature, Parameter | ||||
| from typing import Callable | ||||
|  | ||||
| from cpl_core.dependency_injection import ServiceProviderABC | ||||
| from cpl_core.type import R, T | ||||
| from cpl_query.extension import List | ||||
|  | ||||
|  | ||||
| @@ -52,3 +54,18 @@ class FilterABC(ABC): | ||||
|             return f(*args, **kwargs) | ||||
|  | ||||
|         return decorator | ||||
|  | ||||
|     def _get_attributes_from_dict(self, attrs: list[tuple[str, type]], values: dict): | ||||
|         for key_pair in attrs: | ||||
|             attr = key_pair[0] | ||||
|             attr_type = key_pair[1] | ||||
|             if attr in values: | ||||
|                 self.__setattr__(f"_{attr}", attr_type(values[attr])) | ||||
|  | ||||
|     @staticmethod | ||||
|     def _filter_by_attributes(attrs: list[dict], values: List[T]) -> List[R]: | ||||
|         for attr in attrs: | ||||
|             if attr["attr"] is None: | ||||
|                 continue | ||||
|             values = values.where(attr["func"]) | ||||
|         return values | ||||
|   | ||||
| @@ -20,6 +20,7 @@ from bot_data.model.auto_role_rule import AutoRoleRule | ||||
| from bot_data.model.client import Client | ||||
| from bot_data.model.known_user import KnownUser | ||||
| from bot_data.model.level import Level | ||||
| from bot_data.model.scheduled_event import ScheduledEvent | ||||
| from bot_data.model.server import Server | ||||
| from bot_data.model.server_config import ServerConfig | ||||
| from bot_data.model.short_role_name import ShortRoleName | ||||
| @@ -213,6 +214,16 @@ class QueryABC(ObjectType): | ||||
|                     access = True | ||||
|                     break | ||||
|  | ||||
|         elif type(element) == ScheduledEvent: | ||||
|             element: ScheduledEvent = element | ||||
|             for u in user.users: | ||||
|                 u: User = u | ||||
|                 guild = bot.get_guild(u.server.discord_id) | ||||
|                 member = guild.get_member(u.discord_id) | ||||
|                 if permissions.is_member_moderator(member) and u.server.id == element.server.id: | ||||
|                     access = True | ||||
|                     break | ||||
|  | ||||
|         elif type(element) == dict and "key" in element and element["key"] in [e.value for e in FeatureFlagsEnum]: | ||||
|             for u in user.users: | ||||
|                 u: User = u | ||||
|   | ||||
							
								
								
									
										71
									
								
								bot/src/bot_graphql/filter/scheduled_event_filter.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								bot/src/bot_graphql/filter/scheduled_event_filter.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | ||||
| from cpl_core.dependency_injection import ServiceProviderABC | ||||
| from cpl_discord.service import DiscordBotServiceABC | ||||
| from cpl_query.extension import List | ||||
|  | ||||
| from bot_data.model.scheduled_event import ScheduledEvent | ||||
| from bot_graphql.abc.filter_abc import FilterABC | ||||
|  | ||||
|  | ||||
| class ScheduledEventFilter(FilterABC): | ||||
|     def __init__(self, bot: DiscordBotServiceABC, services: ServiceProviderABC): | ||||
|         FilterABC.__init__(self) | ||||
|         self._bot = bot | ||||
|         self._services = services | ||||
|  | ||||
|         self._id = None | ||||
|         self._interval = None | ||||
|         self._name = None | ||||
|         self._description = None | ||||
|         self._channel_id = None | ||||
|         self._start_time = None | ||||
|         self._end_time = None | ||||
|         self._entity_type = None | ||||
|         self._location = None | ||||
|         self._server = None | ||||
|  | ||||
|     def from_dict(self, values: dict): | ||||
|         self._get_attributes_from_dict( | ||||
|             [ | ||||
|                 ("id", int), | ||||
|                 ("interval", str), | ||||
|                 ("name", str), | ||||
|                 ("description", str), | ||||
|                 ("channel_id", str), | ||||
|                 ("start_time", str), | ||||
|                 ("end_time", str), | ||||
|                 ("entity_type", str), | ||||
|                 ("location", str), | ||||
|             ], | ||||
|             values, | ||||
|         ) | ||||
|  | ||||
|         if "server" in values: | ||||
|             from bot_graphql.filter.server_filter import ServerFilter | ||||
|  | ||||
|             self._server: ServerFilter = self._services.get_service(ServerFilter) | ||||
|             self._server.from_dict(values["server"]) | ||||
|  | ||||
|     def filter(self, query: List[ScheduledEvent]) -> List[ScheduledEvent]: | ||||
|         if self._id is not None: | ||||
|             query = query.where(lambda x: x.id == self._id) | ||||
|  | ||||
|         query = self._filter_by_attributes( | ||||
|             [ | ||||
|                 {"attr": self._id, "func": lambda x: x.id == self._id}, | ||||
|                 {"attr": self._interval, "func": lambda x: x.interval == self._interval}, | ||||
|                 {"attr": self._name, "func": lambda x: x.name == self._name}, | ||||
|                 {"attr": self._description, "func": lambda x: x.description == self._description}, | ||||
|                 {"attr": self._channel_id, "func": lambda x: x.channel_id == self._channel_id}, | ||||
|                 {"attr": self._start_time, "func": lambda x: x.start_time == self._start_time}, | ||||
|                 {"attr": self._end_time, "func": lambda x: x.end_time == self._end_time}, | ||||
|                 {"attr": self._entity_type, "func": lambda x: x.entity_type == self._entity_type}, | ||||
|                 {"attr": self._location, "func": lambda x: x.location == self._location}, | ||||
|             ], | ||||
|             query, | ||||
|         ) | ||||
|  | ||||
|         if self._server is not None: | ||||
|             servers = self._server.filter(query.select(lambda x: x.server)).select(lambda x: x.id) | ||||
|             query = query.where(lambda x: x.server.id in servers) | ||||
|  | ||||
|         return query | ||||
| @@ -1,3 +1,4 @@ | ||||
| from cpl_core.dependency_injection import ServiceProviderABC | ||||
| from cpl_discord.service import DiscordBotServiceABC | ||||
| from cpl_query.extension import List | ||||
|  | ||||
| @@ -8,9 +9,10 @@ from bot_graphql.abc.filter_abc import FilterABC | ||||
|  | ||||
|  | ||||
| class ShortRoleNameFilter(FilterABC): | ||||
|     def __init__(self, bot: DiscordBotServiceABC): | ||||
|     def __init__(self, bot: DiscordBotServiceABC, services: ServiceProviderABC): | ||||
|         FilterABC.__init__(self) | ||||
|         self._bot = bot | ||||
|         self._services = services | ||||
|  | ||||
|         self._id = None | ||||
|         self._short_name = None | ||||
|   | ||||
| @@ -6,6 +6,7 @@ type Mutation { | ||||
|     userJoinedGameServer: UserJoinedGameServerMutation | ||||
|     achievement: AchievementMutation | ||||
|     shortRoleName: ShortRoleNameMutation | ||||
|     scheduledEvent: ScheduledEventMutation | ||||
|     technicianConfig: TechnicianConfigMutation | ||||
|     serverConfig: ServerConfigMutation | ||||
| } | ||||
| @@ -41,6 +41,9 @@ type Query { | ||||
|     shortRoleNames(filter: ShortRoleNameFilter, page: Page, sort: Sort): [ShortRoleName] | ||||
|     shortRoleNamePositions: [String] | ||||
|  | ||||
|     scheduledEventCount: Int | ||||
|     scheduledEvents(filter: ScheduledEventFilter, page: Page, sort: Sort): [ScheduledEvent] | ||||
|  | ||||
|     userWarningCount: Int | ||||
|     userWarnings(filter: UserWarningFilter, page: Page, sort: Sort): [UserWarning] | ||||
|  | ||||
|   | ||||
							
								
								
									
										68
									
								
								bot/src/bot_graphql/graphql/scheduledEvent.gql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								bot/src/bot_graphql/graphql/scheduledEvent.gql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | ||||
| type ScheduledEvent implements TableWithHistoryQuery { | ||||
|     id: ID | ||||
|     interval: String | ||||
|     name: String | ||||
|     description: String | ||||
|     channelId: String | ||||
|     startTime: String | ||||
|     endTime: String | ||||
|     entityType: Int | ||||
|     location: String | ||||
|  | ||||
|     server: Server | ||||
|  | ||||
|     createdAt: String | ||||
|     modifiedAt: String | ||||
|  | ||||
|     history: [ScheduledEventHistory] | ||||
| } | ||||
|  | ||||
| type ScheduledEventHistory implements HistoryTableQuery { | ||||
|     id: ID | ||||
|     interval: String | ||||
|     name: String | ||||
|     description: String | ||||
|     channelId: String | ||||
|     startTime: String | ||||
|     endTime: String | ||||
|     entityType: Int | ||||
|     location: String | ||||
|  | ||||
|     server: ID | ||||
|  | ||||
|     deleted: Boolean | ||||
|     dateFrom: String | ||||
|     dateTo: String | ||||
| } | ||||
|  | ||||
| input ScheduledEventFilter { | ||||
|     id: ID | ||||
|     interval: String | ||||
|     name: String | ||||
|     description: String | ||||
|     channelId: String | ||||
|     startTime: String | ||||
|     endTime: String | ||||
|     entityType: Int | ||||
|     location: String | ||||
|     server: ServerFilter | ||||
| } | ||||
|  | ||||
| type ScheduledEventMutation { | ||||
|     createScheduledEvent(input: ScheduledEventInput!): ScheduledEvent | ||||
|     updateScheduledEvent(input: ScheduledEventInput!): ScheduledEvent | ||||
|     deleteScheduledEvent(id: ID): ScheduledEvent | ||||
| } | ||||
|  | ||||
| input ScheduledEventInput { | ||||
|     id: ID | ||||
|     interval: String | ||||
|     name: String | ||||
|     description: String | ||||
|     channelId: String | ||||
|     startTime: String | ||||
|     endTime: String | ||||
|     entityType: Int | ||||
|     location: String | ||||
|     serverId: ID | ||||
| } | ||||
| @@ -35,6 +35,9 @@ type Server implements TableWithHistoryQuery { | ||||
|     shortRoleNameCount: Int | ||||
|     shortRoleNames(filter: ShortRoleNameFilter, page: Page, sort: Sort): [ShortRoleName] | ||||
|  | ||||
|     scheduledEventCount: Int | ||||
|     scheduledEvents(filter: ScheduledEventFilter, page: Page, sort: Sort): [ScheduledEvent] | ||||
|  | ||||
|     config: ServerConfig | ||||
|     hasFeatureFlag(flag: String): FeatureFlag | ||||
|  | ||||
|   | ||||
| @@ -12,6 +12,7 @@ from bot_graphql.filter.auto_role_filter import AutoRoleFilter | ||||
| from bot_graphql.filter.auto_role_rule_filter import AutoRoleRuleFilter | ||||
| from bot_graphql.filter.client_filter import ClientFilter | ||||
| from bot_graphql.filter.level_filter import LevelFilter | ||||
| from bot_graphql.filter.scheduled_event_filter import ScheduledEventFilter | ||||
| from bot_graphql.filter.server_filter import ServerFilter | ||||
| from bot_graphql.filter.short_role_name_filter import ShortRoleNameFilter | ||||
| from bot_graphql.filter.user_filter import UserFilter | ||||
| @@ -27,6 +28,7 @@ from bot_graphql.mutations.achievement_mutation import AchievementMutation | ||||
| from bot_graphql.mutations.auto_role_mutation import AutoRoleMutation | ||||
| from bot_graphql.mutations.auto_role_rule_mutation import AutoRoleRuleMutation | ||||
| from bot_graphql.mutations.level_mutation import LevelMutation | ||||
| from bot_graphql.mutations.scheduled_event_mutation import ScheduledEventMutation | ||||
| from bot_graphql.mutations.server_config_mutation import ServerConfigMutation | ||||
| from bot_graphql.mutations.short_role_name_mutation import ShortRoleNameMutation | ||||
| from bot_graphql.mutations.technician_config_mutation import TechnicianConfigMutation | ||||
| @@ -54,6 +56,7 @@ from bot_graphql.queries.known_user_history_query import KnownUserHistoryQuery | ||||
| from bot_graphql.queries.known_user_query import KnownUserQuery | ||||
| from bot_graphql.queries.level_history_query import LevelHistoryQuery | ||||
| from bot_graphql.queries.level_query import LevelQuery | ||||
| from bot_graphql.queries.scheduled_event_query import ScheduledEventQuery | ||||
| from bot_graphql.queries.server_config_query import ServerConfigQuery | ||||
| from bot_graphql.queries.server_history_query import ServerHistoryQuery | ||||
| from bot_graphql.queries.server_query import ServerQuery | ||||
| @@ -140,6 +143,7 @@ class GraphQLModule(ModuleABC): | ||||
|         services.add_transient(QueryABC, UserWarningHistoryQuery) | ||||
|         services.add_transient(QueryABC, UserWarningQuery) | ||||
|         services.add_transient(QueryABC, ServerStatisticQuery) | ||||
|         services.add_transient(QueryABC, ScheduledEventQuery) | ||||
|  | ||||
|         services.add_transient(QueryABC, DiscordQuery) | ||||
|         services.add_transient(QueryABC, GuildQuery) | ||||
| @@ -161,6 +165,7 @@ class GraphQLModule(ModuleABC): | ||||
|         services.add_transient(FilterABC, UserJoinedGameServerFilter) | ||||
|         services.add_transient(FilterABC, ShortRoleNameFilter) | ||||
|         services.add_transient(FilterABC, UserWarningFilter) | ||||
|         services.add_transient(FilterABC, ScheduledEventFilter) | ||||
|  | ||||
|         # mutations | ||||
|         services.add_transient(QueryABC, AutoRoleMutation) | ||||
| @@ -172,3 +177,4 @@ class GraphQLModule(ModuleABC): | ||||
|         services.add_transient(QueryABC, UserJoinedGameServerMutation) | ||||
|         services.add_transient(QueryABC, TechnicianConfigMutation) | ||||
|         services.add_transient(QueryABC, ServerConfigMutation) | ||||
|         services.add_transient(QueryABC, ScheduledEventMutation) | ||||
|   | ||||
| @@ -4,6 +4,7 @@ from bot_graphql.mutations.achievement_mutation import AchievementMutation | ||||
| from bot_graphql.mutations.auto_role_mutation import AutoRoleMutation | ||||
| from bot_graphql.mutations.auto_role_rule_mutation import AutoRoleRuleMutation | ||||
| from bot_graphql.mutations.level_mutation import LevelMutation | ||||
| from bot_graphql.mutations.scheduled_event_mutation import ScheduledEventMutation | ||||
| from bot_graphql.mutations.server_config_mutation import ServerConfigMutation | ||||
| from bot_graphql.mutations.short_role_name_mutation import ShortRoleNameMutation | ||||
| from bot_graphql.mutations.technician_config_mutation import TechnicianConfigMutation | ||||
| @@ -23,6 +24,7 @@ class Mutation(MutationType): | ||||
|         achievement_mutation: AchievementMutation, | ||||
|         user_joined_game_server: UserJoinedGameServerMutation, | ||||
|         technician_config: TechnicianConfigMutation, | ||||
|         scheduled_event: ScheduledEventMutation, | ||||
|         server_config: ServerConfigMutation, | ||||
|         short_role_name_mutation: ShortRoleNameMutation, | ||||
|     ): | ||||
| @@ -36,4 +38,5 @@ class Mutation(MutationType): | ||||
|         self.set_field("userJoinedGameServer", lambda *_: user_joined_game_server) | ||||
|         self.set_field("shortRoleName", lambda *_: short_role_name_mutation) | ||||
|         self.set_field("technicianConfig", lambda *_: technician_config) | ||||
|         self.set_field("scheduledEvent", lambda *_: scheduled_event) | ||||
|         self.set_field("serverConfig", lambda *_: server_config) | ||||
|   | ||||
							
								
								
									
										107
									
								
								bot/src/bot_graphql/mutations/scheduled_event_mutation.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								bot/src/bot_graphql/mutations/scheduled_event_mutation.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| from datetime import datetime | ||||
|  | ||||
| from cpl_core.database.context import DatabaseContextABC | ||||
| from cpl_discord.service import DiscordBotServiceABC | ||||
| from discord import EntityType | ||||
|  | ||||
| from bot_data.abc.server_repository_abc import ServerRepositoryABC | ||||
| from bot_data.abc.scheduled_event_repository_abc import ScheduledEventRepositoryABC | ||||
| from bot_data.model.scheduled_event import ScheduledEvent | ||||
| from bot_data.model.scheduled_event_interval_enum import ScheduledEventIntervalEnum | ||||
| from bot_data.model.user_role_enum import UserRoleEnum | ||||
| from bot_graphql.abc.query_abc import QueryABC | ||||
| from modules.permission.service.permission_service import PermissionService | ||||
|  | ||||
|  | ||||
| class ScheduledEventMutation(QueryABC): | ||||
|     def __init__( | ||||
|         self, | ||||
|         servers: ServerRepositoryABC, | ||||
|         scheduled_events: ScheduledEventRepositoryABC, | ||||
|         bot: DiscordBotServiceABC, | ||||
|         db: DatabaseContextABC, | ||||
|         permissions: PermissionService, | ||||
|     ): | ||||
|         QueryABC.__init__(self, "ScheduledEventMutation") | ||||
|  | ||||
|         self._servers = servers | ||||
|         self._scheduled_events = scheduled_events | ||||
|         self._bot = bot | ||||
|         self._db = db | ||||
|         self._permissions = permissions | ||||
|  | ||||
|         self.set_field("createScheduledEvent", self.resolve_create_scheduled_event) | ||||
|         self.set_field("updateScheduledEvent", self.resolve_update_scheduled_event) | ||||
|         self.set_field("deleteScheduledEvent", self.resolve_delete_scheduled_event) | ||||
|  | ||||
|     def resolve_create_scheduled_event(self, *_, input: dict): | ||||
|         server = self._servers.get_server_by_id(input["serverId"]) | ||||
|         self._can_user_mutate_data(server, UserRoleEnum.moderator) | ||||
|  | ||||
|         scheduled_event = ScheduledEvent( | ||||
|             ScheduledEventIntervalEnum(input["interval"]), | ||||
|             input["name"], | ||||
|             input["description"] if "description" in input else None, | ||||
|             input["channelId"] if "channelId" in input else None, | ||||
|             datetime.strptime(input["startTime"], "%Y-%m-%dT%H:%M:%S.%fZ"), | ||||
|             datetime.strptime(input["endTime"], "%Y-%m-%dT%H:%M:%S.%fZ") if "endTime" in input else None, | ||||
|             input["entityType"], | ||||
|             input["location"] if "location" in input else None, | ||||
|             server, | ||||
|         ) | ||||
|  | ||||
|         self._scheduled_events.add_scheduled_event(scheduled_event) | ||||
|         self._db.save_changes() | ||||
|  | ||||
|         def get_new_scheduled_event(srn: ScheduledEvent): | ||||
|             return ( | ||||
|                 srn.interval == scheduled_event.interval | ||||
|                 and srn.name == scheduled_event.name | ||||
|                 and srn.description == scheduled_event.description | ||||
|             ) | ||||
|  | ||||
|         return ( | ||||
|             self._scheduled_events.get_scheduled_events_by_server_id(scheduled_event.server.id) | ||||
|             .where(get_new_scheduled_event) | ||||
|             .last() | ||||
|         ) | ||||
|  | ||||
|     def resolve_update_scheduled_event(self, *_, input: dict): | ||||
|         scheduled_event = self._scheduled_events.get_scheduled_event_by_id(input["id"]) | ||||
|         self._can_user_mutate_data(scheduled_event.server, UserRoleEnum.moderator) | ||||
|  | ||||
|         scheduled_event.interval = ( | ||||
|             ScheduledEventIntervalEnum(input["interval"]) if "interval" in input else scheduled_event.interval | ||||
|         ) | ||||
|         scheduled_event.name = input["name"] if "name" in input else scheduled_event.name | ||||
|         scheduled_event.description = input["description"] if "description" in input else scheduled_event.description | ||||
|         scheduled_event.channel_id = input["channelId"] if "channelId" in input else scheduled_event.channel_id | ||||
|         scheduled_event.start_time = ( | ||||
|             datetime.strptime(input["startTime"], "%Y-%m-%dT%H:%M:%S.%fZ") | ||||
|             if "startTime" in input | ||||
|             else scheduled_event.start_time | ||||
|         ) | ||||
|         scheduled_event.end_time = ( | ||||
|             datetime.strptime(input["endTime"], "%Y-%m-%dT%H:%M:%S.%fZ") | ||||
|             if "endTime" in input | ||||
|             else scheduled_event.end_time | ||||
|         ) | ||||
|         scheduled_event.entity_type = ( | ||||
|             EntityType(int(input["entityType"])) if "entityType" in input else scheduled_event.entity_type | ||||
|         ) | ||||
|         scheduled_event.location = input["location"] if "location" in input else scheduled_event.location | ||||
|  | ||||
|         self._scheduled_events.update_scheduled_event(scheduled_event) | ||||
|         self._db.save_changes() | ||||
|  | ||||
|         scheduled_event = self._scheduled_events.get_scheduled_event_by_id(input["id"]) | ||||
|         return scheduled_event | ||||
|  | ||||
|     def resolve_delete_scheduled_event(self, *_, id: int): | ||||
|         scheduled_event = self._scheduled_events.get_scheduled_event_by_id(id) | ||||
|         self._can_user_mutate_data(scheduled_event.server, UserRoleEnum.admin) | ||||
|  | ||||
|         self._scheduled_events.delete_scheduled_event(scheduled_event) | ||||
|         self._db.save_changes() | ||||
|  | ||||
|         return scheduled_event | ||||
							
								
								
									
										24
									
								
								bot/src/bot_graphql/queries/scheduled_event_history_query.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								bot/src/bot_graphql/queries/scheduled_event_history_query.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| from cpl_core.database.context import DatabaseContextABC | ||||
|  | ||||
| from bot_data.model.scheduled_event_history import ScheduledEventHistory | ||||
| from bot_graphql.abc.data_query_with_history_abc import DataQueryWithHistoryABC | ||||
|  | ||||
|  | ||||
| class ScheduledEventHistoryQuery(DataQueryWithHistoryABC): | ||||
|     def __init__( | ||||
|         self, | ||||
|         db: DatabaseContextABC, | ||||
|     ): | ||||
|         DataQueryWithHistoryABC.__init__(self, "ScheduledEvent", "ScheduledEventsHistory", ScheduledEventHistory, db) | ||||
|  | ||||
|         self.set_field("id", lambda x, *_: x.id) | ||||
|         self.set_field("id", lambda x, *_: x.id) | ||||
|         self.set_field("interval", lambda x, *_: x.interval.value) | ||||
|         self.set_field("name", lambda x, *_: x.name) | ||||
|         self.set_field("description", lambda x, *_: x.description) | ||||
|         self.set_field("channel_id", lambda x, *_: x.channel_id) | ||||
|         self.set_field("start_time", lambda x, *_: x.start_time) | ||||
|         self.set_field("end_time", lambda x, *_: x.end_time) | ||||
|         self.set_field("entity_type", lambda x, *_: x.entity_type) | ||||
|         self.set_field("location", lambda x, *_: x.location) | ||||
|         self.set_field("server", lambda x, *_: x.server) | ||||
							
								
								
									
										23
									
								
								bot/src/bot_graphql/queries/scheduled_event_query.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								bot/src/bot_graphql/queries/scheduled_event_query.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| from cpl_core.database.context import DatabaseContextABC | ||||
|  | ||||
| from bot_data.model.scheduled_event_history import ScheduledEventHistory | ||||
| from bot_graphql.abc.data_query_with_history_abc import DataQueryWithHistoryABC | ||||
|  | ||||
|  | ||||
| class ScheduledEventQuery(DataQueryWithHistoryABC): | ||||
|     def __init__( | ||||
|         self, | ||||
|         db: DatabaseContextABC, | ||||
|     ): | ||||
|         DataQueryWithHistoryABC.__init__(self, "ScheduledEvent", "ScheduledEventsHistory", ScheduledEventHistory, db) | ||||
|  | ||||
|         self.set_field("id", lambda x, *_: x.id) | ||||
|         self.set_field("interval", lambda x, *_: x.interval.value) | ||||
|         self.set_field("name", lambda x, *_: x.name) | ||||
|         self.set_field("description", lambda x, *_: x.description) | ||||
|         self.set_field("channelId", lambda x, *_: x.channel_id) | ||||
|         self.set_field("startTime", lambda x, *_: x.start_time) | ||||
|         self.set_field("endTime", lambda x, *_: x.end_time) | ||||
|         self.set_field("entityType", lambda x, *_: x.entity_type.value) | ||||
|         self.set_field("location", lambda x, *_: x.location) | ||||
|         self.set_field("server", lambda x, *_: x.server) | ||||
| @@ -9,6 +9,7 @@ from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC | ||||
| from bot_data.abc.client_repository_abc import ClientRepositoryABC | ||||
| from bot_data.abc.game_server_repository_abc import GameServerRepositoryABC | ||||
| from bot_data.abc.level_repository_abc import LevelRepositoryABC | ||||
| from bot_data.abc.scheduled_event_repository_abc import ScheduledEventRepositoryABC | ||||
| from bot_data.abc.server_config_repository_abc import ServerConfigRepositoryABC | ||||
| from bot_data.abc.short_role_name_repository_abc import ShortRoleNameRepositoryABC | ||||
| from bot_data.abc.user_joined_server_repository_abc import UserJoinedServerRepositoryABC | ||||
| @@ -24,6 +25,7 @@ from bot_graphql.filter.achievement_filter import AchievementFilter | ||||
| from bot_graphql.filter.auto_role_filter import AutoRoleFilter | ||||
| from bot_graphql.filter.client_filter import ClientFilter | ||||
| from bot_graphql.filter.level_filter import LevelFilter | ||||
| from bot_graphql.filter.scheduled_event_filter import ScheduledEventFilter | ||||
| from bot_graphql.filter.short_role_name_filter import ShortRoleNameFilter | ||||
| from bot_graphql.filter.user_filter import UserFilter | ||||
| from bot_graphql.model.server_statistics import ServerStatistics | ||||
| @@ -44,6 +46,7 @@ class ServerQuery(DataQueryWithHistoryABC): | ||||
|         ujvs: UserJoinedVoiceChannelRepositoryABC, | ||||
|         achievements: AchievementRepositoryABC, | ||||
|         short_role_names: ShortRoleNameRepositoryABC, | ||||
|         scheduled_events: ScheduledEventRepositoryABC, | ||||
|         server_configs: ServerConfigRepositoryABC, | ||||
|     ): | ||||
|         DataQueryWithHistoryABC.__init__(self, "Server", "ServersHistory", ServerHistory, db) | ||||
| @@ -100,6 +103,11 @@ class ServerQuery(DataQueryWithHistoryABC): | ||||
|             lambda server, *_: short_role_names.get_short_role_names_by_server_id(server.id), | ||||
|             ShortRoleNameFilter, | ||||
|         ) | ||||
|         self.add_collection( | ||||
|             "scheduledEvent", | ||||
|             lambda server, *_: scheduled_events.get_scheduled_events_by_server_id(server.id), | ||||
|             ScheduledEventFilter, | ||||
|         ) | ||||
|         self.set_field( | ||||
|             "config", | ||||
|             lambda server, *_: server_configs.get_server_config_by_server(server.id), | ||||
|   | ||||
| @@ -8,6 +8,7 @@ from bot_data.abc.client_repository_abc import ClientRepositoryABC | ||||
| from bot_data.abc.game_server_repository_abc import GameServerRepositoryABC | ||||
| from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC | ||||
| from bot_data.abc.level_repository_abc import LevelRepositoryABC | ||||
| from bot_data.abc.scheduled_event_repository_abc import ScheduledEventRepositoryABC | ||||
| from bot_data.abc.server_repository_abc import ServerRepositoryABC | ||||
| from bot_data.abc.short_role_name_repository_abc import ShortRoleNameRepositoryABC | ||||
| from bot_data.abc.technician_config_repository_abc import TechnicianConfigRepositoryABC | ||||
| @@ -27,6 +28,7 @@ from bot_graphql.filter.auto_role_filter import AutoRoleFilter | ||||
| from bot_graphql.filter.auto_role_rule_filter import AutoRoleRuleFilter | ||||
| from bot_graphql.filter.client_filter import ClientFilter | ||||
| from bot_graphql.filter.level_filter import LevelFilter | ||||
| from bot_graphql.filter.scheduled_event_filter import ScheduledEventFilter | ||||
| from bot_graphql.filter.server_filter import ServerFilter | ||||
| from bot_graphql.filter.short_role_name_filter import ShortRoleNameFilter | ||||
| from bot_graphql.filter.user_filter import UserFilter | ||||
| @@ -59,6 +61,7 @@ class Query(QueryABC): | ||||
|         user_warnings: UserWarningsRepositoryABC, | ||||
|         achievement_service: AchievementService, | ||||
|         technician_config: TechnicianConfigRepositoryABC, | ||||
|         scheduled_events: ScheduledEventRepositoryABC, | ||||
|     ): | ||||
|         QueryABC.__init__(self, "Query") | ||||
|  | ||||
| @@ -95,6 +98,11 @@ class Query(QueryABC): | ||||
|             lambda *_: short_role_names.get_short_role_names(), | ||||
|             ShortRoleNameFilter, | ||||
|         ) | ||||
|         self.add_collection( | ||||
|             "scheduledEvent", | ||||
|             lambda server, *_: scheduled_events.get_scheduled_events_by_server_id(server.id), | ||||
|             ScheduledEventFilter, | ||||
|         ) | ||||
|         self.add_collection( | ||||
|             "userWarning", | ||||
|             lambda *_: user_warnings.get_user_warnings(), | ||||
|   | ||||
| @@ -16,10 +16,10 @@ | ||||
|     "LicenseName": "MIT", | ||||
|     "LicenseDescription": "MIT, see LICENSE for more details.", | ||||
|     "Dependencies": [ | ||||
|       "cpl-core>=1.2.1" | ||||
|       "cpl-core>=1.2.dev410" | ||||
|     ], | ||||
|     "DevDependencies": [ | ||||
|       "cpl-cli>=1.2.1" | ||||
|       "cpl-cli>=1.2.dev410" | ||||
|     ], | ||||
|     "PythonVersion": ">=3.10.4", | ||||
|     "PythonPath": {}, | ||||
|   | ||||
| @@ -7,7 +7,7 @@ from cpl_discord.service.discord_collection_abc import DiscordCollectionABC | ||||
| from bot_core.abc.module_abc import ModuleABC | ||||
| from bot_core.abc.task_abc import TaskABC | ||||
| from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum | ||||
| from modules.base.birthday_watcher import BirthdayWatcher | ||||
| from modules.base.tasks.birthday_watcher import BirthdayWatcher | ||||
| from modules.base.command.afk_command import AFKCommand | ||||
| from modules.base.command.game_server_group import GameServerGroup | ||||
| from modules.base.command.help_command import HelpCommand | ||||
| @@ -45,6 +45,7 @@ from modules.base.events.base_on_voice_state_update_event_scheduled_event_bonus | ||||
| from modules.base.forms.bug_report_form import BugReportForm | ||||
| from modules.base.forms.complaint_form import ComplaintForm | ||||
| from modules.base.helper.base_reaction_handler import BaseReactionHandler | ||||
| from modules.base.tasks.scheduled_events_watcher import ScheduledEventsWatcher | ||||
| from modules.base.service.event_service import EventService | ||||
| from modules.base.service.user_warnings_service import UserWarningsService | ||||
|  | ||||
| @@ -61,6 +62,7 @@ class BaseModule(ModuleABC): | ||||
|         services.add_singleton(EventService) | ||||
|         services.add_transient(UserWarningsService) | ||||
|         services.add_singleton(TaskABC, BirthdayWatcher) | ||||
|         services.add_singleton(TaskABC, ScheduledEventsWatcher) | ||||
|  | ||||
|         # forms | ||||
|         services.add_transient(BugReportForm) | ||||
|   | ||||
							
								
								
									
										26
									
								
								bot/src/modules/base/tasks/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								bot/src/modules/base/tasks/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
| bot sh-edraft.de Discord bot | ||||
| ~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| Discord bot for customers of sh-edraft.de | ||||
|  | ||||
| :copyright: (c) 2022 - 2023 sh-edraft.de | ||||
| :license: MIT, see LICENSE for more details. | ||||
|  | ||||
| """ | ||||
|  | ||||
| __title__ = "modules.base.tasks" | ||||
| __author__ = "Sven Heidemann" | ||||
| __license__ = "MIT" | ||||
| __copyright__ = "Copyright (c) 2022 - 2023 sh-edraft.de" | ||||
| __version__ = "1.2.2" | ||||
|  | ||||
| from collections import namedtuple | ||||
|  | ||||
|  | ||||
| # imports: | ||||
|  | ||||
| VersionInfo = namedtuple("VersionInfo", "major minor micro") | ||||
| version_info = VersionInfo(major="1", minor="2", micro="2") | ||||
							
								
								
									
										126
									
								
								bot/src/modules/base/tasks/scheduled_events_watcher.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								bot/src/modules/base/tasks/scheduled_events_watcher.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | ||||
| import calendar | ||||
| from datetime import datetime, timedelta | ||||
| from zoneinfo import ZoneInfo | ||||
|  | ||||
| from cpl_core.configuration import ConfigurationABC | ||||
| from cpl_core.database.context import DatabaseContextABC | ||||
| from cpl_discord.service import DiscordBotServiceABC | ||||
| from cpl_query.extension import List | ||||
| from cpl_translation import TranslatePipe | ||||
| from discord import Guild, PrivacyLevel | ||||
| from discord.ext import tasks | ||||
| from discord.scheduled_event import ScheduledEvent as DiscordEvent | ||||
|  | ||||
| from bot_core.abc.task_abc import TaskABC | ||||
| from bot_core.logging.task_logger import TaskLogger | ||||
| from bot_core.service.message_service import MessageService | ||||
| from bot_data.abc.scheduled_event_repository_abc import ScheduledEventRepositoryABC | ||||
| from bot_data.abc.server_repository_abc import ServerRepositoryABC | ||||
| from bot_data.model.scheduled_event import ScheduledEvent | ||||
| from bot_data.model.scheduled_event_interval_enum import ScheduledEventIntervalEnum | ||||
|  | ||||
|  | ||||
| class ScheduledEventsWatcher(TaskABC): | ||||
|     def __init__( | ||||
|         self, | ||||
|         config: ConfigurationABC, | ||||
|         logger: TaskLogger, | ||||
|         bot: DiscordBotServiceABC, | ||||
|         db: DatabaseContextABC, | ||||
|         servers: ServerRepositoryABC, | ||||
|         events: ScheduledEventRepositoryABC, | ||||
|         message_service: MessageService, | ||||
|         t: TranslatePipe, | ||||
|     ): | ||||
|         TaskABC.__init__(self) | ||||
|  | ||||
|         self._config = config | ||||
|         self._logger = logger | ||||
|         self._bot = bot | ||||
|         self._db = db | ||||
|         self._servers = servers | ||||
|         self._events = events | ||||
|         self._message_service = message_service | ||||
|         self._t = t | ||||
|  | ||||
|         if not self._is_maintenance(): | ||||
|             self.watch.start() | ||||
|  | ||||
|     def _append_interval(self, interval: ScheduledEventIntervalEnum, ts: datetime) -> datetime: | ||||
|         now = datetime.now() | ||||
|         if ts >= now: | ||||
|             return ts | ||||
|  | ||||
|         if interval == ScheduledEventIntervalEnum.daily: | ||||
|             ts = ts + timedelta(days=1) | ||||
|  | ||||
|         elif interval == ScheduledEventIntervalEnum.weekly: | ||||
|             ts = ts + timedelta(weeks=1) | ||||
|  | ||||
|         elif interval == ScheduledEventIntervalEnum.monthly: | ||||
|             days_in_month = calendar.monthrange(ts.year, ts.month + 1)[1] | ||||
|             ts = ts + timedelta(days=days_in_month) | ||||
|  | ||||
|         elif interval == ScheduledEventIntervalEnum.yearly: | ||||
|             ts = ts + timedelta(days=365) | ||||
|  | ||||
|         return ts | ||||
|  | ||||
|     @tasks.loop(hours=24) | ||||
|     async def watch(self): | ||||
|         self._logger.info(__name__, "Watching scheduled events") | ||||
|         try: | ||||
|             for guild in self._bot.guilds: | ||||
|                 guild: Guild = guild | ||||
|                 server = self._servers.get_server_by_discord_id(guild.id) | ||||
|                 scheduled_events_from_guild = self._events.get_scheduled_events_by_server_id(server.id) | ||||
|                 for scheduled_event in scheduled_events_from_guild: | ||||
|                     scheduled_event: ScheduledEvent = scheduled_event | ||||
|                     from_guild = List(DiscordEvent, guild.scheduled_events).where( | ||||
|                         lambda x: x.name == scheduled_event.name | ||||
|                         and x.description == scheduled_event.description | ||||
|                         and x.entity_type == scheduled_event.entity_type | ||||
|                     ) | ||||
|                     if from_guild.count() != 0: | ||||
|                         continue | ||||
|  | ||||
|                     kwargs = {"name": scheduled_event.name, "description": scheduled_event.description} | ||||
|  | ||||
|                     if scheduled_event.channel_id is not None: | ||||
|                         kwargs["channel"] = guild.get_channel(scheduled_event.channel_id) | ||||
|  | ||||
|                     if scheduled_event.start_time is not None: | ||||
|                         scheduled_event.start_time = self._append_interval( | ||||
|                             scheduled_event.interval, scheduled_event.start_time | ||||
|                         ) | ||||
|  | ||||
|                         start_time = scheduled_event.start_time.replace(tzinfo=ZoneInfo("Europe/Berlin")) | ||||
|  | ||||
|                         kwargs["start_time"] = start_time | ||||
|  | ||||
|                     if scheduled_event.end_time is not None: | ||||
|                         scheduled_event.end_time = self._append_interval( | ||||
|                             scheduled_event.interval, scheduled_event.end_time | ||||
|                         ) | ||||
|                         end_time = scheduled_event.end_time.replace(tzinfo=ZoneInfo("Europe/Berlin")) | ||||
|                         kwargs["end_time"] = end_time | ||||
|  | ||||
|                     kwargs["entity_type"] = scheduled_event.entity_type | ||||
|                     if scheduled_event.location is not None: | ||||
|                         kwargs["location"] = scheduled_event.location | ||||
|  | ||||
|                     kwargs["privacy_level"] = PrivacyLevel.guild_only | ||||
|  | ||||
|                     try: | ||||
|                         self._logger.debug(__name__, f"Try to create scheduled event for guild {guild.name}") | ||||
|                         await guild.create_scheduled_event(**kwargs) | ||||
|                         self._events.update_scheduled_event(scheduled_event) | ||||
|                         self._db.save_changes() | ||||
|                     except Exception as e: | ||||
|                         self._logger.error(__name__, f"Watching scheduled events failed", e) | ||||
|         except Exception as e: | ||||
|             self._logger.error(__name__, f"Watching scheduled events failed", e) | ||||
|  | ||||
|     @watch.before_loop | ||||
|     async def wait(self): | ||||
|         await self._wait_until_ready() | ||||
| @@ -16,10 +16,10 @@ | ||||
|     "LicenseName": "", | ||||
|     "LicenseDescription": "", | ||||
|     "Dependencies": [ | ||||
|       "cpl-core>=1.2.1" | ||||
|       "cpl-core>=1.2.dev410" | ||||
|     ], | ||||
|     "DevDependencies": [ | ||||
|       "cpl-cli>=1.2.1" | ||||
|       "cpl-cli>=1.2.dev410" | ||||
|     ], | ||||
|     "PythonVersion": ">=3.10.4", | ||||
|     "PythonPath": { | ||||
|   | ||||
| @@ -16,10 +16,10 @@ | ||||
|     "LicenseName": "", | ||||
|     "LicenseDescription": "", | ||||
|     "Dependencies": [ | ||||
|       "cpl-core>=1.2.1" | ||||
|       "cpl-core>=1.2.dev410" | ||||
|     ], | ||||
|     "DevDependencies": [ | ||||
|       "cpl-cli>=1.2.1" | ||||
|       "cpl-cli>=1.2.dev410" | ||||
|     ], | ||||
|     "PythonVersion": ">=3.10.4", | ||||
|     "PythonPath": { | ||||
|   | ||||
| @@ -16,10 +16,10 @@ | ||||
|     "LicenseName": "", | ||||
|     "LicenseDescription": "", | ||||
|     "Dependencies": [ | ||||
|       "cpl-core>=1.2.1" | ||||
|       "cpl-core>=1.2.dev410" | ||||
|     ], | ||||
|     "DevDependencies": [ | ||||
|       "cpl-cli>=1.2.1" | ||||
|       "cpl-cli>=1.2.dev410" | ||||
|     ], | ||||
|     "PythonVersion": ">=3.10.4", | ||||
|     "PythonPath": { | ||||
|   | ||||
| @@ -2,9 +2,9 @@ | ||||
|   "ProjectSettings": { | ||||
|     "Name": "migration-to-sql", | ||||
|     "Version": { | ||||
|       "Major": "0", | ||||
|       "Minor": "0", | ||||
|       "Micro": "0" | ||||
|       "Major": "1", | ||||
|       "Minor": "2", | ||||
|       "Micro": "2" | ||||
|     }, | ||||
|     "Author": "", | ||||
|     "AuthorEmail": "", | ||||
| @@ -16,10 +16,10 @@ | ||||
|     "LicenseName": "", | ||||
|     "LicenseDescription": "", | ||||
|     "Dependencies": [ | ||||
|       "cpl-core>=2023.10.0" | ||||
|       "cpl-core>=1.2.dev410" | ||||
|     ], | ||||
|     "DevDependencies": [ | ||||
|       "cpl-cli>=2023.4.0.post3" | ||||
|       "cpl-cli>=1.2.dev410" | ||||
|     ], | ||||
|     "PythonVersion": ">=3.10.4", | ||||
|     "PythonPath": { | ||||
|   | ||||
							
								
								
									
										110
									
								
								web/package.json
									
									
									
									
									
								
							
							
						
						
									
										110
									
								
								web/package.json
									
									
									
									
									
								
							| @@ -1,56 +1,56 @@ | ||||
| { | ||||
|   "name": "web", | ||||
|   "version": "1.2.2", | ||||
|   "scripts": { | ||||
|     "ng": "ng", | ||||
|     "update-version": "ts-node update-version.ts", | ||||
|     "prestart": "npm run update-version", | ||||
|     "start": "ng serve", | ||||
|     "prebuild": "npm run update-version", | ||||
|     "build": "ng build", | ||||
|     "watch": "ng build --watch --configuration development", | ||||
|     "test": "ng test", | ||||
|     "gv": "echo $npm_package_version", | ||||
|     "predocker-build": "npm run update-version", | ||||
|     "docker-build": "export VERSION=$npm_package_version; ng build; docker build -t sh-edraft.de/sdb-web:$VERSION .", | ||||
|     "docker-build-dev": "export VERSION=$npm_package_version; ng build --configuration development; docker build -t sh-edraft.de/sdb-web:$VERSION .", | ||||
|     "docker-build-stage": "export VERSION=$npm_package_version; ng build --configuration staging; docker build -t sh-edraft.de/sdb-web:$VERSION ." | ||||
|   }, | ||||
|   "private": true, | ||||
|   "dependencies": { | ||||
|     "@angular/animations": "^15.1.4", | ||||
|     "@angular/common": "^15.1.4", | ||||
|     "@angular/compiler": "^15.1.4", | ||||
|     "@angular/core": "^15.1.4", | ||||
|     "@angular/forms": "^15.1.4", | ||||
|     "@angular/platform-browser": "^15.1.4", | ||||
|     "@angular/platform-browser-dynamic": "^15.1.4", | ||||
|     "@angular/router": "^15.1.4", | ||||
|     "@auth0/angular-jwt": "^5.1.0", | ||||
|     "@microsoft/signalr": "^6.0.9", | ||||
|     "@ngx-translate/core": "^14.0.0", | ||||
|     "@ngx-translate/http-loader": "^7.0.0", | ||||
|     "@types/socket.io-client": "^3.0.0", | ||||
|     "moment": "^2.29.4", | ||||
|     "primeicons": "^6.0.1", | ||||
|     "primeng": "^15.2.0", | ||||
|     "rxjs": "~7.5.0", | ||||
|     "socket.io-client": "^4.5.3", | ||||
|     "zone.js": "~0.11.4" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@angular-devkit/build-angular": "^15.1.5", | ||||
|     "@angular/cli": "~15.1.5", | ||||
|     "@angular/compiler-cli": "^15.1.4", | ||||
|     "@types/jasmine": "~4.0.0", | ||||
|     "@types/node": "^18.11.9", | ||||
|     "jasmine-core": "~4.1.0", | ||||
|     "karma": "~6.3.0", | ||||
|     "karma-chrome-launcher": "~3.1.0", | ||||
|     "karma-coverage": "~2.2.0", | ||||
|     "karma-jasmine": "~5.0.0", | ||||
|     "karma-jasmine-html-reporter": "~1.7.0", | ||||
|     "tslib": "^2.4.1", | ||||
|     "typescript": "~4.9.5" | ||||
|   } | ||||
| } | ||||
|     "name": "web", | ||||
|     "version": "1.2.dev410", | ||||
|     "scripts": { | ||||
|         "ng": "ng", | ||||
|         "update-version": "ts-node update-version.ts", | ||||
|         "prestart": "npm run update-version", | ||||
|         "start": "ng serve", | ||||
|         "prebuild": "npm run update-version", | ||||
|         "build": "ng build", | ||||
|         "watch": "ng build --watch --configuration development", | ||||
|         "test": "ng test", | ||||
|         "gv": "echo $npm_package_version", | ||||
|         "predocker-build": "npm run update-version", | ||||
|         "docker-build": "export VERSION=$npm_package_version; ng build; docker build -t sh-edraft.de/sdb-web:$VERSION .", | ||||
|         "docker-build-dev": "export VERSION=$npm_package_version; ng build --configuration development; docker build -t sh-edraft.de/sdb-web:$VERSION .", | ||||
|         "docker-build-stage": "export VERSION=$npm_package_version; ng build --configuration staging; docker build -t sh-edraft.de/sdb-web:$VERSION ." | ||||
|     }, | ||||
|     "private": true, | ||||
|     "dependencies": { | ||||
|         "@angular/animations": "^15.1.4", | ||||
|         "@angular/common": "^15.1.4", | ||||
|         "@angular/compiler": "^15.1.4", | ||||
|         "@angular/core": "^15.1.4", | ||||
|         "@angular/forms": "^15.1.4", | ||||
|         "@angular/platform-browser": "^15.1.4", | ||||
|         "@angular/platform-browser-dynamic": "^15.1.4", | ||||
|         "@angular/router": "^15.1.4", | ||||
|         "@auth0/angular-jwt": "^5.1.0", | ||||
|         "@microsoft/signalr": "^6.0.9", | ||||
|         "@ngx-translate/core": "^14.0.0", | ||||
|         "@ngx-translate/http-loader": "^7.0.0", | ||||
|         "@types/socket.io-client": "^3.0.0", | ||||
|         "moment": "^2.29.4", | ||||
|         "primeicons": "^6.0.1", | ||||
|         "primeng": "^15.2.0", | ||||
|         "rxjs": "~7.5.0", | ||||
|         "socket.io-client": "^4.5.3", | ||||
|         "zone.js": "~0.11.4" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "@angular-devkit/build-angular": "^15.1.5", | ||||
|         "@angular/cli": "~15.1.5", | ||||
|         "@angular/compiler-cli": "^15.1.4", | ||||
|         "@types/jasmine": "~4.0.0", | ||||
|         "@types/node": "^18.11.9", | ||||
|         "jasmine-core": "~4.1.0", | ||||
|         "karma": "~6.3.0", | ||||
|         "karma-chrome-launcher": "~3.1.0", | ||||
|         "karma-coverage": "~2.2.0", | ||||
|         "karma-jasmine": "~5.0.0", | ||||
|         "karma-jasmine-html-reporter": "~1.7.0", | ||||
|         "tslib": "^2.4.1", | ||||
|         "typescript": "~4.9.5" | ||||
|     } | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| export  interface Discord { | ||||
| export interface Discord { | ||||
|   guilds?: Guild[]; | ||||
|   users?: DiscordUser[]; | ||||
| } | ||||
| @@ -21,7 +21,8 @@ export interface Channel { | ||||
| export enum ChannelType { | ||||
|   category = "CategoryChannel", | ||||
|   text = "TextChannel", | ||||
|   voice = "VoiceChannel" | ||||
|   voice = "VoiceChannel", | ||||
|   stage = "StageChannel" | ||||
| } | ||||
|  | ||||
| export interface Role { | ||||
|   | ||||
							
								
								
									
										43
									
								
								web/src/app/models/data/scheduled_events.model.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								web/src/app/models/data/scheduled_events.model.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| import { DataWithHistory } from "./data.model"; | ||||
| import { Server, ServerFilter } from "./server.model"; | ||||
|  | ||||
| export enum EventType { | ||||
|   stageInstance = 1, | ||||
|   voice = 2, | ||||
|   external = 3, | ||||
| } | ||||
|  | ||||
| export interface ScheduledEvent extends DataWithHistory { | ||||
|   id?: number; | ||||
|   interval?: ScheduledEventInterval; | ||||
|   name?: string; | ||||
|   description?: string; | ||||
|   channelId?: string; | ||||
|   startTime?: Date; | ||||
|   endTime?: Date; | ||||
|   entityType?: EventType; | ||||
|   location?: string; | ||||
|   server?: Server; | ||||
| } | ||||
|  | ||||
| export interface ScheduledEventFilter { | ||||
|   id?: number; | ||||
|   interval?: string; | ||||
|   name?: string; | ||||
|   description?: string; | ||||
|   channelId?: string; | ||||
|   startTime?: string; | ||||
|   endTime?: string; | ||||
|   entityType?: number; | ||||
|   location?: string; | ||||
|   server?: ServerFilter; | ||||
| } | ||||
|  | ||||
| // export enum ScheduledEventInterval { | ||||
| //   daily = "daily", | ||||
| //   weekly = "weekly", | ||||
| //   monthly = "monthly", | ||||
| //   yearly = "yearly" | ||||
| // } | ||||
|  | ||||
| export type ScheduledEventInterval = "daily" | "weekly" | "monthly" | "month" | ||||
| @@ -173,6 +173,78 @@ export class Mutations { | ||||
|     } | ||||
|   `; | ||||
|  | ||||
|   static createScheduledEvent = ` | ||||
|     mutation createScheduledEvent($interval: String,$name: String,$description: String,$channelId: String,$startTime: String, $endTime: String,$entityType: Int,$location: String, $serverId: ID) { | ||||
|       scheduledEvent { | ||||
|         createScheduledEvent(input: { | ||||
|           interval: $interval, | ||||
|           name: $name, | ||||
|           description: $description, | ||||
|           channelId: $channelId, | ||||
|           startTime: $startTime, | ||||
|           endTime: $endTime, | ||||
|           entityType: $entityType, | ||||
|           location: $location, | ||||
|           serverId: $serverId | ||||
|         }) { | ||||
|           interval | ||||
|           name | ||||
|           description | ||||
|           channelId | ||||
|           startTime | ||||
|           endTime | ||||
|           entityType | ||||
|           location | ||||
|           server { | ||||
|             id | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   `; | ||||
|  | ||||
|   static updateScheduledEvent = ` | ||||
|     mutation updateScheduledEvent($id: ID, $interval: String,$name: String,$description: String,$channelId: String,$startTime: String, $endTime: String,$entityType: Int,$location: String, $serverId: ID) { | ||||
|       scheduledEvent { | ||||
|         updateScheduledEvent(input: { | ||||
|           id: $id, | ||||
|           interval: $interval, | ||||
|           name: $name, | ||||
|           description: $description, | ||||
|           channelId: $channelId, | ||||
|           startTime: $startTime, | ||||
|           endTime: $endTime, | ||||
|           entityType: $entityType, | ||||
|           location: $location, | ||||
|           serverId: $serverId} | ||||
|         ) { | ||||
|           interval | ||||
|           name | ||||
|           description | ||||
|           channelId | ||||
|           startTime | ||||
|           endTime | ||||
|           entityType | ||||
|           location | ||||
|           server { | ||||
|             id | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   `; | ||||
|  | ||||
|   static deleteScheduledEvent = ` | ||||
|     mutation deleteScheduledEvent($id: ID) { | ||||
|       scheduledEvent { | ||||
|         deleteScheduledEvent(id: $id) { | ||||
|           id | ||||
|           name | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   `; | ||||
|  | ||||
|   static createShortRoleName = ` | ||||
|     mutation createShortRoleName($shortName: String, $roleId: String, $position: String, $serverId: ID) { | ||||
|       shortRoleName { | ||||
| @@ -333,7 +405,6 @@ export class Mutations { | ||||
|   `; | ||||
|  | ||||
|  | ||||
|  | ||||
|   static createUserWarning = ` | ||||
|     mutation createUserWarning($name: String, $description: String, $attribute: String, $operator: String, $value: String, $serverId: ID) { | ||||
|       userWarning { | ||||
|   | ||||
| @@ -229,6 +229,58 @@ export class Queries { | ||||
|     } | ||||
|   `; | ||||
|  | ||||
|   static scheduledEventQuery = ` | ||||
|     query ScheduledEventList($serverId: ID, $filter: ScheduledEventFilter, $page: Page, $sort: Sort) { | ||||
|       servers(filter: {id: $serverId}) { | ||||
|         scheduledEventCount | ||||
|         scheduledEvents(filter: $filter, page: $page, sort: $sort) { | ||||
|           id | ||||
|           interval | ||||
|           name | ||||
|           description | ||||
|           channelId | ||||
|           startTime | ||||
|           endTime | ||||
|           entityType | ||||
|           location | ||||
|           server { | ||||
|             id | ||||
|             name | ||||
|           } | ||||
|           createdAt | ||||
|           modifiedAt | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   `; | ||||
|  | ||||
|   static scheduledEventWithHistoryQuery = ` | ||||
|     query ScheduledEventHistory($serverId: ID, $id: ID) { | ||||
|       servers(filter: {id: $serverId}) { | ||||
|         scheduledEventCount | ||||
|         scheduledEvents(filter: {id: $id}) { | ||||
|           id | ||||
|  | ||||
|           history { | ||||
|             id | ||||
|             interval | ||||
|             name | ||||
|             description | ||||
|             channelId | ||||
|             startTime | ||||
|             endTime | ||||
|             entityType | ||||
|             location | ||||
|             server | ||||
|             deleted | ||||
|             dateFrom | ||||
|             dateTo | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   `; | ||||
|  | ||||
|  | ||||
|   static shortRoleNamePositionsQuery = ` | ||||
|     query { | ||||
|   | ||||
| @@ -9,6 +9,7 @@ import { ServerConfig } from "../config/server-config.model"; | ||||
| import { ShortRoleName } from "../data/short_role_name.model"; | ||||
| import { FeatureFlag } from "../config/feature-flags.model"; | ||||
| import { UserWarning } from "../data/user_warning.model"; | ||||
| import { ScheduledEvent } from "../data/scheduled_events.model"; | ||||
|  | ||||
| export interface Query { | ||||
|   serverCount: number; | ||||
| @@ -57,6 +58,11 @@ export interface AchievementListQuery { | ||||
|   achievements: Achievement[]; | ||||
| } | ||||
|  | ||||
| export interface ScheduledEventListQuery { | ||||
|   scheduledEventCount: number; | ||||
|   scheduledEvents: ScheduledEvent[]; | ||||
| } | ||||
|  | ||||
| export interface AutoRoleQuery { | ||||
|   autoRoleCount: number; | ||||
|   autoRoles: AutoRole[]; | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import { TechnicianConfig } from "../config/technician-config.model"; | ||||
| import { ServerConfig } from "../config/server-config.model"; | ||||
| import { ShortRoleName } from "../data/short_role_name.model"; | ||||
| import { UserWarning } from "../data/user_warning.model"; | ||||
| import { ScheduledEvent } from "../data/scheduled_events.model"; | ||||
|  | ||||
| export interface GraphQLResult { | ||||
|   data: { | ||||
| @@ -71,6 +72,14 @@ export interface AchievementMutationResult { | ||||
|   }; | ||||
| } | ||||
|  | ||||
| export interface ScheduledEventMutationResult { | ||||
|   scheduledEvent: { | ||||
|     createScheduledEvent?: ScheduledEvent | ||||
|     updateScheduledEvent?: ScheduledEvent | ||||
|     deleteScheduledEvent?: ScheduledEvent | ||||
|   }; | ||||
| } | ||||
|  | ||||
| export interface ShortRoleNameMutationResult { | ||||
|   shortRoleName: { | ||||
|     createShortRoleName?: ShortRoleName | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { CommonModule } from "@angular/common"; | ||||
| import { CommonModule, DatePipe } from "@angular/common"; | ||||
| import { HttpClientModule } from "@angular/common/http"; | ||||
| import { NgModule } from "@angular/core"; | ||||
| import { FormsModule, ReactiveFormsModule } from "@angular/forms"; | ||||
| @@ -13,7 +13,7 @@ import { InputTextModule } from "primeng/inputtext"; | ||||
| import { MenuModule } from "primeng/menu"; | ||||
| import { PasswordModule } from "primeng/password"; | ||||
| import { ProgressSpinnerModule } from "primeng/progressspinner"; | ||||
| import { SortableColumn, TableModule } from "primeng/table"; | ||||
| import { TableModule } from "primeng/table"; | ||||
| import { ToastModule } from "primeng/toast"; | ||||
| import { AuthRolePipe } from "./pipes/auth-role.pipe"; | ||||
| import { IpAddressPipe } from "./pipes/ip-address.pipe"; | ||||
| @@ -24,18 +24,22 @@ import { InputNumberModule } from "primeng/inputnumber"; | ||||
| import { ImageModule } from "primeng/image"; | ||||
| import { SidebarModule } from "primeng/sidebar"; | ||||
| import { HistoryBtnComponent } from "./components/history-btn/history-btn.component"; | ||||
| import { DataViewModule, DataViewLayoutOptions } from "primeng/dataview"; | ||||
| import { DataViewLayoutOptions, DataViewModule } from "primeng/dataview"; | ||||
| import { ConfigListComponent } from "./components/config-list/config-list.component"; | ||||
| import { MultiSelectModule } from "primeng/multiselect"; | ||||
| import { HideableColumnComponent } from './components/hideable-column/hideable-column.component'; | ||||
| import { HideableHeaderComponent } from './components/hideable-header/hideable-header.component'; | ||||
| import { MultiSelectColumnsComponent } from './base/multi-select-columns/multi-select-columns.component'; | ||||
| import { FeatureFlagListComponent } from './components/feature-flag-list/feature-flag-list.component'; | ||||
| import { HideableColumnComponent } from "./components/hideable-column/hideable-column.component"; | ||||
| import { HideableHeaderComponent } from "./components/hideable-header/hideable-header.component"; | ||||
| import { MultiSelectColumnsComponent } from "./base/multi-select-columns/multi-select-columns.component"; | ||||
| import { FeatureFlagListComponent } from "./components/feature-flag-list/feature-flag-list.component"; | ||||
| import { InputSwitchModule } from "primeng/inputswitch"; | ||||
| import { CalendarModule } from "primeng/calendar"; | ||||
| import { DataImportAndExportComponent } from './components/data-import-and-export/data-import-and-export.component'; | ||||
| import { DataImportAndExportComponent } from "./components/data-import-and-export/data-import-and-export.component"; | ||||
| import { FileUploadModule } from "primeng/fileupload"; | ||||
| import { SelectButtonModule } from "primeng/selectbutton"; | ||||
| import { TabViewModule } from "primeng/tabview"; | ||||
| import { RadioButtonModule } from "primeng/radiobutton"; | ||||
| import { InputTextareaModule } from "primeng/inputtextarea"; | ||||
| import { InputMaskModule } from "primeng/inputmask"; | ||||
|  | ||||
|  | ||||
| const PrimeNGModules = [ | ||||
| @@ -66,7 +70,11 @@ const PrimeNGModules = [ | ||||
|   CalendarModule, | ||||
|   FileUploadModule, | ||||
|   SelectButtonModule, | ||||
| ] | ||||
|   TabViewModule, | ||||
|   RadioButtonModule, | ||||
|   InputTextareaModule, | ||||
|   InputMaskModule | ||||
| ]; | ||||
|  | ||||
| @NgModule({ | ||||
|   declarations: [ | ||||
| @@ -79,7 +87,7 @@ const PrimeNGModules = [ | ||||
|     HideableHeaderComponent, | ||||
|     MultiSelectColumnsComponent, | ||||
|     FeatureFlagListComponent, | ||||
|     DataImportAndExportComponent, | ||||
|     DataImportAndExportComponent | ||||
|   ], | ||||
|   imports: [ | ||||
|     CommonModule, | ||||
| @@ -98,6 +106,9 @@ const PrimeNGModules = [ | ||||
|     MultiSelectColumnsComponent, | ||||
|     FeatureFlagListComponent, | ||||
|     DataImportAndExportComponent | ||||
|   ], | ||||
|   providers: [ | ||||
|     DatePipe | ||||
|   ] | ||||
| }) | ||||
| export class SharedModule { | ||||
|   | ||||
| @@ -0,0 +1,92 @@ | ||||
| <div class="edit-dialog"> | ||||
|   <p-dialog [contentStyle]="{'overflow':'visible'}" [(visible)]="visible" [header]="header" [modal]="true" | ||||
|             [draggable]="false" [resizable]="false" | ||||
|             [style]="{ width: '500px', height: '575px' }"> | ||||
|     <form [formGroup]="inputForm"> | ||||
|       <p-tabView [(activeIndex)]="activeIndex"> | ||||
|         <p-tabPanel header="{{'view.server.scheduled_events.edit_dialog.location.tab_name' | translate}}"> | ||||
|           <div class="form"> | ||||
|             <div style="display: flex; flex-direction: column; gap: 5px;"> | ||||
|               {{'view.server.scheduled_events.edit_dialog.location.interval' | translate}}: | ||||
|               <p-dropdown [options]="interval" | ||||
|                           formControlName="interval" | ||||
|                           placeholder="{{'view.server.scheduled_events.edit_dialog.location.interval' | translate}}"></p-dropdown> | ||||
|             </div> | ||||
|  | ||||
|             <div class="type"> | ||||
|               <div *ngFor="let enum of EventType | keyvalue" class="field-checkbox"> | ||||
|                 <p-radioButton [inputId]="enum.key" [value]="enum.value" formControlName="entityType"></p-radioButton> | ||||
|                 <label [for]="enum.key" | ||||
|                        class="ml-2">{{'view.server.scheduled_events.edit_dialog.location.' + enum.key | translate}}</label> | ||||
|               </div> | ||||
|             </div> | ||||
|  | ||||
|             <div class="input"> | ||||
|               <p-dropdown *ngIf="inputForm.controls.entityType.value == 1" | ||||
|                           [options]="stageChannels" | ||||
|                           optionLabel="name" | ||||
|                           optionValue="id" | ||||
|                           formControlName="channelId" | ||||
|                           placeholder="{{'view.server.scheduled_events.edit_dialog.location.stage_input' | translate}}"></p-dropdown> | ||||
|               <p-dropdown *ngIf="inputForm.controls.entityType.value == 2" | ||||
|                           [options]="voiceChannels" | ||||
|                           optionLabel="name" | ||||
|                           optionValue="id" | ||||
|                           formControlName="channelId" | ||||
|                           placeholder="{{'view.server.scheduled_events.edit_dialog.location.voice_input' | translate}}"></p-dropdown> | ||||
|               <input *ngIf="inputForm.controls.entityType.value == 3" pInputText class="table-edit-input" | ||||
|                      formControlName="location" | ||||
|                      placeholder="{{'view.server.scheduled_events.edit_dialog.location.somewhere_else' | translate}}"> | ||||
|             </div> | ||||
|           </div> | ||||
|         </p-tabPanel> | ||||
|         <p-tabPanel header="{{'view.server.scheduled_events.edit_dialog.event_info.tab_name' | translate}}"> | ||||
|           <div class="form"> | ||||
|             <div style="display: flex; flex-direction: column; gap: 5px;"> | ||||
|               {{'view.server.scheduled_events.edit_dialog.event_info.event_topic' | translate}}: | ||||
|               <input pInputText class="table-edit-input" | ||||
|                      formControlName="name" | ||||
|                      placeholder="{{'view.server.scheduled_events.edit_dialog.event_info.event_topic_input' | translate}}"> | ||||
|             </div> | ||||
|  | ||||
|             <div style="display: flex; flex-direction: column; gap: 5px;"> | ||||
|               {{'view.server.scheduled_events.edit_dialog.event_info.start_date_time' | translate}}: | ||||
|               <p-calendar formControlName="startTime" dateFormat="dd.mm.yy" [showIcon]="true" | ||||
|                           [showTime]="true" [stepMinute]="15"></p-calendar> | ||||
|             </div> | ||||
|  | ||||
|             <div style="display: flex; flex-direction: column; gap: 5px;"> | ||||
|               {{'view.server.scheduled_events.edit_dialog.event_info.end_date_time' | translate}}: | ||||
|               <p-calendar formControlName="endTime" dateFormat="dd.mm.yy" [showIcon]="true" | ||||
|                           [showTime]="true"></p-calendar> | ||||
|             </div> | ||||
|  | ||||
|             <div style="display: flex; flex-direction: column; gap: 5px;"> | ||||
|               {{'view.server.scheduled_events.edit_dialog.event_info.description' | translate}}: | ||||
|               <textarea rows="5" cols="30" pInputTextarea [autoResize]="true" | ||||
|                         class="table-edit-input" | ||||
|                         formControlName="description" | ||||
|                         placeholder="{{'view.server.scheduled_events.edit_dialog.event_info.description_input' | translate}}"></textarea> | ||||
|             </div> | ||||
|           </div> | ||||
|         </p-tabPanel> | ||||
|       </p-tabView> | ||||
|     </form> | ||||
|     <ng-template pTemplate="footer"> | ||||
|       <div style="display: flex;"> | ||||
|         <div class="btn-wrapper" style="flex: 1;"> | ||||
|           <button pButton label="{{'common.back' | translate}}" class="text-btn" | ||||
|                   (click)="back()" [disabled]="activeIndex == 0"></button> | ||||
|         </div> | ||||
|         <div class="btn-wrapper"> | ||||
|           <button pButton label="{{'common.abort' | translate}}" class="btn danger-btn" | ||||
|                   (click)="event = undefined"></button> | ||||
|           <button *ngIf="activeIndex < 1" pButton label="{{'common.continue' | translate}}" class="btn" | ||||
|                   (click)="next()" [disabled]="!inputForm"></button> | ||||
|           <button *ngIf="activeIndex == 1" pButton label="{{'common.save' | translate}}" class="btn" | ||||
|                   (click)="saveEvent()" [disabled]="!inputForm.valid"></button> | ||||
|         </div> | ||||
|       </div> | ||||
|     </ng-template> | ||||
|   </p-dialog> | ||||
| </div> | ||||
| @@ -0,0 +1,23 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
|  | ||||
| import { EditScheduledEventDialogComponent } from './edit-scheduled-event-dialog.component'; | ||||
|  | ||||
| describe('EditScheduledEventDialogComponent', () => { | ||||
|   let component: EditScheduledEventDialogComponent; | ||||
|   let fixture: ComponentFixture<EditScheduledEventDialogComponent>; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ EditScheduledEventDialogComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|  | ||||
|     fixture = TestBed.createComponent(EditScheduledEventDialogComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,200 @@ | ||||
| import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from "@angular/core"; | ||||
| import { | ||||
|   EventType, | ||||
|   ScheduledEvent, | ||||
|   ScheduledEventInterval | ||||
| } from "../../../../../../models/data/scheduled_events.model"; | ||||
| import { TranslateService } from "@ngx-translate/core"; | ||||
| import { FormBuilder, FormControl, FormGroup, Validators } from "@angular/forms"; | ||||
| import { SingleDiscordQuery } from "../../../../../../models/graphql/query.model"; | ||||
| import { Queries } from "../../../../../../models/graphql/queries.model"; | ||||
| import { Channel, ChannelType, Guild } from "../../../../../../models/data/discord.model"; | ||||
| import { DataService } from "../../../../../../services/data/data.service"; | ||||
| import { SpinnerService } from "../../../../../../services/spinner/spinner.service"; | ||||
| import { ActivatedRoute } from "@angular/router"; | ||||
| import { Server } from "../../../../../../models/data/server.model"; | ||||
| import { DatePipe } from "@angular/common"; | ||||
|  | ||||
| @Component({ | ||||
|   selector: "app-edit-scheduled-event-dialog", | ||||
|   templateUrl: "./edit-scheduled-event-dialog.component.html", | ||||
|   styleUrls: ["./edit-scheduled-event-dialog.component.scss"] | ||||
| }) | ||||
| export class EditScheduledEventDialogComponent implements OnInit, OnChanges { | ||||
|   public _event?: ScheduledEvent; | ||||
|  | ||||
|   @Input() | ||||
|   set event(event: ScheduledEvent | undefined) { | ||||
|     this._event = event; | ||||
|     this.eventChange.emit(event); | ||||
|   } | ||||
|  | ||||
|   get event(): ScheduledEvent | undefined { | ||||
|     return this._event; | ||||
|   } | ||||
|  | ||||
|   @Output() eventChange: EventEmitter<ScheduledEvent> = new EventEmitter<ScheduledEvent>(); | ||||
|  | ||||
|   get header() { | ||||
|     if (this.event && this.event.createdAt === "" && this.event.modifiedAt === "") { | ||||
|       return this.translate.instant("view.server.scheduled_events.edit_dialog.add_header"); | ||||
|     } else { | ||||
|       return this.translate.instant("view.server.scheduled_events.edit_dialog.edit_header"); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   get visible() { | ||||
|     return this.event != undefined; | ||||
|   } | ||||
|  | ||||
|   set visible(val: boolean) { | ||||
|     if (!val) { | ||||
|       this.event = undefined; | ||||
|       this.inputForm.reset(); | ||||
|       this.activeIndex = 0; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   public activeIndex: number = 0; | ||||
|   public inputForm!: FormGroup<{ | ||||
|     entityType: FormControl<number | undefined | null>; | ||||
|     name: FormControl<string | undefined | null>; | ||||
|     description: FormControl<string | undefined | null>; | ||||
|     interval: FormControl<ScheduledEventInterval | undefined | null>; | ||||
|     location: FormControl<string | undefined | null>; | ||||
|     startTime: FormControl<Date | undefined | null>; | ||||
|     id: FormControl<number | undefined | null>; | ||||
|     endTime: FormControl<Date | undefined | null>; | ||||
|     channelId: FormControl<string | undefined | null> | ||||
|   }>; | ||||
|   server: Server = {}; | ||||
|   public voiceChannels: Channel[] = []; | ||||
|   public stageChannels: Channel[] = []; | ||||
|   public guild: Guild = { channels: [], emojis: [], roles: [] }; | ||||
|   public times: string[] = []; | ||||
|   public now = new Date(); | ||||
|   public interval = [ | ||||
|     { | ||||
|       label: this.translate.instant("view.server.scheduled_events.edit_dialog.location.intervals.daily"), | ||||
|       value: "daily" | ||||
|     }, | ||||
|     { | ||||
|       label: this.translate.instant("view.server.scheduled_events.edit_dialog.location.intervals.weekly"), | ||||
|       value: "weekly" | ||||
|     }, | ||||
|     { | ||||
|       label: this.translate.instant("view.server.scheduled_events.edit_dialog.location.intervals.monthly"), | ||||
|       value: "monthly" | ||||
|     }, | ||||
|     { | ||||
|       label: this.translate.instant("view.server.scheduled_events.edit_dialog.location.intervals.yearly"), | ||||
|       value: "yearly" | ||||
|     } | ||||
|   ]; | ||||
|  | ||||
|   constructor( | ||||
|     private translate: TranslateService, | ||||
|     private fb: FormBuilder, | ||||
|     private data: DataService, | ||||
|     private spinner: SpinnerService, | ||||
|     private route: ActivatedRoute, | ||||
|     private datePipe: DatePipe | ||||
|   ) { | ||||
|     for (let i = 0; i < 25; i++) { | ||||
|       let time = ""; | ||||
|       if (i < 10) { | ||||
|         time = `0${i}`; | ||||
|       } else { | ||||
|         time = `${i}`; | ||||
|       } | ||||
|       this.times.push(`${time}:00`); | ||||
|       this.times.push(`${time}:15`); | ||||
|       this.times.push(`${time}:30`); | ||||
|       this.times.push(`${time}:45`); | ||||
|     } | ||||
|     this.setInputForm(); | ||||
|   } | ||||
|  | ||||
|   public ngOnChanges() { | ||||
|     this.setInputForm(); | ||||
|   } | ||||
|  | ||||
|   public ngOnInit() { | ||||
|     this.data.getServerFromRoute(this.route).then(server => { | ||||
|       this.server = server; | ||||
|       this.spinner.showSpinner(); | ||||
|  | ||||
|       this.data.query<SingleDiscordQuery>(Queries.guildsQuery, { | ||||
|           id: server?.discordId | ||||
|         } | ||||
|       ).subscribe(data => { | ||||
|         if (data.discord.guilds) { | ||||
|           this.guild = data.discord.guilds[0]; | ||||
|         } | ||||
|  | ||||
|         this.voiceChannels = this.guild.channels.filter(x => x.type === ChannelType.voice); | ||||
|         this.stageChannels = this.guild.channels.filter(x => x.type === ChannelType.stage); | ||||
|         this.setInputForm(); | ||||
|         this.spinner.hideSpinner(); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   public setInputForm() { | ||||
|     if (this.now.getMinutes() % 15 != 0) { | ||||
|       if (this.now.getMinutes() < 15) { | ||||
|         this.now.setMinutes(15); | ||||
|       } else if (this.now.getMinutes() < 30) { | ||||
|         this.now.setMinutes(30); | ||||
|       } else if (this.now.getMinutes() < 45) { | ||||
|         this.now.setMinutes(45); | ||||
|       } else if (this.now.getMinutes() > 45) { | ||||
|         this.now.setMinutes(0); | ||||
|         this.now.setHours(this.now.getHours() + 1); | ||||
|       } | ||||
|     } | ||||
|     this.now.setSeconds(0); | ||||
|  | ||||
|     this.inputForm = this.fb.group({ | ||||
|       id: new FormControl<number | undefined>(this.event?.id), | ||||
|       interval: new FormControl<ScheduledEventInterval | undefined>(this.event?.interval, [Validators.required]), | ||||
|       entityType: new FormControl<number | undefined>(this.event?.entityType, [Validators.required]), | ||||
|       channelId: new FormControl<string | undefined>(this.event?.channelId, this.event?.entityType == EventType.voice || this.event?.entityType == EventType.stageInstance ? [Validators.required] : []), | ||||
|       location: new FormControl<string | undefined>(this.event?.location, this.event?.entityType == EventType.external ? [Validators.required] : []), | ||||
|       name: new FormControl<string | undefined>(this.event?.name, [Validators.required]), | ||||
|       startTime: new FormControl<Date | undefined>(this.event?.startTime ? new Date(this.event.startTime) : this.now, [Validators.required]), | ||||
|       endTime: new FormControl<Date | undefined>(this.event?.endTime ? new Date(this.event.endTime) : undefined), | ||||
|       description: new FormControl<string | undefined>(this.event?.description) | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   public saveEvent() { | ||||
|     this.event = { | ||||
|       id: this.inputForm.controls.id.value ?? undefined, | ||||
|       interval: this.inputForm.controls.interval.value ?? undefined, | ||||
|       name: this.inputForm.controls.name.value ?? undefined, | ||||
|       description: this.inputForm.controls.description.value ?? undefined, | ||||
|       channelId: this.inputForm.controls.channelId.value ?? undefined, | ||||
|       startTime: this.inputForm.controls.startTime.value ?? undefined, | ||||
|       endTime: this.inputForm.controls.endTime.value ?? undefined, | ||||
|       entityType: this.inputForm.controls.entityType.value ?? undefined, | ||||
|       location: this.inputForm.controls.location.value ?? undefined, | ||||
|       server: this.server | ||||
|     }; | ||||
|     this.visible = false; | ||||
|   } | ||||
|  | ||||
|   public next() { | ||||
|     this.activeIndex++; | ||||
|   } | ||||
|  | ||||
|   public back() { | ||||
|     this.activeIndex--; | ||||
|   } | ||||
|  | ||||
|   protected readonly EventType = { | ||||
|     stage: EventType.stageInstance, | ||||
|     voice: EventType.voice, | ||||
|     somewhere_else: EventType.external | ||||
|   }; | ||||
| } | ||||
| @@ -0,0 +1,244 @@ | ||||
| <div *ngIf="editableScheduledEvent"> | ||||
|   <app-edit-scheduled-event-dialog [(event)]="editableScheduledEvent"></app-edit-scheduled-event-dialog> | ||||
| </div> | ||||
|  | ||||
| <h1> | ||||
|   {{'view.server.scheduled_events.header' | translate}} | ||||
| </h1> | ||||
| <div class="content-wrapper"> | ||||
|   <div class="content"> | ||||
|     <p-table #dt [value]="scheduledEvents" [responsive]="true" responsiveLayout="stack" [breakpoint]="'720px'" | ||||
|              dataKey="id" [rowHover]="true" [rows]="10" | ||||
|              [rowsPerPageOptions]="[10,25,50]" [paginator]="true" [loading]="loading" [totalRecords]="totalRecords" | ||||
|              [lazy]="true" (onLazyLoad)="nextPage($event)"> | ||||
|  | ||||
|       <ng-template pTemplate="caption"> | ||||
|         <div class="table-caption"> | ||||
|           <div class="table-caption-table-info"> | ||||
|             <div class="table-caption-text"> | ||||
|               <ng-container *ngIf="!loading">{{scheduledEvents.length}} {{'common.of' | translate}} | ||||
|                 {{dt.totalRecords}} | ||||
|               </ng-container> | ||||
|               {{'view.server.scheduled_events.scheduled_events' | translate}} | ||||
|             </div> | ||||
|  | ||||
|             <app-multi-select-columns [table]="name" [columns]="columns" | ||||
|                                       [(hiddenColumns)]="hiddenColumns"></app-multi-select-columns> | ||||
|           </div> | ||||
|  | ||||
|           <div class="table-caption-btn-wrapper btn-wrapper"> | ||||
|             <button pButton label="{{'common.add' | translate}}" class="icon-btn btn" | ||||
|                     icon="pi pi-plus" (click)="addScheduledEvent()" | ||||
|                     [disabled]="isEditingNew || !user?.isModerator && !user?.isAdmin"> | ||||
|             </button> | ||||
|             <button pButton label="{{'common.reset_filters' | translate}}" icon="pi pi-undo" | ||||
|                     class="icon-btn btn" (click)="resetFilters()"> | ||||
|             </button> | ||||
|             <app-data-import-and-export name="scheduledEvent" [(data)]="scheduledEvents" | ||||
|                                         [callback]="callback" [validator]="validator"></app-data-import-and-export> | ||||
|           </div> | ||||
|         </div> | ||||
|       </ng-template> | ||||
|  | ||||
|       <ng-template pTemplate="header"> | ||||
|         <tr> | ||||
|           <th hideable-th="id" [parent]="this" [sortable]="true"> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.id' | translate}}</div> | ||||
|               <p-sortIcon field="id" class="table-header-icon"></p-sortIcon> | ||||
|             </div> | ||||
|           </th> | ||||
|  | ||||
|           <th hideable-th="interval" [parent]="this" [sortable]="true"> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.interval' | translate}}</div> | ||||
|               <p-sortIcon field="interval" class="table-header-icon"></p-sortIcon> | ||||
|             </div> | ||||
|           </th> | ||||
|           <th hideable-th="name" [parent]="this" [sortable]="true"> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.name' | translate}}</div> | ||||
|               <p-sortIcon field="name" class="table-header-icon"></p-sortIcon> | ||||
|             </div> | ||||
|           </th> | ||||
|           <th hideable-th="description" [parent]="this" [sortable]="true"> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.description' | translate}}</div> | ||||
|               <p-sortIcon field="description" class="table-header-icon"></p-sortIcon> | ||||
|             </div> | ||||
|           </th> | ||||
|           <th hideable-th="channel_id" [parent]="this" [sortable]="true"> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.channel_id' | translate}}</div> | ||||
|               <p-sortIcon field="channelId" class="table-header-icon"></p-sortIcon> | ||||
|             </div> | ||||
|           </th> | ||||
|           <th hideable-th="start_time" [parent]="this" [sortable]="true"> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.start_time' | translate}}</div> | ||||
|               <p-sortIcon field="startTime" class="table-header-icon"></p-sortIcon> | ||||
|             </div> | ||||
|           </th> | ||||
|           <th hideable-th="end_time" [parent]="this" [sortable]="true"> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.end_time' | translate}}</div> | ||||
|               <p-sortIcon field="endTime" class="table-header-icon"></p-sortIcon> | ||||
|             </div> | ||||
|           </th> | ||||
|           <th hideable-th="type" [parent]="this" [sortable]="true"> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.type' | translate}}</div> | ||||
|               <p-sortIcon field="entityType" class="table-header-icon"></p-sortIcon> | ||||
|             </div> | ||||
|           </th> | ||||
|           <th hideable-th="location" [parent]="this" [sortable]="true"> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.location' | translate}}</div> | ||||
|               <p-sortIcon field="location" class="table-header-icon"></p-sortIcon> | ||||
|             </div> | ||||
|           </th> | ||||
|  | ||||
|           <th> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.created_at' | translate}}</div> | ||||
|             </div> | ||||
|           </th> | ||||
|  | ||||
|           <th> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.modified_at' | translate}}</div> | ||||
|             </div> | ||||
|           </th> | ||||
|  | ||||
|           <th> | ||||
|             <div class="table-header-label"> | ||||
|               <div class="table-header-text">{{'common.actions' | translate}}</div> | ||||
|             </div> | ||||
|           </th> | ||||
|         </tr> | ||||
|  | ||||
|         <tr> | ||||
|           <th hideable-th="id" [parent]="this" class="table-header-small"> | ||||
|             <form [formGroup]="filterForm"> | ||||
|               <input type="text" pInputText formControlName="id" | ||||
|                      placeholder="{{'common.id' | translate}}"> | ||||
|             </form> | ||||
|           </th> | ||||
|  | ||||
|           <th hideable-th="interval" [parent]="this"></th> | ||||
|  | ||||
|           <th hideable-th="name" [parent]="this"> | ||||
|             <form [formGroup]="filterForm"> | ||||
|               <input type="text" pInputText formControlName="name" | ||||
|                      placeholder="{{'common.name' | translate}}"> | ||||
|             </form> | ||||
|           </th> | ||||
|  | ||||
|           <th hideable-th="description" [parent]="this"> | ||||
|             <form [formGroup]="filterForm"> | ||||
|               <input type="text" pInputText formControlName="description" | ||||
|                      placeholder="{{'common.description' | translate}}"> | ||||
|             </form> | ||||
|           </th> | ||||
|  | ||||
|           <th hideable-th="channel_id" [parent]="this"> | ||||
|             <form [formGroup]="filterForm"> | ||||
|               <input type="number" pInputText formControlName="channelId" | ||||
|                      placeholder="{{'common.channel_id' | translate}}"> | ||||
|             </form> | ||||
|           </th> | ||||
|  | ||||
|           <th hideable-th="start_time" [parent]="this"></th> | ||||
|           <th hideable-th="end_time" [parent]="this"></th> | ||||
|           <th hideable-th="type" [parent]="this"></th> | ||||
|           <th hideable-th="location" [parent]="this"></th> | ||||
|           <th class="table-header-small-dropdown"></th> | ||||
|           <th class="table-header-small-dropdown"></th> | ||||
|           <th class="table-header-actions"></th> | ||||
|         </tr> | ||||
|       </ng-template> | ||||
|  | ||||
|       <ng-template pTemplate="body" let-scheduledEvent let-editing="editing" let-ri="rowIndex"> | ||||
|         <tr [pEditableRow]="scheduledEvent"> | ||||
|           <td hideable-td="id" [parent]="this"> | ||||
|             <span class="p-column-title">{{'common.id' | translate}}:</span> | ||||
|             {{scheduledEvent.id}} | ||||
|           </td> | ||||
|  | ||||
|           <td hideable-td="interval" [parent]="this"> | ||||
|             <span class="p-column-title">{{'common.interval' | translate}}:</span> | ||||
|             {{scheduledEvent.interval}} | ||||
|           </td> | ||||
|  | ||||
|           <td hideable-td="name" [parent]="this"> | ||||
|             <span class="p-column-title">{{'common.name' | translate}}:</span> | ||||
|             {{scheduledEvent.name}} | ||||
|           </td> | ||||
|  | ||||
|           <td hideable-td="description" [parent]="this"> | ||||
|             <span class="p-column-title">{{'common.description' | translate}}:</span> | ||||
|             {{scheduledEvent.description}} | ||||
|           </td> | ||||
|  | ||||
|           <td hideable-td="channel_id" [parent]="this"> | ||||
|             <span class="p-column-title">{{'common.channel_id' | translate}}:</span> | ||||
|             {{scheduledEvent.channelId}} | ||||
|           </td> | ||||
|  | ||||
|           <td hideable-td="start_time" [parent]="this"> | ||||
|             <span class="p-column-title">{{'common.start_time' | translate}}:</span> | ||||
|             {{scheduledEvent.startTime}} | ||||
|           </td> | ||||
|  | ||||
|           <td hideable-td="end_time" [parent]="this"> | ||||
|             <span class="p-column-title">{{'common.end_time' | translate}}:</span> | ||||
|             {{scheduledEvent.endTime}} | ||||
|           </td> | ||||
|  | ||||
|           <td hideable-td="type" [parent]="this"> | ||||
|             <span class="p-column-title">{{'common.type' | translate}}:</span> | ||||
|             {{scheduledEvent.entityType}} | ||||
|           </td> | ||||
|  | ||||
|           <td hideable-td="location" [parent]="this"> | ||||
|             <span class="p-column-title">{{'common.location' | translate}}:</span> | ||||
|             {{scheduledEvent.location}} | ||||
|           </td> | ||||
|  | ||||
|           <td> | ||||
|             <span class="p-column-title">{{'common.created_at' | translate}}:</span> | ||||
|             {{scheduledEvent.createdAt | date:'dd.MM.yy HH:mm'}} | ||||
|           </td> | ||||
|           <td> | ||||
|             <span class="p-column-title">{{'common.modified_at' | translate}}:</span> | ||||
|             {{scheduledEvent.modifiedAt | date:'dd.MM.yy HH:mm'}} | ||||
|           </td> | ||||
|           <td> | ||||
|             <div class="btn-wrapper"> | ||||
|               <app-history-btn *ngIf="!isEditingNew" [id]="scheduledEvent.id" [query]="query" | ||||
|                                translationKey="view.server.scheduledEvent.header"></app-history-btn> | ||||
|               <button *ngIf="!editing" pButton class="btn icon-btn" icon="pi pi-pencil" | ||||
|                       (click)="onRowEditInit(dt, scheduledEvent, ri)" | ||||
|                       [disabled]="!user || !user.isModerator && !user.isAdmin"></button> | ||||
|               <button *ngIf="!editing" pButton class="btn icon-btn danger-icon-btn" icon="pi pi-trash" | ||||
|                       (click)="deleteScheduledEvent(scheduledEvent)" | ||||
|                       [disabled]="!user || !user.isModerator && !user.isAdmin"></button> | ||||
|             </div> | ||||
|           </td> | ||||
|         </tr> | ||||
|       </ng-template> | ||||
|  | ||||
|       <ng-template pTemplate="emptymessage"> | ||||
|         <tr></tr> | ||||
|         <tr> | ||||
|           <td colspan="14">{{'common.no_entries_found' | translate}}</td> | ||||
|         </tr> | ||||
|         <tr></tr> | ||||
|       </ng-template> | ||||
|  | ||||
|       <ng-template pTemplate="paginatorleft"> | ||||
|       </ng-template> | ||||
|     </p-table> | ||||
|   </div> | ||||
| </div> | ||||
|  | ||||
| @@ -0,0 +1,23 @@ | ||||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | ||||
|  | ||||
| import { ScheduledEventsComponent } from './scheduled-events.component'; | ||||
|  | ||||
| describe('ScheduledEventsComponent', () => { | ||||
|   let component: ScheduledEventsComponent; | ||||
|   let fixture: ComponentFixture<ScheduledEventsComponent>; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
|       declarations: [ ScheduledEventsComponent ] | ||||
|     }) | ||||
|     .compileComponents(); | ||||
|  | ||||
|     fixture = TestBed.createComponent(ScheduledEventsComponent); | ||||
|     component = fixture.componentInstance; | ||||
|     fixture.detectChanges(); | ||||
|   }); | ||||
|  | ||||
|   it('should create', () => { | ||||
|     expect(component).toBeTruthy(); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,306 @@ | ||||
| import { Component, OnDestroy, OnInit } from "@angular/core"; | ||||
| import { FormBuilder, FormControl, FormGroup } from "@angular/forms"; | ||||
| import { Page } from "../../../../../../models/graphql/filter/page.model"; | ||||
| import { Sort, SortDirection } from "../../../../../../models/graphql/filter/sort.model"; | ||||
| import { Subject, throwError } from "rxjs"; | ||||
| import { Server } from "../../../../../../models/data/server.model"; | ||||
| import { UserDTO } from "../../../../../../models/auth/auth-user.dto"; | ||||
| import { LazyLoadEvent } from "primeng/api"; | ||||
| import { Queries } from "../../../../../../models/graphql/queries.model"; | ||||
| import { AuthService } from "../../../../../../services/auth/auth.service"; | ||||
| import { SpinnerService } from "../../../../../../services/spinner/spinner.service"; | ||||
| import { ToastService } from "../../../../../../services/toast/toast.service"; | ||||
| import { ConfirmationDialogService } from "../../../../../../services/confirmation-dialog/confirmation-dialog.service"; | ||||
| import { TranslateService } from "@ngx-translate/core"; | ||||
| import { DataService } from "../../../../../../services/data/data.service"; | ||||
| import { SidebarService } from "../../../../../../services/sidebar/sidebar.service"; | ||||
| import { ActivatedRoute } from "@angular/router"; | ||||
| import { Query, ScheduledEventListQuery } from "../../../../../../models/graphql/query.model"; | ||||
| import { catchError, debounceTime, takeUntil } from "rxjs/operators"; | ||||
| import { Table } from "primeng/table"; | ||||
| import { ScheduledEventMutationResult } from "../../../../../../models/graphql/result.model"; | ||||
| import { Mutations } from "../../../../../../models/graphql/mutations.model"; | ||||
| import { ComponentWithTable } from "../../../../../../base/component-with-table"; | ||||
| import { EventType, ScheduledEvent, ScheduledEventFilter } from "../../../../../../models/data/scheduled_events.model"; | ||||
|  | ||||
| @Component({ | ||||
|   selector: "app-scheduled-events", | ||||
|   templateUrl: "./scheduled-events.component.html", | ||||
|   styleUrls: ["./scheduled-events.component.scss"] | ||||
| }) | ||||
| export class ScheduledEventsComponent extends ComponentWithTable implements OnInit, OnDestroy { | ||||
|   public scheduledEvents: ScheduledEvent[] = []; | ||||
|   public loading = true; | ||||
|  | ||||
|   public filterForm!: FormGroup<{ | ||||
|     id: FormControl<number | null>, | ||||
|     interval: FormControl<string | null>, | ||||
|     name: FormControl<string | null>, | ||||
|     description: FormControl<string | null>, | ||||
|     channelId: FormControl<string | null>, | ||||
|     startTime: FormControl<string | null>, | ||||
|     endTime: FormControl<string | null>, | ||||
|     entityType: FormControl<EventType | null>, | ||||
|     location: FormControl<string | null>, | ||||
|   }>; | ||||
|  | ||||
|   public filter: ScheduledEventFilter = {}; | ||||
|   public page: Page = { | ||||
|     pageSize: undefined, | ||||
|     pageIndex: undefined | ||||
|   }; | ||||
|   public sort: Sort = { | ||||
|     sortColumn: undefined, | ||||
|     sortDirection: undefined | ||||
|   }; | ||||
|  | ||||
|   public totalRecords: number = 0; | ||||
|  | ||||
|   private unsubscriber = new Subject<void>(); | ||||
|   private server: Server = {}; | ||||
|   public user: UserDTO | null = null; | ||||
|   public query: string = Queries.scheduledEventWithHistoryQuery; | ||||
|   public _editableScheduledEvent?: ScheduledEvent = undefined; | ||||
|  | ||||
|   set editableScheduledEvent(event: ScheduledEvent | undefined) { | ||||
|     if (!event) { | ||||
|       this.isEditingNew = false; | ||||
|     } else if (this._editableScheduledEvent && event && this._editableScheduledEvent !== event) { | ||||
|       this.onRowEditSave(event); | ||||
|     } | ||||
|     this._editableScheduledEvent = event; | ||||
|   } | ||||
|  | ||||
|   get editableScheduledEvent(): ScheduledEvent | undefined { | ||||
|     return this._editableScheduledEvent; | ||||
|   } | ||||
|  | ||||
|   public constructor( | ||||
|     private authService: AuthService, | ||||
|     private spinner: SpinnerService, | ||||
|     private toastService: ToastService, | ||||
|     private confirmDialog: ConfirmationDialogService, | ||||
|     private fb: FormBuilder, | ||||
|     private translate: TranslateService, | ||||
|     private data: DataService, | ||||
|     private sidebar: SidebarService, | ||||
|     private route: ActivatedRoute) { | ||||
|     super("ScheduledEvent", ["id", "interval", "name", "description", "channel_id", "start_time", "end_time", "type", "location"], | ||||
|       (oldElement: ScheduledEvent, newElement: ScheduledEvent) => { | ||||
|         return oldElement.name === newElement.name; | ||||
|       }); | ||||
|   } | ||||
|  | ||||
|   public ngOnInit(): void { | ||||
|     this.loading = true; | ||||
|     this.setFilterForm(); | ||||
|     this.data.getServerFromRoute(this.route).then(async server => { | ||||
|       this.server = server; | ||||
|       let authUser = await this.authService.getLoggedInUser(); | ||||
|       this.user = authUser?.users?.find(u => u.server == this.server.id) ?? null; | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   public ngOnDestroy(): void { | ||||
|     this.unsubscriber.next(); | ||||
|     this.unsubscriber.complete(); | ||||
|   } | ||||
|  | ||||
|   public loadNextPage(): void { | ||||
|     this.data.query<ScheduledEventListQuery>(Queries.scheduledEventQuery, { | ||||
|         serverId: this.server.id, filter: this.filter, page: this.page, sort: this.sort | ||||
|       }, | ||||
|       (data: Query) => { | ||||
|         return data.servers[0]; | ||||
|       } | ||||
|     ).subscribe(data => { | ||||
|       this.totalRecords = data.scheduledEventCount; | ||||
|       this.scheduledEvents = data.scheduledEvents; | ||||
|       this.spinner.hideSpinner(); | ||||
|       this.loading = false; | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   public setFilterForm(): void { | ||||
|     this.filterForm = this.fb.group({ | ||||
|       id: new FormControl<number | null>(null), | ||||
|       interval: new FormControl<string | null>(null), | ||||
|       name: new FormControl<string | null>(null), | ||||
|       description: new FormControl<string | null>(null), | ||||
|       channelId: new FormControl<string | null>(null), | ||||
|       startTime: new FormControl<string | null>(null), | ||||
|       endTime: new FormControl<string | null>(null), | ||||
|       entityType: new FormControl<EventType | null>(null), | ||||
|       location: new FormControl<string | null>(null) | ||||
|     }); | ||||
|  | ||||
|     this.filterForm.valueChanges.pipe( | ||||
|       takeUntil(this.unsubscriber), | ||||
|       debounceTime(600) | ||||
|     ).subscribe(changes => { | ||||
|       if (changes.id) { | ||||
|         this.filter.id = changes.id; | ||||
|       } else { | ||||
|         this.filter.id = undefined; | ||||
|       } | ||||
|  | ||||
|       if (changes.interval) { | ||||
|         this.filter.interval = changes.interval; | ||||
|       } else { | ||||
|         this.filter.interval = undefined; | ||||
|       } | ||||
|  | ||||
|       if (changes.name) { | ||||
|         this.filter.name = changes.name; | ||||
|       } else { | ||||
|         this.filter.name = undefined; | ||||
|       } | ||||
|  | ||||
|       if (changes.description) { | ||||
|         this.filter.description = changes.description; | ||||
|       } else { | ||||
|         this.filter.description = undefined; | ||||
|       } | ||||
|  | ||||
|       if (changes.channelId) { | ||||
|         this.filter.channelId = changes.channelId; | ||||
|       } else { | ||||
|         this.filter.channelId = undefined; | ||||
|       } | ||||
|  | ||||
|       if (changes.startTime) { | ||||
|         this.filter.startTime = changes.startTime; | ||||
|       } else { | ||||
|         this.filter.startTime = undefined; | ||||
|       } | ||||
|  | ||||
|       if (changes.endTime) { | ||||
|         this.filter.endTime = changes.endTime; | ||||
|       } else { | ||||
|         this.filter.endTime = undefined; | ||||
|       } | ||||
|  | ||||
|       if (changes.entityType) { | ||||
|         this.filter.entityType = changes.entityType; | ||||
|       } else { | ||||
|         this.filter.entityType = undefined; | ||||
|       } | ||||
|  | ||||
|       if (changes.location) { | ||||
|         this.filter.location = changes.location; | ||||
|       } else { | ||||
|         this.filter.location = undefined; | ||||
|       } | ||||
|  | ||||
|       if (this.page.pageSize) | ||||
|         this.page.pageSize = 10; | ||||
|  | ||||
|       if (this.page.pageIndex) | ||||
|         this.page.pageIndex = 0; | ||||
|  | ||||
|       this.loadNextPage(); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   public newScheduledEventTemplate: ScheduledEvent = { | ||||
|     createdAt: "", | ||||
|     modifiedAt: "" | ||||
|   }; | ||||
|  | ||||
|   public nextPage(event: LazyLoadEvent): void { | ||||
|     this.page.pageSize = event.rows ?? 0; | ||||
|     if (event.first != null && event.rows != null) | ||||
|       this.page.pageIndex = event.first / event.rows; | ||||
|     this.sort.sortColumn = event.sortField ?? undefined; | ||||
|     this.sort.sortDirection = event.sortOrder === 1 ? SortDirection.ASC : event.sortOrder === -1 ? SortDirection.DESC : SortDirection.ASC; | ||||
|  | ||||
|     this.loadNextPage(); | ||||
|   } | ||||
|  | ||||
|   public resetFilters(): void { | ||||
|     this.filterForm.reset(); | ||||
|   } | ||||
|  | ||||
|   public onRowEditInit(table: Table, event: ScheduledEvent, index: number): void { | ||||
|     this.editableScheduledEvent = event; | ||||
|   } | ||||
|  | ||||
|   public override onRowEditSave(newScheduledEvent: ScheduledEvent): void { | ||||
|     if (this.isEditingNew && JSON.stringify(newScheduledEvent) === JSON.stringify(this.newScheduledEventTemplate)) { | ||||
|       this.isEditingNew = false; | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     if (this.isEditingNew) { | ||||
|       this.spinner.showSpinner(); | ||||
|       this.data.mutation<ScheduledEventMutationResult>(Mutations.createScheduledEvent, { | ||||
|           interval: newScheduledEvent.interval, | ||||
|           name: newScheduledEvent.name, | ||||
|           description: newScheduledEvent.description, | ||||
|           channelId: newScheduledEvent.channelId, | ||||
|           startTime: newScheduledEvent.startTime, | ||||
|           endTime: newScheduledEvent.endTime, | ||||
|           entityType: newScheduledEvent.entityType, | ||||
|           location: newScheduledEvent.location, | ||||
|           serverId: this.server.id | ||||
|         } | ||||
|       ).pipe(catchError(err => { | ||||
|         this.isEditingNew = false; | ||||
|         this.spinner.hideSpinner(); | ||||
|         return throwError(err); | ||||
|       })).subscribe(result => { | ||||
|         this.isEditingNew = false; | ||||
|         this.spinner.hideSpinner(); | ||||
|         this.toastService.success(this.translate.instant("view.server.scheduled_events.message.scheduled_event_create"), this.translate.instant("view.server.scheduled_events.message.scheduled_event_create_d", { name: result.scheduledEvent.createScheduledEvent?.name })); | ||||
|         this.loadNextPage(); | ||||
|       }); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     this.spinner.showSpinner(); | ||||
|     this.data.mutation<ScheduledEventMutationResult>(Mutations.updateScheduledEvent, { | ||||
|         id: newScheduledEvent.id, | ||||
|         interval: newScheduledEvent.interval, | ||||
|         name: newScheduledEvent.name, | ||||
|         description: newScheduledEvent.description, | ||||
|         channelId: newScheduledEvent.channelId, | ||||
|         startTime: newScheduledEvent.startTime, | ||||
|         endTime: newScheduledEvent.endTime, | ||||
|         entityType: newScheduledEvent.entityType, | ||||
|         location: newScheduledEvent.location, | ||||
|         serverId: this.server.id | ||||
|       } | ||||
|     ).pipe(catchError(err => { | ||||
|       this.spinner.hideSpinner(); | ||||
|       return throwError(err); | ||||
|     })).subscribe(_ => { | ||||
|       this.spinner.hideSpinner(); | ||||
|       this.toastService.success(this.translate.instant("view.server.scheduled_events.message.scheduled_event_update"), this.translate.instant("view.server.scheduled_events.message.scheduled_event_update_d", { name: newScheduledEvent.name })); | ||||
|       this.loadNextPage(); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   public deleteScheduledEvent(ScheduledEvent: ScheduledEvent): void { | ||||
|     this.confirmDialog.confirmDialog( | ||||
|       this.translate.instant("view.server.scheduled_events.message.scheduled_event_delete"), this.translate.instant("view.server.scheduled_events.message.scheduled_event_delete_q", { name: ScheduledEvent.name }), | ||||
|       () => { | ||||
|         this.spinner.showSpinner(); | ||||
|         this.data.mutation<ScheduledEventMutationResult>(Mutations.deleteScheduledEvent, { | ||||
|             id: ScheduledEvent.id | ||||
|           } | ||||
|         ).pipe(catchError(err => { | ||||
|           this.spinner.hideSpinner(); | ||||
|           return throwError(err); | ||||
|         })).subscribe(l => { | ||||
|           this.spinner.hideSpinner(); | ||||
|           this.toastService.success(this.translate.instant("view.server.scheduled_events.message.scheduled_event_deleted"), this.translate.instant("view.server.scheduled_events.message.scheduled_event_deleted_d", { name: ScheduledEvent.name })); | ||||
|           this.loadNextPage(); | ||||
|         }); | ||||
|       }); | ||||
|   } | ||||
|  | ||||
|   public addScheduledEvent(): void { | ||||
|     this.isEditingNew = true; | ||||
|     this.editableScheduledEvent = JSON.parse(JSON.stringify(this.newScheduledEventTemplate)); | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,14 @@ | ||||
| import { NgModule } from "@angular/core"; | ||||
| import { RouterModule, Routes } from "@angular/router"; | ||||
| import { ScheduledEventsComponent } from "./components/scheduled-events/scheduled-events.component"; | ||||
|  | ||||
| const routes: Routes = [ | ||||
|   { path: "", component: ScheduledEventsComponent } | ||||
| ]; | ||||
|  | ||||
| @NgModule({ | ||||
|   imports: [RouterModule.forChild(routes)], | ||||
|   exports: [RouterModule] | ||||
| }) | ||||
| export class ScheduledEventsRoutingModule { | ||||
| } | ||||
| @@ -0,0 +1,25 @@ | ||||
| import { NgModule } from "@angular/core"; | ||||
| import { CommonModule } from "@angular/common"; | ||||
|  | ||||
| import { ScheduledEventsRoutingModule } from "./scheduled-events-routing.module"; | ||||
| import { ScheduledEventsComponent } from "./components/scheduled-events/scheduled-events.component"; | ||||
| import { SharedModule } from "../../../shared/shared.module"; | ||||
| import { | ||||
|   EditScheduledEventDialogComponent | ||||
| } from "./components/edit-scheduled-event-dialog/edit-scheduled-event-dialog.component"; | ||||
|  | ||||
|  | ||||
| @NgModule({ | ||||
|   declarations: [ | ||||
|     ScheduledEventsComponent, | ||||
|     EditScheduledEventDialogComponent | ||||
|   ], | ||||
|   imports: [ | ||||
|     CommonModule, | ||||
|     SharedModule, | ||||
|     ScheduledEventsRoutingModule | ||||
|   ], | ||||
|   exports: [] | ||||
| }) | ||||
| export class ScheduledEventsModule { | ||||
| } | ||||
| @@ -19,6 +19,7 @@ const routes: Routes = [ | ||||
|   { path: "levels", loadChildren: () => import("./levels/levels.module").then(m => m.LevelsModule), canActivate: [AuthGuard], data: { memberRole: MemberRoles.Moderator } }, | ||||
|   { path: "achievements", loadChildren: () => import("./achievements/achievements.module").then(m => m.AchievementsModule), canActivate: [AuthGuard], data: { memberRole: MemberRoles.Moderator } }, | ||||
|   { path: "short-role-names", loadChildren: () => import("./short-role-name/short-role-name.module").then(m => m.ShortRoleNameModule), canActivate: [AuthGuard], data: { memberRole: MemberRoles.Moderator } }, | ||||
|   { path: "scheduled-events", loadChildren: () => import("./scheduled-events/scheduled-events.module").then(m => m.ScheduledEventsModule), canActivate: [AuthGuard], data: { memberRole: MemberRoles.Moderator } }, | ||||
|   { path: "config", loadChildren: () => import("./config/config.module").then(m => m.ConfigModule), canActivate: [AuthGuard], data: { memberRole: MemberRoles.Admin } } | ||||
| ]; | ||||
|  | ||||
|   | ||||
| @@ -1,14 +1,8 @@ | ||||
| import { NgModule } from '@angular/core'; | ||||
| import { CommonModule } from '@angular/common'; | ||||
| import { ShortRoleNamesComponent } from './components/short-role-names/short-role-names.component'; | ||||
| import { NgModule } from "@angular/core"; | ||||
| import { CommonModule } from "@angular/common"; | ||||
| import { ShortRoleNamesComponent } from "./components/short-role-names/short-role-names.component"; | ||||
| import { ShortRoleNameRoutingModule } from "./short-role-name-routing.module"; | ||||
| import { ButtonModule } from "primeng/button"; | ||||
| import { InputTextModule } from "primeng/inputtext"; | ||||
| import { ReactiveFormsModule } from "@angular/forms"; | ||||
| import { SharedModule } from "../../../shared/shared.module"; | ||||
| import { TableModule } from "primeng/table"; | ||||
| import { TranslateModule } from "@ngx-translate/core"; | ||||
|  | ||||
|  | ||||
|  | ||||
| @NgModule({ | ||||
| @@ -18,13 +12,7 @@ import { TranslateModule } from "@ngx-translate/core"; | ||||
|   imports: [ | ||||
|     CommonModule, | ||||
|     ShortRoleNameRoutingModule, | ||||
|     ButtonModule, | ||||
|     InputTextModule, | ||||
|     ReactiveFormsModule, | ||||
|     SharedModule, | ||||
|     SharedModule, | ||||
|     TableModule, | ||||
|     TranslateModule | ||||
|   ] | ||||
| }) | ||||
| export class ShortRoleNameModule { } | ||||
|   | ||||
| @@ -4,7 +4,7 @@ import { BehaviorSubject, forkJoin, Observable } from "rxjs"; | ||||
| import { AuthRoles } from "../../models/auth/auth-roles.enum"; | ||||
| import { AuthService } from "../auth/auth.service"; | ||||
| import { TranslateService } from "@ngx-translate/core"; | ||||
| import { NavigationEnd, Router } from "@angular/router"; | ||||
| import { Router } from "@angular/router"; | ||||
| import { ThemeService } from "../theme/theme.service"; | ||||
| import { Server } from "../../models/data/server.model"; | ||||
| import { UserDTO } from "../../models/auth/auth-user.dto"; | ||||
| @@ -30,6 +30,7 @@ export class SidebarService { | ||||
|   serverAutoRoles: MenuItem = {}; | ||||
|   serverLevels: MenuItem = {}; | ||||
|   serverAchievements: MenuItem = {}; | ||||
|   serverScheduledEvents: MenuItem = {}; | ||||
|   serverShortRoleNames: MenuItem = {}; | ||||
|   serverConfig: MenuItem = {}; | ||||
|   serverMenu: MenuItem = {}; | ||||
| @@ -110,6 +111,13 @@ export class SidebarService { | ||||
|       routerLink: `server/${this.server?.id}/achievements` | ||||
|     }; | ||||
|  | ||||
|     this.serverScheduledEvents = { | ||||
|       label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.scheduled_events") : "", | ||||
|       icon: "pi pi-calendar", | ||||
|       visible: true, | ||||
|       routerLink: `server/${this.server?.id}/scheduled-events` | ||||
|     }; | ||||
|  | ||||
|     this.serverShortRoleNames = { | ||||
|       label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.short_role_names") : "", | ||||
|       icon: "pi pi-list", | ||||
| @@ -129,7 +137,7 @@ export class SidebarService { | ||||
|       icon: "pi pi-server", | ||||
|       visible: false, | ||||
|       expanded: true, | ||||
|       items: [this.serverDashboard, this.serverProfile, this.serverMembers, this.serverAutoRoles, this.serverLevels, this.serverAchievements, this.serverShortRoleNames, this.serverConfig] | ||||
|       items: [this.serverDashboard, this.serverProfile, this.serverMembers, this.serverAutoRoles, this.serverLevels, this.serverAchievements, this.serverScheduledEvents, this.serverShortRoleNames, this.serverConfig] | ||||
|     }; | ||||
|     this.adminConfig = { | ||||
|       label: this.isSidebarOpen ? this.translateService.instant("sidebar.config") : "", | ||||
| @@ -205,6 +213,7 @@ export class SidebarService { | ||||
|         this.serverAutoRoles.visible = isTechnicianAndFullAccessActive || this.hasFeature("AutoRoleModule") && user?.isModerator; | ||||
|         this.serverLevels.visible = isTechnicianAndFullAccessActive || this.hasFeature("LevelModule") && user?.isModerator; | ||||
|         this.serverAchievements.visible = isTechnicianAndFullAccessActive || this.hasFeature("AchievementsModule") && user?.isModerator; | ||||
|         this.serverScheduledEvents.visible = isTechnicianAndFullAccessActive || this.hasFeature("ScheduledEvents") && user?.isModerator; | ||||
|         this.serverShortRoleNames.visible = isTechnicianAndFullAccessActive || this.hasFeature("ShortRoleName") && user?.isAdmin; | ||||
|  | ||||
|         this.serverConfig.visible = isTechnicianAndFullAccessActive || user?.isAdmin; | ||||
|   | ||||
| @@ -124,12 +124,14 @@ | ||||
|   }, | ||||
|   "common": { | ||||
|     "404": "404 - Der Eintrag konnte nicht gefunden werden", | ||||
|     "abort": "Abbrechen", | ||||
|     "actions": "Aktionen", | ||||
|     "active": "Aktiv", | ||||
|     "add": "Hinzufügen", | ||||
|     "attribute": "Attribut", | ||||
|     "auth_role": "Rolle", | ||||
|     "author": "Autor", | ||||
|     "back": "Zurück", | ||||
|     "bool_as_string": { | ||||
|       "false": "Nein", | ||||
|       "true": "Ja" | ||||
| @@ -137,12 +139,14 @@ | ||||
|     "channel_id": "Kanal Id", | ||||
|     "channel_name": "Kanal", | ||||
|     "color": "Farbe", | ||||
|     "continue": "Weiter", | ||||
|     "created_at": "Erstellt am", | ||||
|     "description": "Beschreibung", | ||||
|     "discord_id": "Discord Id", | ||||
|     "edit": "Bearbeiten", | ||||
|     "email": "E-Mail", | ||||
|     "emoji": "Emoji", | ||||
|     "end_time": "Endzeit", | ||||
|     "error": "Fehler", | ||||
|     "export": "Exportieren", | ||||
|     "feature_flags": "Funktionen", | ||||
| @@ -176,11 +180,13 @@ | ||||
|     }, | ||||
|     "id": "Id", | ||||
|     "import": "Importieren", | ||||
|     "interval": "Interval", | ||||
|     "joined_at": "Beigetreten am", | ||||
|     "last_name": "Nachname", | ||||
|     "leaved_at": "Verlassen am", | ||||
|     "left_server": "Aktiv", | ||||
|     "level": "Level", | ||||
|     "location": "Ort", | ||||
|     "message_id": "Nachricht Id", | ||||
|     "min_xp": "Min. XP", | ||||
|     "modified_at": "Bearbeitet am", | ||||
| @@ -200,10 +206,12 @@ | ||||
|     "role": "Rolle", | ||||
|     "rule_count": "Regeln", | ||||
|     "save": "Speichern", | ||||
|     "start_time": "Startzeit", | ||||
|     "state": { | ||||
|       "off": "Aus", | ||||
|       "on": "Ein" | ||||
|     }, | ||||
|     "type": "Typ", | ||||
|     "user_warnings": "Verwarnungen", | ||||
|     "users": "Benutzer", | ||||
|     "value": "Wert", | ||||
| @@ -327,6 +335,7 @@ | ||||
|     "config": "Konfiguration", | ||||
|     "dashboard": "Dashboard", | ||||
|     "members": "Mitglieder", | ||||
|     "scheduled_events": "Geplante Events", | ||||
|     "server": { | ||||
|       "achievements": "Errungenschaften", | ||||
|       "auto_roles": "Auto Rollen", | ||||
| @@ -335,6 +344,7 @@ | ||||
|       "levels": "Level", | ||||
|       "members": "Mitglieder", | ||||
|       "profile": "Dein Profil", | ||||
|       "scheduled_events": "Geplante Events", | ||||
|       "short_role_names": "Rollen Kürzel" | ||||
|     }, | ||||
|     "server_empty": "Kein Server ausgewählt", | ||||
| @@ -538,6 +548,57 @@ | ||||
|         "reaction_count": "Anzahl Reaktionen", | ||||
|         "xp": "XP" | ||||
|       }, | ||||
|       "scheduled_events": { | ||||
|         "edit_dialog": { | ||||
|           "add_header": "Event hinzufügen", | ||||
|           "edit_header": "Event bearbeiten", | ||||
|           "event_info": { | ||||
|             "description": "Beschreibung", | ||||
|             "description_input": "Erzähl den Leuten ein wenig mehr über dein Event. Markdown, neue Zeilen und Links werden unterstützt.", | ||||
|             "end_date_time": "Endzeitpunkt", | ||||
|             "event_topic": "Thema", | ||||
|             "event_topic_input": "Worum geht es bei deinem Event?", | ||||
|             "header": "Worum geht es bei deinem Event?", | ||||
|             "start_date_time": "Startzeitpunkt", | ||||
|             "tab_name": "Eventinformationen" | ||||
|           }, | ||||
|           "location": { | ||||
|             "header": "Wo ist dein Event?", | ||||
|             "interval": "Interval", | ||||
|             "intervals": { | ||||
|               "daily": "Täglich", | ||||
|               "monthly": "Monatlich", | ||||
|               "weekly": "Wöchentlich", | ||||
|               "yearly": "Jährlich" | ||||
|             }, | ||||
|             "somewhere_else": "Irgendwo anders", | ||||
|             "somewhere_else_input": "Ort eingeben", | ||||
|             "stage": "Stage-Kanal", | ||||
|             "stage_input": "Stage-Kanal auswählen", | ||||
|             "tab_name": "Verzeichnis", | ||||
|             "voice": "Sprachkanal", | ||||
|             "voice_input": "Sprachkanal auswählen" | ||||
|           } | ||||
|         }, | ||||
|         "header": "Geplante Events", | ||||
|         "message": { | ||||
|           "scheduled_event_create": "Geplantes Event erstellt", | ||||
|           "scheduled_event_create_d": "Geplantes Event {{name}} erfolgreich erstellt", | ||||
|           "scheduled_event_create_failed": "Geplantes Event Erstellung fehlgeschlagen", | ||||
|           "scheduled_event_create_failed_d": "Die Erstellung der Geplantes Event ist fehlgeschlagen!", | ||||
|           "scheduled_event_delete": "Geplantes Event löschen", | ||||
|           "scheduled_event_delete_failed": "Geplantes Event Löschung fehlgeschlagen", | ||||
|           "scheduled_event_delete_failed_d": "Die Löschung der Geplantes Event {{name}} ist fehlgeschlagen!", | ||||
|           "scheduled_event_delete_q": "Sind Sie sich sicher, dass Sie das Geplantes Event {{name}} löschen möchten?", | ||||
|           "scheduled_event_deleted": "Geplantes Event gelöscht", | ||||
|           "scheduled_event_deleted_d": "Geplantes Event {{name}} erfolgreich gelöscht", | ||||
|           "scheduled_event_update": "Geplantes Event bearbeitet", | ||||
|           "scheduled_event_update_d": "Geplantes Event {{name}} erfolgreich bearbeitet", | ||||
|           "scheduled_event_update_failed": "Geplantes Event Bearbeitung fehlgeschlagen", | ||||
|           "scheduled_event_update_failed_d": "Die Bearbeitung der Geplantes Event ist fehlgeschlagen!" | ||||
|         }, | ||||
|         "scheduled_events": "Geplante Events" | ||||
|       }, | ||||
|       "short_role_names": { | ||||
|         "header": "Rollen Kürzel", | ||||
|         "message": { | ||||
|   | ||||
| @@ -124,12 +124,14 @@ | ||||
|   }, | ||||
|   "common": { | ||||
|     "404": "404 - Entry not found!", | ||||
|     "abort": "Abort", | ||||
|     "actions": "Actions", | ||||
|     "active": "Active", | ||||
|     "add": "Add", | ||||
|     "attribute": "Attribute", | ||||
|     "auth_role": "Role", | ||||
|     "author": "Author", | ||||
|     "back": "Back", | ||||
|     "bool_as_string": { | ||||
|       "false": "No", | ||||
|       "true": "Yes" | ||||
| @@ -137,12 +139,14 @@ | ||||
|     "channel_id": "Channel Id", | ||||
|     "channel_name": "Channel", | ||||
|     "color": "Color", | ||||
|     "continue": "Continue", | ||||
|     "created_at": "Created at", | ||||
|     "description": "Description", | ||||
|     "discord_id": "Discord Id", | ||||
|     "edit": "Edit", | ||||
|     "email": "E-Mail", | ||||
|     "emoji": "Emoji", | ||||
|     "end_time": "End time", | ||||
|     "error": "Error", | ||||
|     "export": "Export", | ||||
|     "feature_flags": "Features", | ||||
| @@ -176,11 +180,13 @@ | ||||
|     }, | ||||
|     "id": "Id", | ||||
|     "import": "Import", | ||||
|     "interval": "interval", | ||||
|     "joined_at": "Joined at", | ||||
|     "last_name": "Last name", | ||||
|     "leaved_at": "Leaved at", | ||||
|     "left_server": "Active", | ||||
|     "level": "Level", | ||||
|     "location": "Location", | ||||
|     "message_id": "Message Id", | ||||
|     "min_xp": "Min. XP", | ||||
|     "modified_at": "Modified at", | ||||
| @@ -200,10 +206,12 @@ | ||||
|     "role": "Role", | ||||
|     "rule_count": "Rules", | ||||
|     "save": "Save", | ||||
|     "start_time": "Start time", | ||||
|     "state": { | ||||
|       "off": "Off", | ||||
|       "on": "On" | ||||
|     }, | ||||
|     "type": "Type", | ||||
|     "user_warnings": "User warnings", | ||||
|     "users": "User", | ||||
|     "value": "Value", | ||||
| @@ -327,6 +335,7 @@ | ||||
|     "config": "Configuration", | ||||
|     "dashboard": "Dashboard", | ||||
|     "members": "Members", | ||||
|     "scheduled_events": "Scheduled event", | ||||
|     "server": { | ||||
|       "achievements": "Achievements", | ||||
|       "auto_roles": "Auto role", | ||||
| @@ -335,6 +344,7 @@ | ||||
|       "levels": "Level", | ||||
|       "members": "Members", | ||||
|       "profile": "Your profile", | ||||
|       "scheduled_events": "Scheduled events", | ||||
|       "short_role_names": "Short role names" | ||||
|     }, | ||||
|     "server_empty": "No server selected", | ||||
| @@ -538,6 +548,57 @@ | ||||
|         "reaction_count": "Reaction count", | ||||
|         "xp": "XP" | ||||
|       }, | ||||
|       "scheduled_events": { | ||||
|         "edit_dialog": { | ||||
|           "add_header": "Add event", | ||||
|           "edit_header": "Edit event", | ||||
|           "event_info": { | ||||
|             "description": "Description", | ||||
|             "description_input": "Tell people a little more about your event. Markdown, new lines and links are supported.", | ||||
|             "end_date_time": "End date", | ||||
|             "event_topic": "Event topic", | ||||
|             "event_topic_input": "What's your event?", | ||||
|             "header": "What's your event about?", | ||||
|             "start_date_time": "Start date", | ||||
|             "tab_name": "Event info" | ||||
|           }, | ||||
|           "location": { | ||||
|             "header": "Where is your event?", | ||||
|             "interval": "Interval", | ||||
|             "intervals": { | ||||
|               "daily": "Daily", | ||||
|               "monthly": "Monthly", | ||||
|               "weekly": "Weekly", | ||||
|               "yearly": "Yearly" | ||||
|             }, | ||||
|             "somewhere_else": "Somewhere else", | ||||
|             "somewhere_else_input": "Enter a location", | ||||
|             "stage": "Stage channel", | ||||
|             "stage_input": "Select a stage channel", | ||||
|             "tab_name": "Location", | ||||
|             "voice": "Voice channel", | ||||
|             "voice_input": "Select a voice channel" | ||||
|           } | ||||
|         }, | ||||
|         "header": "Scheduled events", | ||||
|         "message": { | ||||
|           "scheduled_event_create": "Scheduled event created", | ||||
|           "scheduled_event_create_d": "Scheduled event {{name}} successfully created", | ||||
|           "scheduled_event_create_failed": "Scheduled event creation failed", | ||||
|           "scheduled_event_create_failed_d": "Creation of scheduled event failed!", | ||||
|           "scheduled_event_delete": "Delete scheduled event", | ||||
|           "scheduled_event_delete_failed": "Scheduled event deletion failed", | ||||
|           "scheduled_event_delete_failed_d": "Deletion of scheduled event {{name}} failed!", | ||||
|           "scheduled_event_delete_q": "Are you sure you want to delete the {{name}} scheduled_event?", | ||||
|           "scheduled_event_deleted": "Scheduled event deleted", | ||||
|           "scheduled_event_deleted_d": "Scheduled event {{name}} successfully deleted\t", | ||||
|           "scheduled_event_update": "Scheduled event edited", | ||||
|           "scheduled_event_update_d": "Scheduled event {{name}} edited successfully", | ||||
|           "scheduled_event_update_failed": "Scheduled event editing failed", | ||||
|           "scheduled_event_update_failed_d": "Scheduled event editing failed!" | ||||
|         }, | ||||
|         "scheduled_events": "Scheduled events" | ||||
|       }, | ||||
|       "short_role_names": { | ||||
|         "header": "Level", | ||||
|         "message": { | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| { | ||||
|   "WebVersion": { | ||||
|     "Major": "1", | ||||
|     "Minor": "2", | ||||
|     "Micro": "2" | ||||
|   } | ||||
| } | ||||
|     "WebVersion": { | ||||
|         "Major": "1", | ||||
|         "Minor": "2", | ||||
|         "Micro": "dev410" | ||||
|     } | ||||
| } | ||||
| @@ -635,6 +635,18 @@ footer { | ||||
|   border: 0; | ||||
| } | ||||
|  | ||||
| .btn, | ||||
| .icon-btn, | ||||
| .text-btn, | ||||
| .danger-btn, | ||||
| .danger-icon-btn { | ||||
|   span { | ||||
|     transition-duration: unset !important; | ||||
|   } | ||||
|  | ||||
|   transition: none !important; | ||||
| } | ||||
|  | ||||
| .spinner-component-wrapper { | ||||
|   position: absolute; | ||||
|   top: 0; | ||||
| @@ -661,6 +673,61 @@ p-inputNumber { | ||||
|   border: none !important; | ||||
| } | ||||
|  | ||||
| .edit-dialog { | ||||
|   .p-dialog-content { | ||||
|     padding: 0 !important; | ||||
|  | ||||
|     .p-tabview-nav { | ||||
|       justify-content: space-between; | ||||
|  | ||||
|       li { | ||||
|         width: 100%; | ||||
|  | ||||
|         a { | ||||
|           width: 100%; | ||||
|           justify-content: center; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .form { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     gap: 20px; | ||||
|  | ||||
|     textarea { | ||||
|       min-height: 101px !important; | ||||
|  | ||||
|       &:focus { | ||||
|         box-shadow: none !important; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     .type { | ||||
|       display: flex; | ||||
|       flex-direction: column; | ||||
|       gap: 10px; | ||||
|  | ||||
|       .field-checkbox { | ||||
|         display: flex; | ||||
|         gap: 15px; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     input, | ||||
|     .p-dropdown { | ||||
|       width: 100%; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| p-calendar { | ||||
|   .p-calendar { | ||||
|     width: 100% !important; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @media (max-width: 720px) { | ||||
|   footer { | ||||
|     .left, | ||||
|   | ||||
| @@ -113,3 +113,4 @@ p-table { | ||||
| .pi-sort-amount-down:before { | ||||
|   content: "\e913" !important; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -187,12 +187,15 @@ | ||||
|     } | ||||
|   } | ||||
|  | ||||
|  | ||||
|   .p-dialog-header { | ||||
|     background-color: $secondaryBackgroundColor !important; | ||||
|     background-color: $primaryBackgroundColor !important; | ||||
|     color: $primaryTextColor !important; | ||||
|   } | ||||
|  | ||||
|   .p-dialog-content { | ||||
|     background-color: $secondaryBackgroundColor !important; | ||||
|  | ||||
|     .content-data-name, | ||||
|     .content-data-value { | ||||
|       color: $primaryTextColor; | ||||
| @@ -345,9 +348,11 @@ | ||||
|   } | ||||
|  | ||||
|   p-dropdown { | ||||
|  | ||||
|     .p-dropdown { | ||||
|       background-color: $primaryBackgroundColor !important; | ||||
|       border-color: $primaryTextColor !important; | ||||
|       border: $default-border; | ||||
|       border-radius: 10px; | ||||
|       background-color: $secondaryBackgroundColor !important; | ||||
|       color: $primaryTextColor !important; | ||||
|  | ||||
|       span { | ||||
| @@ -411,8 +416,6 @@ | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|  | ||||
|  | ||||
|     } | ||||
|  | ||||
|     .table-edit-input { | ||||
| @@ -537,6 +540,10 @@ | ||||
|   } | ||||
|  | ||||
|   .icon-btn { | ||||
|     .p-button-label { | ||||
|       transition-duration: unset !important; | ||||
|     } | ||||
|  | ||||
|     &:hover { | ||||
|       background-color: transparent !important; | ||||
|       color: $primaryHeaderColor !important; | ||||
| @@ -544,6 +551,7 @@ | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .danger-btn, | ||||
|   .danger-icon-btn { | ||||
|     background-color: transparent !important; | ||||
|     color: $primaryTextColor !important; | ||||
| @@ -560,25 +568,11 @@ | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .danger-btn { | ||||
|     background-color: $primaryErrorColor !important; | ||||
|     color: $primaryErrorColor !important; | ||||
|     border: 0 !important; | ||||
|  | ||||
|     &:hover { | ||||
|       background-color: $primaryErrorColor !important; | ||||
|       color: $primaryTextColor !important; | ||||
|       border: 0; | ||||
|     } | ||||
|  | ||||
|     .pi { | ||||
|       font-size: 1.275rem !important; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .p-datatable .p-sortable-column.p-highlight, | ||||
|   .p-datatable .p-sortable-column.p-highlight .p-sortable-column-icon { | ||||
|   .p-datatable .p-sortable-column.p-highlight .p-sortable-column-icon, | ||||
|   .p-datatable .p-sortable-column:not(.p-highlight):hover { | ||||
|     color: $primaryHeaderColor !important; | ||||
|     background-color: transparent !important; | ||||
|   } | ||||
|  | ||||
|   .p-dropdown:not(.p-disabled):hover, | ||||
| @@ -616,6 +610,27 @@ | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .p-selectbutton { | ||||
|     .p-highlight { | ||||
|       background-color: $primaryHeaderColor !important; | ||||
|     } | ||||
|  | ||||
|     .p-button { | ||||
|  | ||||
|       border: 1px solid $primaryHeaderColor !important; | ||||
|  | ||||
|       &:hover { | ||||
|         background-color: $secondaryHeaderColor !important; | ||||
|         border: 1px solid $secondaryHeaderColor !important; | ||||
|       } | ||||
|  | ||||
|       &:focus { | ||||
|         border-color: $primaryHeaderColor !important; | ||||
|         box-shadow: none !important; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .p-multiselect { | ||||
|     background-color: $primaryBackgroundColor !important; | ||||
|     color: $primaryTextColor !important; | ||||
| @@ -661,4 +676,141 @@ | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .p-inputswitch.p-inputswitch-checked .p-inputswitch-slider { | ||||
|     background: $primaryHeaderColor !important; | ||||
|   } | ||||
|  | ||||
|   .p-inputswitch.p-focus .p-inputswitch-slider { | ||||
|     box-shadow: none !important; | ||||
|   } | ||||
|  | ||||
|   p-inputNumber { | ||||
|     background-color: $primaryBackgroundColor !important; | ||||
|   } | ||||
|  | ||||
|   p-calendar > span > button { | ||||
|     background-color: $primaryHeaderColor !important; | ||||
|     border: 1px solid $primaryHeaderColor !important; | ||||
|  | ||||
|     &:focus { | ||||
|       box-shadow: none !important; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .p-calendar { | ||||
|     .p-datepicker:not(.p-datepicker-inline) { | ||||
|       background-color: $secondaryBackgroundColor !important; | ||||
|     } | ||||
|  | ||||
|     .p-datepicker { | ||||
|       color: $primaryTextColor !important; | ||||
|  | ||||
|       .p-datepicker-header { | ||||
|         color: $primaryHeaderColor !important; | ||||
|         background-color: $primaryBackgroundColor !important; | ||||
|  | ||||
|         .p-datepicker-title .p-datepicker-year, | ||||
|         .p-datepicker-title .p-datepicker-month, | ||||
|         .p-datepicker .p-datepicker-header .p-datepicker-title .p-datepicker-month { | ||||
|           color: $primaryTextColor !important; | ||||
|  | ||||
|           &:hover { | ||||
|             color: $primaryHeaderColor !important; | ||||
|           } | ||||
|  | ||||
|           &:focus { | ||||
|             box-shadow: none !important; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     table td > span { | ||||
|       color: $primaryTextColor !important; | ||||
|  | ||||
|       &:hover { | ||||
|         color: $primaryHeaderColor !important; | ||||
|         background-color: $primaryBackgroundColor !important; | ||||
|       } | ||||
|  | ||||
|       &:focus { | ||||
|         box-shadow: none !important; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     table td.p-datepicker-today > span { | ||||
|       color: $primaryHeaderColor !important; | ||||
|       background-color: $primaryBackgroundColor !important; | ||||
|     } | ||||
|  | ||||
|     table td > span.p-highlight { | ||||
|       color: $primaryHeaderColor !important; | ||||
|       background-color: $primaryBackgroundColor !important; | ||||
|     } | ||||
|  | ||||
|     .p-yearpicker .p-yearpicker-year, | ||||
|     .p-monthpicker .p-monthpicker-month:not(.p-disabled):not(.p-highlight) { | ||||
|       color: $primaryTextColor !important; | ||||
|       background-color: $secondaryBackgroundColor !important; | ||||
|  | ||||
|       &:hover { | ||||
|         color: $primaryHeaderColor !important; | ||||
|       } | ||||
|  | ||||
|       &:focus { | ||||
|         box-shadow: none !important; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .edit-dialog { | ||||
|     textarea { | ||||
|       background-color: $secondaryBackgroundColor; | ||||
|       color: $primaryTextColor; | ||||
|  | ||||
|       &:hover { | ||||
|         border-color: $primaryHeaderColor; | ||||
|       } | ||||
|  | ||||
|       &:focus { | ||||
|         border-color: $primaryHeaderColor; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     .p-dialog-content { | ||||
|       .p-tabview { | ||||
|         .p-tabview-nav li .p-tabview-nav-link:not(.p-disabled):focus { | ||||
|           box-shadow: none !important; | ||||
|         } | ||||
|  | ||||
|         .p-tabview-nav li.p-highlight .p-tabview-nav-link { | ||||
|           color: $primaryHeaderColor !important; | ||||
|           border-color: $primaryHeaderColor !important; | ||||
|         } | ||||
|  | ||||
|         .p-tabview-nav, | ||||
|         .p-tabview-nav li .p-tabview-nav-link, | ||||
|         .p-tabview-panels { | ||||
|           background-color: $secondaryBackgroundColor !important; | ||||
|           color: $primaryTextColor !important; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .p-radiobutton { | ||||
|     .p-radiobutton-box.p-highlight { | ||||
|       border-color: $primaryHeaderColor !important; | ||||
|       background: $primaryHeaderColor !important; | ||||
|     } | ||||
|  | ||||
|     .p-radiobutton-box:not(.p-disabled):not(.p-highlight):hover { | ||||
|       border-color: $primaryHeaderColor !important; | ||||
|     } | ||||
|  | ||||
|     .p-radiobutton-box:not(.p-disabled).p-focus { | ||||
|       box-shadow: none !important; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -544,6 +544,7 @@ | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .danger-btn, | ||||
|   .danger-icon-btn { | ||||
|     background-color: transparent !important; | ||||
|     color: $primaryTextColor !important; | ||||
| @@ -560,22 +561,6 @@ | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .danger-btn { | ||||
|     background-color: $primaryErrorColor !important; | ||||
|     color: $primaryErrorColor !important; | ||||
|     border: 0 !important; | ||||
|  | ||||
|     &:hover { | ||||
|       background-color: $primaryErrorColor !important; | ||||
|       color: $primaryTextColor !important; | ||||
|       border: 0; | ||||
|     } | ||||
|  | ||||
|     .pi { | ||||
|       font-size: 1.275rem !important; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .p-datatable .p-sortable-column.p-highlight, | ||||
|   .p-datatable .p-sortable-column.p-highlight .p-sortable-column-icon { | ||||
|     color: $primaryHeaderColor !important; | ||||
| @@ -602,16 +587,23 @@ | ||||
|     color: $primaryHeaderColor !important; | ||||
|   } | ||||
|  | ||||
|   .input-number { | ||||
|     span { | ||||
|       .p-button { | ||||
|         background-color: $primaryHeaderColor !important; | ||||
|         border: 1px solid $primaryHeaderColor !important; | ||||
|   .p-selectbutton { | ||||
|     .p-highlight { | ||||
|       background-color: $primaryHeaderColor !important; | ||||
|     } | ||||
|  | ||||
|         &:hover { | ||||
|           background-color: $secondaryHeaderColor !important; | ||||
|           border: 1px solid $secondaryHeaderColor !important; | ||||
|         } | ||||
|     .p-button { | ||||
|  | ||||
|       border: 1px solid $primaryHeaderColor !important; | ||||
|  | ||||
|       &:hover { | ||||
|         background-color: $secondaryHeaderColor !important; | ||||
|         border: 1px solid $secondaryHeaderColor !important; | ||||
|       } | ||||
|  | ||||
|       &:focus { | ||||
|         border-color: $primaryHeaderColor !important; | ||||
|         box-shadow: none !important; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| @@ -629,12 +621,12 @@ | ||||
|  | ||||
|     .p-multiselect-panel { | ||||
|       .p-multiselect-header { | ||||
|         background-color: $secondaryBackgroundColor !important; | ||||
|         background-color: $primaryBackgroundColor !important; | ||||
|       } | ||||
|  | ||||
|       .p-multiselect-items, | ||||
|       .p-multiselect-item { | ||||
|         background-color: $primaryBackgroundColor !important; | ||||
|         background-color: $secondaryBackgroundColor !important; | ||||
|       } | ||||
|  | ||||
|       .p-multiselect-item { | ||||
| @@ -661,4 +653,141 @@ | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .p-inputswitch.p-inputswitch-checked .p-inputswitch-slider { | ||||
|     background: $primaryHeaderColor !important; | ||||
|   } | ||||
|  | ||||
|   .p-inputswitch.p-focus .p-inputswitch-slider { | ||||
|     box-shadow: none !important; | ||||
|   } | ||||
|  | ||||
|   p-inputNumber { | ||||
|     background-color: $primaryBackgroundColor !important; | ||||
|   } | ||||
|  | ||||
|   p-calendar > span > button { | ||||
|     background-color: $primaryHeaderColor !important; | ||||
|     border: 1px solid $primaryHeaderColor !important; | ||||
|  | ||||
|     &:focus { | ||||
|       box-shadow: none !important; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .p-calendar { | ||||
|     .p-datepicker:not(.p-datepicker-inline) { | ||||
|       background-color: $primaryBackgroundColor !important; | ||||
|     } | ||||
|  | ||||
|     .p-datepicker { | ||||
|       color: $primaryTextColor !important; | ||||
|  | ||||
|       .p-datepicker-header { | ||||
|         color: $primaryHeaderColor !important; | ||||
|         background-color: $primaryBackgroundColor !important; | ||||
|  | ||||
|         .p-datepicker-title .p-datepicker-year, | ||||
|         .p-datepicker-title .p-datepicker-month, | ||||
|         .p-datepicker .p-datepicker-header .p-datepicker-title .p-datepicker-month { | ||||
|           color: $primaryTextColor !important; | ||||
|  | ||||
|           &:hover { | ||||
|             color: $primaryHeaderColor !important; | ||||
|           } | ||||
|  | ||||
|           &:focus { | ||||
|             box-shadow: none !important; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     table td > span { | ||||
|       color: $primaryTextColor !important; | ||||
|  | ||||
|       &:hover { | ||||
|         color: $primaryHeaderColor !important; | ||||
|         background-color: $primaryBackgroundColor !important; | ||||
|       } | ||||
|  | ||||
|       &:focus { | ||||
|         box-shadow: none !important; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     table td.p-datepicker-today > span { | ||||
|       color: $primaryHeaderColor !important; | ||||
|       background-color: $primaryBackgroundColor !important; | ||||
|     } | ||||
|  | ||||
|     table td > span.p-highlight { | ||||
|       color: $primaryHeaderColor !important; | ||||
|       background-color: $primaryBackgroundColor !important; | ||||
|     } | ||||
|  | ||||
|     .p-yearpicker .p-yearpicker-year, | ||||
|     .p-monthpicker .p-monthpicker-month:not(.p-disabled):not(.p-highlight) { | ||||
|       color: $primaryTextColor !important; | ||||
|       background-color: $secondaryBackgroundColor !important; | ||||
|  | ||||
|       &:hover { | ||||
|         color: $primaryHeaderColor !important; | ||||
|       } | ||||
|  | ||||
|       &:focus { | ||||
|         box-shadow: none !important; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .edit-dialog { | ||||
|     textarea { | ||||
|       background-color: $primaryBackgroundColor; | ||||
|       color: $primaryTextColor; | ||||
|  | ||||
|       &:hover { | ||||
|         border-color: $primaryHeaderColor; | ||||
|       } | ||||
|  | ||||
|       &:focus { | ||||
|         border-color: $primaryHeaderColor; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     .p-dialog-content { | ||||
|       .p-tabview { | ||||
|         .p-tabview-nav li .p-tabview-nav-link:not(.p-disabled):focus { | ||||
|           box-shadow: none !important; | ||||
|         } | ||||
|  | ||||
|         .p-tabview-nav li.p-highlight .p-tabview-nav-link { | ||||
|           color: $primaryHeaderColor !important; | ||||
|           border-color: $primaryHeaderColor !important; | ||||
|         } | ||||
|  | ||||
|         .p-tabview-nav, | ||||
|         .p-tabview-nav li .p-tabview-nav-link, | ||||
|         .p-tabview-panels { | ||||
|           background-color: $primaryBackgroundColor !important; | ||||
|           color: $primaryTextColor !important; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .p-radiobutton { | ||||
|     .p-radiobutton-box.p-highlight { | ||||
|       border-color: $primaryHeaderColor !important; | ||||
|       background: $primaryHeaderColor !important; | ||||
|     } | ||||
|  | ||||
|     .p-radiobutton-box:not(.p-disabled):not(.p-highlight):hover { | ||||
|       border-color: $primaryHeaderColor !important; | ||||
|     } | ||||
|  | ||||
|     .p-radiobutton-box:not(.p-disabled).p-focus { | ||||
|       box-shadow: none !important; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -552,6 +552,7 @@ | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .danger-btn, | ||||
|   .danger-icon-btn { | ||||
|     background-color: transparent !important; | ||||
|     color: $primaryTextColor !important; | ||||
| @@ -568,22 +569,6 @@ | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .danger-btn { | ||||
|     background-color: $primaryErrorColor !important; | ||||
|     color: $primaryErrorColor !important; | ||||
|     border: 0 !important; | ||||
|  | ||||
|     &:hover { | ||||
|       background-color: $primaryErrorColor !important; | ||||
|       color: $primaryTextColor !important; | ||||
|       border: 0; | ||||
|     } | ||||
|  | ||||
|     .pi { | ||||
|       font-size: 1.275rem !important; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .p-datatable .p-sortable-column.p-highlight, | ||||
|   .p-datatable .p-sortable-column.p-highlight .p-sortable-column-icon, | ||||
|   .p-datatable .p-sortable-column:not(.p-highlight):hover { | ||||
| @@ -720,6 +705,8 @@ | ||||
|     } | ||||
|  | ||||
|     .p-datepicker { | ||||
|       color: $primaryTextColor !important; | ||||
|  | ||||
|       .p-datepicker-header { | ||||
|         color: $primaryHeaderColor !important; | ||||
|         background-color: $primaryBackgroundColor !important; | ||||
| @@ -777,4 +764,54 @@ | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .edit-dialog { | ||||
|     textarea { | ||||
|       background-color: $secondaryBackgroundColor; | ||||
|       color: $primaryTextColor; | ||||
|  | ||||
|       &:hover { | ||||
|         border-color: $primaryHeaderColor; | ||||
|       } | ||||
|  | ||||
|       &:focus { | ||||
|         border-color: $primaryHeaderColor; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     .p-dialog-content { | ||||
|       .p-tabview { | ||||
|         .p-tabview-nav li .p-tabview-nav-link:not(.p-disabled):focus { | ||||
|           box-shadow: none !important; | ||||
|         } | ||||
|  | ||||
|         .p-tabview-nav li.p-highlight .p-tabview-nav-link { | ||||
|           color: $primaryHeaderColor !important; | ||||
|           border-color: $primaryHeaderColor !important; | ||||
|         } | ||||
|  | ||||
|         .p-tabview-nav, | ||||
|         .p-tabview-nav li .p-tabview-nav-link, | ||||
|         .p-tabview-panels { | ||||
|           background-color: $secondaryBackgroundColor !important; | ||||
|           color: $primaryTextColor !important; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .p-radiobutton { | ||||
|     .p-radiobutton-box.p-highlight { | ||||
|       border-color: $primaryHeaderColor !important; | ||||
|       background: $primaryHeaderColor !important; | ||||
|     } | ||||
|  | ||||
|     .p-radiobutton-box:not(.p-disabled):not(.p-highlight):hover { | ||||
|       border-color: $primaryHeaderColor !important; | ||||
|     } | ||||
|  | ||||
|     .p-radiobutton-box:not(.p-disabled).p-focus { | ||||
|       box-shadow: none !important; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -544,6 +544,7 @@ | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .danger-btn, | ||||
|   .danger-icon-btn { | ||||
|     background-color: transparent !important; | ||||
|     color: $primaryTextColor !important; | ||||
| @@ -560,22 +561,6 @@ | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .danger-btn { | ||||
|     background-color: $primaryErrorColor !important; | ||||
|     color: $primaryErrorColor !important; | ||||
|     border: 0 !important; | ||||
|  | ||||
|     &:hover { | ||||
|       background-color: $primaryErrorColor !important; | ||||
|       color: $primaryTextColor !important; | ||||
|       border: 0; | ||||
|     } | ||||
|  | ||||
|     .pi { | ||||
|       font-size: 1.275rem !important; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .p-datatable .p-sortable-column.p-highlight, | ||||
|   .p-datatable .p-sortable-column.p-highlight .p-sortable-column-icon { | ||||
|     color: $primaryHeaderColor !important; | ||||
| @@ -661,4 +646,142 @@ | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|  | ||||
|   .p-inputswitch.p-inputswitch-checked .p-inputswitch-slider { | ||||
|     background: $primaryHeaderColor !important; | ||||
|   } | ||||
|  | ||||
|   .p-inputswitch.p-focus .p-inputswitch-slider { | ||||
|     box-shadow: none !important; | ||||
|   } | ||||
|  | ||||
|   p-inputNumber { | ||||
|     background-color: $primaryBackgroundColor !important; | ||||
|   } | ||||
|  | ||||
|   p-calendar > span > button { | ||||
|     background-color: $primaryHeaderColor !important; | ||||
|     border: 1px solid $primaryHeaderColor !important; | ||||
|  | ||||
|     &:focus { | ||||
|       box-shadow: none !important; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .p-calendar { | ||||
|     .p-datepicker:not(.p-datepicker-inline) { | ||||
|       background-color: $primaryBackgroundColor !important; | ||||
|     } | ||||
|  | ||||
|     .p-datepicker { | ||||
|       color: $primaryTextColor !important; | ||||
|  | ||||
|       .p-datepicker-header { | ||||
|         color: $primaryHeaderColor !important; | ||||
|         background-color: $primaryBackgroundColor !important; | ||||
|  | ||||
|         .p-datepicker-title .p-datepicker-year, | ||||
|         .p-datepicker-title .p-datepicker-month, | ||||
|         .p-datepicker .p-datepicker-header .p-datepicker-title .p-datepicker-month { | ||||
|           color: $primaryTextColor !important; | ||||
|  | ||||
|           &:hover { | ||||
|             color: $primaryHeaderColor !important; | ||||
|           } | ||||
|  | ||||
|           &:focus { | ||||
|             box-shadow: none !important; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     table td > span { | ||||
|       color: $primaryTextColor !important; | ||||
|  | ||||
|       &:hover { | ||||
|         color: $primaryHeaderColor !important; | ||||
|         background-color: $primaryBackgroundColor !important; | ||||
|       } | ||||
|  | ||||
|       &:focus { | ||||
|         box-shadow: none !important; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     table td.p-datepicker-today > span { | ||||
|       color: $primaryHeaderColor !important; | ||||
|       background-color: $primaryBackgroundColor !important; | ||||
|     } | ||||
|  | ||||
|     table td > span.p-highlight { | ||||
|       color: $primaryHeaderColor !important; | ||||
|       background-color: $primaryBackgroundColor !important; | ||||
|     } | ||||
|  | ||||
|     .p-yearpicker .p-yearpicker-year, | ||||
|     .p-monthpicker .p-monthpicker-month:not(.p-disabled):not(.p-highlight) { | ||||
|       color: $primaryTextColor !important; | ||||
|       background-color: $secondaryBackgroundColor !important; | ||||
|  | ||||
|       &:hover { | ||||
|         color: $primaryHeaderColor !important; | ||||
|       } | ||||
|  | ||||
|       &:focus { | ||||
|         box-shadow: none !important; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .edit-dialog { | ||||
|     textarea { | ||||
|       background-color: $primaryBackgroundColor; | ||||
|       color: $primaryTextColor; | ||||
|  | ||||
|       &:hover { | ||||
|         border-color: $primaryHeaderColor; | ||||
|       } | ||||
|  | ||||
|       &:focus { | ||||
|         border-color: $primaryHeaderColor; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     .p-dialog-content { | ||||
|       .p-tabview { | ||||
|         .p-tabview-nav li .p-tabview-nav-link:not(.p-disabled):focus { | ||||
|           box-shadow: none !important; | ||||
|         } | ||||
|  | ||||
|         .p-tabview-nav li.p-highlight .p-tabview-nav-link { | ||||
|           color: $primaryHeaderColor !important; | ||||
|           border-color: $primaryHeaderColor !important; | ||||
|         } | ||||
|  | ||||
|         .p-tabview-nav, | ||||
|         .p-tabview-nav li .p-tabview-nav-link, | ||||
|         .p-tabview-panels { | ||||
|           background-color: $primaryBackgroundColor !important; | ||||
|           color: $primaryTextColor !important; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .p-radiobutton { | ||||
|     .p-radiobutton-box.p-highlight { | ||||
|       border-color: $primaryHeaderColor !important; | ||||
|       background: $primaryHeaderColor !important; | ||||
|     } | ||||
|  | ||||
|     .p-radiobutton-box:not(.p-disabled):not(.p-highlight):hover { | ||||
|       border-color: $primaryHeaderColor !important; | ||||
|     } | ||||
|  | ||||
|     .p-radiobutton-box:not(.p-disabled).p-focus { | ||||
|       box-shadow: none !important; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user