Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions bin/sandbox-vite-build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { build } from "vite";
import react from "@vitejs/plugin-react";

await build({
configFile: false,
plugins: [react()],
});
28 changes: 28 additions & 0 deletions src/components/ConfirmDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
type Props = {
isOpen: boolean;
message: string;
onConfirm: () => void;
onCancel: () => void;
};

export function ConfirmDialog({
isOpen,
message,
onConfirm,
onCancel
}: Props) {
if (!isOpen) return null;

return (
<div className='dialogBackdrop'>
<div data-testid="confirm-dialog" className='dialog'>
<p>{message}</p>
<div className='dialogButtonContainer'>
<button data-testid="confirm-button" onClick={onConfirm}>Tak</button>
<button data-testid="cancel-button" onClick={onCancel}>Nie</button>
</div>
</div>
</div>
);
}

108 changes: 108 additions & 0 deletions src/components/activityForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import User from "../model/User";
import { ActivityFormState } from "../model/ActivityFormState";
import TemplateButtons from "./templateButtons";
import { ActivityValidationResult } from "../model/ActivityValidationResult";

const currentDateTimeUTC = new Date()
const timeZoneOffset = currentDateTimeUTC.getTimezoneOffset()
const currentDateTimeLocal = new Date(currentDateTimeUTC.getTime() - timeZoneOffset * 60 * 1000)
const currentDate = currentDateTimeLocal.toISOString().split("T")[0]
const yesterdayDateTimeLocal = new Date(currentDateTimeLocal.getTime() - 24 * 60 * 60 * 1000);
const yesterdayDate = yesterdayDateTimeLocal.toISOString().split("T")[0]

export function ActivityForm(
{
activity,
users,
validationResult,
onActivityPropertyChanged,
onTemplateButtonClicked,
onSubmit,
onCancel
} : {
activity: ActivityFormState,
users: Map<string, User>,
validationResult: ActivityValidationResult,
onActivityPropertyChanged: <K extends keyof ActivityFormState>(key: K, value: ActivityFormState[K]) => void;
onTemplateButtonClicked: (type: string, exp: number) => void,
onSubmit: (e: any) => void,
onCancel: (e: any) => void
}
) {
return (
<form onSubmit={onSubmit}>
<div className="entryDetails">
<p className="label">Data wykonania czynności</p>
<div>
<button type="button" data-testid="yesterday-button" className="entityButton" onClick={() => onActivityPropertyChanged("date", yesterdayDate)}>Wczoraj</button>
<button type="button" data-testid="today-button" className="entityButton" onClick={() => onActivityPropertyChanged("date", currentDate)}>Dziś</button>

<input
id="activityDate"
data-testid="activity-date-input"
aria-label="Date"
type="date"
value={activity.date}
className="entityDatePicker"
onChange={ e => onActivityPropertyChanged("date", e.target.value)}
/>
</div>

<p className="label">Wykonawca</p>
{validationResult.user ? (<p className="validationMessage">{validationResult.user}</p>) : (<></>)}
<p><select
id="activityPerson"
data-testid="activity-person-input"
className="entityText"
value={activity.user}
onChange={e => onActivityPropertyChanged("user", e.target.value)}
>
{Array.from(users.values()).map((user: User) => {
return <option key={user.id} value={user.id}>{user.nickname}</option>
}
)}
</select></p>

<p className="label">Szablony</p>
<TemplateButtons fillTemplate={onTemplateButtonClicked} />

<p className="label">Czynność</p>
{validationResult.type ? (<p className="validationMessage">{validationResult.type}</p>) : (<></>)}
<p><input
id="activityType"
data-testid="activity-type-input"
type="text"
className="entityTextArea"
value={activity.type}
onChange={ e => onActivityPropertyChanged("type", e.target.value)}
/></p>

<p className="label">Zdobyte punkty doświadczenia</p>
{validationResult.exp ? (<p className="validationMessage">{validationResult.exp}</p>) : (<></>)}
<p><input
id="activityExp"
data-testid="activity-exp-input"
type="text"
className="entityText"
value={activity.exp}
onChange={ e => onActivityPropertyChanged("exp", e.target.value)}
/></p>

<p className="label">Komentarz</p>
{validationResult.comment ? (<p className="validationMessage">{validationResult.comment}</p>) : (<></>)}
<p><textarea
id="activityComment"
data-testid="activity-comment-input"
className="entityTextArea"
rows={5}
value={activity.comment}
onChange={ e => onActivityPropertyChanged("comment", e.target.value)}
/></p>
</div>
<div>
<button type="submit" data-testid="submit-button">Zatwierdź</button>
<button type="button" data-testid="cancel-button" onClick={onCancel}>Anuluj</button>
</div>
</form>
)
}
38 changes: 38 additions & 0 deletions src/hooks/useConfirm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useState } from "react";
import { ConfirmDialog } from "../components/ConfirmDialog";

type ConfirmState = {
message: string;
resolve: (value: boolean) => void;
} | null;

export function useConfirm() {
const [state, setState] = useState<ConfirmState>(null);

const confirm = (message: string) => {
return new Promise<boolean>((resolve) => {
setState({ message, resolve });
});
};

const handleConfirm = () => {
state?.resolve(true);
setState(null);
};

const handleCancel = () => {
state?.resolve(false);
setState(null);
};

const dialog = (
<ConfirmDialog
isOpen={!!state}
message={state?.message ?? ""}
onConfirm={handleConfirm}
onCancel={handleCancel}
/>
);

return { confirm, dialog };
}
27 changes: 27 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -437,3 +437,30 @@ ul.richText {
border-radius: 0;
}

.dialog {
background: white;
color: black;
padding: 20px;
min-width: 300px;
box-shadow: 0 4px 12px rgba(0,0,0,0.2)
}

.dialogButtonContainer {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 15px;
}

.dialogBackdrop {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.4);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
6 changes: 0 additions & 6 deletions src/model/ActivityFormErrorMessages.ts

This file was deleted.

4 changes: 4 additions & 0 deletions src/model/ActivityValidationResult.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { ActivityFormState } from "./ActivityFormState";

export type ValidationErrors<T> = Partial<Record<keyof T, string>>;
export type ActivityValidationResult = ValidationErrors<ActivityFormState>;
11 changes: 11 additions & 0 deletions src/model/OperationResult.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type OperationResult =
| { success: true; data: string }
| { success: false; message: string; details?: unknown };

export function success(data: string): OperationResult {
return { success: true, data: data};
}

export function failure(message: string, details?: unknown): OperationResult {
return { success: false, message, details };
}
2 changes: 2 additions & 0 deletions src/model/WorkRequestFormState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ export type WorkRequestFormState = {
exp: string;
urgency: string;
instructions: string;
completed: boolean;
completedAs?: string;
}
65 changes: 65 additions & 0 deletions src/model/mappers/activityMapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type { Schema } from "../../../amplify/data/resource";
import type { ActivityFormState } from "../../model/ActivityFormState";
import { AuthUser } from "aws-amplify/auth";
import { toLocalDate, getCurrentDate } from "../../utils/dateUtils";
import reportError from "../../utils/reportError"

function mapActivityModelToActivityFormState(model: Schema["Activity"]["type"] | null): ActivityFormState | null {
if (model === null) {
return null;
}
return {
id: model.id,
date: toLocalDate(model.date),
user: model.user,
type: model.type,
exp: model.exp.toString(),
comment: model.comment ?? "",
requestedAs: model.requestedAs ?? undefined
}
}

function mapWorkRequestModelToActivityFormState(model: Schema["WorkRequest"]["type"] | null, currentUser: AuthUser): ActivityFormState | null {
if (model === null) {
return null;
}

return {
date: getCurrentDate(),
user: currentUser.userId,
type: model.type,
exp: model.exp.toString(),
comment: "",
requestedAs: model.id
};
}

function mapActivityFormStateToActivityModel(activity: ActivityFormState) {
if (activity.date === undefined) {
throw new Error(reportError("State activityDate is undefined during creation of a new activity object"))
}
if (activity.user === undefined) {
throw new Error(reportError("State activityPerson is undefined during creation of a new activity object"))
}
if (activity.type === undefined) {
throw new Error(reportError("State activityType is undefined during creation of a new activity object"))
}
if (activity.exp === undefined || isNaN(Number(activity.exp))) {
throw new Error(reportError("State activityExp is undefined during creation of a new activity object"))
}
return {
id: activity.id,
date: activity.date,
user: activity.user,
type: activity.type,
exp: Number(activity.exp),
comment: activity.comment,
requestedAs: activity.requestedAs
}
}

export {
mapActivityModelToActivityFormState as activityModelToActivityFormState,
mapWorkRequestModelToActivityFormState as workRequestModelToActivityFormState,
mapActivityFormStateToActivityModel as createActivityObjectFromState
}
Loading