Skip to content

Commit

Permalink
add description to training type
Browse files Browse the repository at this point in the history
- some general clean up
- add optional field to training type (description)
  • Loading branch information
ngoerlitz committed Apr 12, 2024
1 parent b31798c commit c0a3b5c
Show file tree
Hide file tree
Showing 21 changed files with 139 additions and 54 deletions.
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ src/**/*.js
**/*.css
**/*.config.ts
**/*.html
**/*.d.ts
**/*.cjs

index.html

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export const TRAINING_TYPES_TABLE_ATTRIBUTES = {
comment: "Name of training type (eg. 'Frankfurt Tower Online'). Max length 70 chars",
allowNull: false,
},
description: {
type: DataType.TEXT,
allowNull: true,
},
type: {
type: DataType.ENUM(...TRAINING_TYPES_TABLE_TYPES),
comment: "Type of Training Type (ie. Sim Session - Sim)",
Expand Down
5 changes: 3 additions & 2 deletions backend/src/Application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const application: Express = express();
function logStartupOptions() {
Logger.log(LogLevels.LOG_WARN, `Debug mode: ${Config.APP_DEBUG ? "ENABLED" : "DISABLED"}`);
Logger.log(LogLevels.LOG_WARN, `SQL logging: ${Config.APP_LOG_SQL ? "ENABLED" : "DISABLED"}`);
Logger.log(LogLevels.LOG_WARN, `CORS enabled: ${Config.APP_CORS_ALLOW ? "ENABLED" : "DISABLED"}`);
Logger.log(LogLevels.LOG_WARN, `File Upload: {location: ${Config.FILE_STORAGE_LOCATION}, tmp: ${Config.FILE_TMP_LOCATION}}\n`);

Logger.log(LogLevels.LOG_SUCCESS, `Server is running on http://${Config.APP_HOST ?? "0.0.0.0"}:${Config.APP_PORT}`, true);
Expand All @@ -22,11 +23,11 @@ function logStartupOptions() {
initializeApplication()
.then(() => {
// Basic server configuration
if (Config.APP_DEBUG) {
if (Config.APP_CORS_ALLOW) {
application.use(
cors({
credentials: true,
origin: Config.APP_CORS_ALLOW,
origin: "http://localhost:8000",
})
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,15 @@ async function getAll(request: Request, response: Response) {
* Gets a training type by its ID
* @param request
* @param response
* @param next
*/
async function getByID(request: Request, response: Response, next: NextFunction) {
try {
const params = request.params as { id: string };

// const validation = ValidationHelper.validate([
// {
// name: "id",
// validationObject: params.id,
// toValidate: [{ val: ValidationOptions.NON_NULL }],
// },
// ]);
Validator.validate(params, {
id: [ValidationTypeEnum.NON_NULL, ValidationTypeEnum.NUMBER],
});

const trainingType = await TrainingType.findOne({
where: {
Expand Down Expand Up @@ -65,7 +62,7 @@ async function getByID(request: Request, response: Response, next: NextFunction)
* @param response
*/
async function create(request: Request, response: Response) {
const body = request.body as { name: string; type: "online" | "sim" | "lesson" | "cpt"; log_template_id?: string };
const body = request.body as { name: string; type: "online" | "sim" | "lesson" | "cpt"; log_template_id?: string; description?: string };
Validator.validate(body, {
name: [ValidationTypeEnum.NON_NULL],
type: [ValidationTypeEnum.NON_NULL, { option: ValidationTypeEnum.IN_ARRAY, value: ["online", "sim", "lesson", "cpt"] }],
Expand All @@ -75,6 +72,7 @@ async function create(request: Request, response: Response) {
const trainingType = await TrainingType.create({
name: body.name,
type: body.type,
description: !body.description || body.description == "" ? null : body.description,
log_template_id: isNaN(log_template_id) || log_template_id == -1 ? null : log_template_id,
});

Expand All @@ -88,9 +86,7 @@ async function create(request: Request, response: Response) {
*/
async function update(request: Request, response: Response) {
const training_type_id = request.params.id;
const body = request.body as { name: string; type: "online" | "sim" | "cpt" | "lesson"; log_template_id?: string };

console.log(body);
const body = request.body as { name: string; type: "online" | "sim" | "cpt" | "lesson"; log_template_id?: string; description?: string };

Validator.validate(body, {
name: [ValidationTypeEnum.NON_NULL],
Expand All @@ -111,6 +107,7 @@ async function update(request: Request, response: Response) {
let updatedTrainingType = await trainingType.update({
name: body.name,
type: body.type,
description: !body.description || body.description == "" ? null : body.description,
log_template_id: body.log_template_id == null || isNaN(Number(body.log_template_id)) ? null : Number(body.log_template_id),
});

Expand Down
13 changes: 5 additions & 8 deletions backend/src/controllers/training-type/TrainingTypeController.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
import { NextFunction, Request, Response } from "express";
import { TrainingType } from "../../models/TrainingType";
import Validator, { ValidationTypeEnum } from "../../utility/Validator";

async function getByID(request: Request, response: Response, next: NextFunction) {
try {
const params = request.params as { id: string };

// const validation = ValidationHelper.validate([
// {
// name: "id",
// validationObject: params.id,
// toValidate: [{ val: ValidationOptions.NON_NULL }],
// },
// ]);
Validator.validate(params, {
id: [ValidationTypeEnum.NON_NULL, ValidationTypeEnum.NUMBER],
});

const trainingType = await TrainingType.findOne({
where: {
id: params.id,
},
attributes: ["id", "name", "type"],
attributes: ["id", "name", "type", "description"],
include: {
association: TrainingType.associations.training_stations,
attributes: ["id", "callsign", "frequency"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,29 @@ import { Course } from "../../models/Course";
import { TrainingRequest } from "../../models/TrainingRequest";
import { TrainingSession } from "../../models/TrainingSession";
import { UsersBelongsToCourses } from "../../models/through/UsersBelongsToCourses";
import Validator, { ValidationTypeEnum } from "../../utility/Validator";

/**
* Returns information about the progress of a user within the specified course.
* For example:
* - Training Requests
* - Training Sessions
* - Training Logs
* etc.
* @param request
* @param response
* @param next
*/
async function getInformation(request: Request, response: Response, next: NextFunction) {
try {
const query = request.query as { course_uuid: string; user_id: string };
_UserCourseProgressAdministrationValidator.validateGetAllRequest(query);
Validator.validate(query, {
course_uuid: [ValidationTypeEnum.NON_NULL],
user_id: [ValidationTypeEnum.NON_NULL, ValidationTypeEnum.NUMBER],
});

// TODO: Return the relevant information for this course ONLY!
// Currently, the controller returns ALL Requests and Histories for the user with this ID, not only those in the specified course!

const user = await User.findOne({
where: {
Expand Down
2 changes: 1 addition & 1 deletion backend/src/core/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const Config = {
// Read from .env
APP_DEBUG: process.env.APP_DEBUG?.toLowerCase() == "true",
APP_LOG_SQL: process.env.APP_LOG_SQL?.toLowerCase() == "true",
APP_CORS_ALLOW: process.env.APP_CORS_ALLOW ?? "*",
APP_CORS_ALLOW: process.env.APP_CORS_ALLOW == "true",

APP_KEY: process.env.APP_KEY,
APP_PORT: Number(process.env.APP_PORT),
Expand Down
2 changes: 1 addition & 1 deletion backend/src/libraries/vatsim/ConnectLibrary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ export class VatsimConnectLibrary {
*/
private async _checkIsUserAllowed() {
if (this.m_userData == undefined) return null;
const allowed_cids = [1373921, 1450775, 1331358, 1357290, 1439797, 1583954, 1438611, 1432304, 1439600, 1463320, 1238939];
const allowed_cids = [1373921, 1450775, 1331358, 1357290, 1439797, 1583954, 1438611, 1432304, 1439600, 1463320, 1238939, 10000010];

if (!allowed_cids.includes(Number(this.m_userData.data.cid))) {
throw new VatsimConnectException(ConnectLibraryErrors.ERR_SUSPENDED);
Expand Down
13 changes: 13 additions & 0 deletions backend/src/models/TrainingType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ import {
TRAINING_TYPES_TABLE_TYPES,
} from "../../db/migrations/20221115171246-create-training-types-table";

export interface ITrainingType {
id: number;
name: string;
type: (typeof TRAINING_TYPES_TABLE_NAME)[number];
description?: string;
log_template_id?: number;
createdAt?: Date;
updatedAt?: Date;

training_stations?: any;
}

export class TrainingType extends Model<InferAttributes<TrainingType>, InferCreationAttributes<TrainingType>> {
//
// Attributes
Expand All @@ -21,6 +33,7 @@ export class TrainingType extends Model<InferAttributes<TrainingType>, InferCrea
// Optional Attributes
//
declare id: CreationOptional<number>;
declare description: CreationOptional<string> | null;
declare log_template_id: CreationOptional<ForeignKey<TrainingLogTemplate["id"]>> | null;
declare createdAt: CreationOptional<Date> | null;
declare updatedAt: CreationOptional<Date> | null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Link, NavigateFunction } from "react-router-dom";
import { TableColumn } from "react-data-table-component";
import { UserModel } from "../../../../../../models/UserModel";
import { Button } from "../../../../../../components/ui/Button/Button";
import { COLOR_OPTS, SIZE_OPTS } from "../../../../../../assets/theme.config";
import { UserModel } from "@/models/UserModel";
import { Button } from "@/components/ui/Button/Button";
import { COLOR_OPTS, SIZE_OPTS } from "@/assets/theme.config";
import { TbEye, TbTrash } from "react-icons/tb";
import { Badge } from "../../../../../../components/ui/Badge/Badge";
import { Badge } from "@/components/ui/Badge/Badge";
import { Dispatch } from "react";

function getColumns(
Expand All @@ -25,7 +25,7 @@ function getColumns(
row == null ? (
"N/A"
) : (
<Link to={"/administration/user/" + row.id}>
<Link to={"/administration/users/" + row.id}>
<span className={"text-primary hover:cursor-pointer hover:underline"}>
{row.first_name} {row.last_name}
</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import useApi from "@/utils/hooks/useApi";
import { axiosInstance } from "@/utils/network/AxiosInstance";
import { AxiosResponse } from "axios";
import { TrainingTypeModel } from "@/models/TrainingTypeModel";
import { TextArea } from "@/components/ui/Textarea/TextArea";

export function TrainingTypeCreateView() {
const navigate = useNavigate();
Expand Down Expand Up @@ -93,6 +94,16 @@ export function TrainingTypeCreateView() {
</Select>
</div>

<TextArea
label={"Beschreibung"}
labelSmall
className={"mt-5"}
description={
"Optionale Beschreibung des Trainingstyps. Wird dem Benutzer bei der Anfrage angezeigt. Könnte bspw. weitere Voraussetzungen, wie das Bestehen eines Moodle-Kurses beinhalten."
}
name={"description"}
/>

<Separator />

<Select
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Input } from "@/components/ui/Input/Input";
import { TbBook2, TbCirclePlus, TbEdit, TbId, TbTemplate, TbTrash } from "react-icons/tb";
import { TbBook2, TbEdit, TbId, TbTemplate } from "react-icons/tb";
import { Select } from "@/components/ui/Select/Select";
import { Separator } from "@/components/ui/Separator/Separator";
import { Button } from "@/components/ui/Button/Button";
Expand All @@ -15,6 +15,8 @@ import { MapArray } from "@/components/conditionals/MapArray";
import { TTViewSettingsSkeleton } from "@/pages/administration/lm/training-type/_skeletons/TTViewSettings.skeleton";
import { axiosInstance } from "@/utils/network/AxiosInstance";
import { AxiosResponse } from "axios";
import { ITrainingType } from "@models/TrainingType";
import { TextArea } from "@/components/ui/Textarea/TextArea";

type TrainingTypeViewSettingsSubpageProps = {
trainingTypeID?: string;
Expand All @@ -28,7 +30,7 @@ export function TTVSettingsSubpage(props: TrainingTypeViewSettingsSubpageProps)
data: trainingType,
loading: loadingTrainingType,
setData: setTrainingType,
} = useApi<TrainingTypeModel>({
} = useApi<ITrainingType>({
url: `/administration/training-type/${props.trainingTypeID ?? "-1"}`,
method: "get",
onLoad: trainingType => {
Expand Down Expand Up @@ -109,6 +111,17 @@ export function TTVSettingsSubpage(props: TrainingTypeViewSettingsSubpageProps)
</Select>
</div>

<TextArea
label={"Beschreibung"}
labelSmall
className={"mt-5"}
description={
"Optionale Beschreibung des Trainingstyps. Wird dem Benutzer bei der Anfrage angezeigt. Könnte bspw. weitere Voraussetzungen, wie das Bestehen eines Moodle-Kurses beinhalten."
}
name={"description"}
value={trainingType?.description}
/>

<Separator />

<Select
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { axiosInstance } from "@/utils/network/AxiosInstance";
import { AxiosResponse } from "axios";
import useApi from "@/utils/hooks/useApi";
import { TrainingTypeModel } from "@/models/TrainingTypeModel";
import { ITrainingType } from "@models/TrainingType";

type RequestTrainingModalPartialProps = {
show: boolean;
Expand All @@ -33,7 +34,7 @@ type RequestTrainingModalPartialProps = {
export function CAVRequestTrainingModal(props: RequestTrainingModalPartialProps) {
const [submitting, setSubmitting] = useState<boolean>(false);

const { data: nextTraining, loading: loadingNextTraining } = useApi<TrainingTypeModel>({
const { data: nextTraining, loading: loadingNextTraining } = useApi<ITrainingType>({
url: `/training-type/${props.course?.UsersBelongsToCourses?.next_training_type}`,
method: "get",
});
Expand Down Expand Up @@ -67,22 +68,32 @@ export function CAVRequestTrainingModal(props: RequestTrainingModalPartialProps)
elementTrue={<CAVTrainingModalSkeleton />}
elementFalse={
<form onSubmit={handleSubmit}>
<Input disabled label={"Name"} labelSmall readOnly value={nextTraining?.name ?? ""} />
<div className={"grid gap-5 grid-cols-1 sm:grid-cols-2"}>
<Input disabled label={"Name"} labelSmall readOnly value={nextTraining?.name ?? ""} />

<Input disabled className={"mt-5"} label={"Typ"} readOnly labelSmall value={StringHelper.capitalize(nextTraining?.type) ?? ""} />
<Input disabled label={"Typ"} readOnly labelSmall value={StringHelper.capitalize(nextTraining?.type) ?? ""} />
</div>

<RenderIf
truthValue={nextTraining?.description != null}
elementTrue={
<TextArea className={"mt-5"} label={"Beschreibung"} labelSmall disabled value={nextTraining?.description} />
}
/>

<Separator />

<RenderIf
truthValue={nextTraining?.type == "cpt"}
elementTrue={
<Alert className={"mt-5"} rounded showIcon type={TYPE_OPTS.DANGER}>
<Alert rounded showIcon type={TYPE_OPTS.DANGER}>
Das aktuell zugewiesene Training ist ein CPT. Dieses kannst du nicht beantragen. Sollte das CPT noch nicht geplant worden
sein (siehe unten in der Trainingshistorie), spreche bitte mit einem Mentoren.
</Alert>
}
elementFalse={
<>
<TextArea
className={"mt-5"}
label={"Kommentar"}
maxLength={150}
description={"Optionaler Kommentar, bspw. zu deiner generellen Verfügbarkeit"}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ function getColumns(navigate: NavigateFunction): TableColumn<TrainingRequestMode
cell: row => {
switch (row.status) {
case "requested":
const n = <span className={"text-info"}>Position in Warteschlange {row.number_in_queue}</span>;
return (
<div>
<Badge color={COLOR_OPTS.PRIMARY}>Beantragt</Badge> {n}
<Badge color={COLOR_OPTS.PRIMARY}>Beantragt</Badge>
</div>
);

Expand All @@ -46,6 +45,10 @@ function getColumns(navigate: NavigateFunction): TableColumn<TrainingRequestMode
return a.status > b.status ? -1 : 1;
},
},
{
name: "Position",
selector: row => (row.number_in_queue ? `#${row.number_in_queue}` : "N/A"),
},
{
name: "Station",
selector: row => row.training_station?.callsign?.toUpperCase() ?? "N/A",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,6 @@ export function CourseView() {
<div className={"grid grid-cols-1 md:grid-cols-2 gap-5 mb-5"}>
<Input labelSmall preIcon={<TbId size={20} />} label={"Name"} disabled value={course?.name} />

<Input labelSmall preIcon={<TbId size={20} />} label={"UUID"} disabled value={course?.uuid} />

<Input
labelSmall
preIcon={<TbClock size={20} />}
Expand Down
Loading

0 comments on commit c0a3b5c

Please sign in to comment.