Compare commits

...

20 commits

Author SHA1 Message Date
Imbus
3526decbad Merge imbs 2024-03-16 17:46:08 +01:00
Imbus
151d6de39b Tygo for go->typescript type generation 2024-03-16 17:43:38 +01:00
Imbus
3c87fd4d8c New api interface 2024-03-16 17:42:28 +01:00
Imbus
17c8a17ebf IF EXISTS condition in salts table 2024-03-16 17:32:51 +01:00
Imbus
47d7d9fe3c Activity type database changes with interface changes in go 2024-03-16 17:22:55 +01:00
dDogge
04e17a1721 Added comments to various functions 2024-03-15 16:57:42 +01:00
Imbus
976ce5900c Merge branch 'dev' of github.com:imbus64/TTime into dev 2024-03-15 16:45:33 +01:00
dDogge
018dc24516 Handler for GetProject from db.go added in global_stage.go 2024-03-15 16:45:26 +01:00
dDogge
581209742a Added GetProject in db.go and corresponding test 2024-03-15 16:45:26 +01:00
Mattias
4920966388 Additional register-layout changes 2024-03-15 14:52:05 +01:00
Davenludd
a388109a8a Add type attribute to button elements in Admin Pages 2024-03-15 14:34:08 +01:00
Davenludd
a16d1d8011 Add type="button" to buttons in PMChangeRole, PMOtherUsersTR, PMProjectMembers, PMProjectPage, PMTotalTimeActivity, PMTotalTimeRole, PMUnsignedReports, and PMViewUnsignedReport components 2024-03-15 14:32:17 +01:00
Davenludd
855dccdfa4 Add type="button" to buttons 2024-03-15 14:30:53 +01:00
Davenludd
a49cfc9f01 Update button styles and add type prop 2024-03-15 14:30:45 +01:00
Mattias
6a25eca01c Changed layout for register-component 2024-03-15 14:16:11 +01:00
Imbus
7f46202633 Merge branch 'gruppDM' of github.com:imbus64/TTime into gruppDM 2024-03-15 12:36:48 +01:00
pavel Hamawand
44b65858aa remove unused import 2024-03-15 12:35:46 +01:00
Imbus
31d82fd1d5 Merge branch 'gruppDM' of github.com:imbus64/TTime into gruppDM 2024-03-15 12:35:35 +01:00
pavel Hamawand
1f16afe528 remove unused import 2024-03-15 12:35:20 +01:00
pavel Hamawand
029e7922d9 remove unused import 2024-03-14 23:49:39 +01:00
36 changed files with 213 additions and 58 deletions

View file

@ -118,3 +118,7 @@ uml: plantuml.jar
install-just: install-just:
@echo "Installing just" @echo "Installing just"
@curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin @curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
.PHONY: types
types:
tygo generate

View file

@ -21,7 +21,7 @@ type Database interface {
AddProject(name string, description string, username string) error AddProject(name string, description string, username string) error
Migrate(dirname string) error Migrate(dirname string) error
GetProjectId(projectname string) (int, error) GetProjectId(projectname string) (int, error)
AddTimeReport(projectName string, userName string, start time.Time, end time.Time) error AddTimeReport(projectName string, userName string, activityType string, start time.Time, end time.Time) error
AddUserToProject(username string, projectname string, role string) error AddUserToProject(username string, projectname string, role string) error
ChangeUserRole(username string, projectname string, role string) error ChangeUserRole(username string, projectname string, role string) error
GetAllUsersProject(projectname string) ([]UserProjectMember, error) GetAllUsersProject(projectname string) ([]UserProjectMember, error)
@ -52,8 +52,8 @@ const projectInsert = "INSERT INTO projects (name, description, owner_user_id) S
const promoteToAdmin = "INSERT INTO site_admin (admin_id) SELECT id FROM users WHERE username = ?" const promoteToAdmin = "INSERT INTO site_admin (admin_id) SELECT id FROM users WHERE username = ?"
const addTimeReport = `WITH UserLookup AS (SELECT id FROM users WHERE username = ?), const addTimeReport = `WITH UserLookup AS (SELECT id FROM users WHERE username = ?),
ProjectLookup AS (SELECT id FROM projects WHERE name = ?) ProjectLookup AS (SELECT id FROM projects WHERE name = ?)
INSERT INTO time_reports (project_id, user_id, start, end) INSERT INTO time_reports (project_id, user_id, activity_type, start, end)
VALUES ((SELECT id FROM ProjectLookup), (SELECT id FROM UserLookup), ?, ?);` VALUES ((SELECT id FROM ProjectLookup), (SELECT id FROM UserLookup),?, ?, ?);`
const addUserToProject = "INSERT INTO user_roles (user_id, project_id, p_role) VALUES (?, ?, ?)" // WIP const addUserToProject = "INSERT INTO user_roles (user_id, project_id, p_role) VALUES (?, ?, ?)" // WIP
const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = ? AND project_id = ?" const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = ? AND project_id = ?"
@ -89,29 +89,34 @@ func DbConnect(dbpath string) Database {
return &Db{db} return &Db{db}
} }
// GetProjectsForUser retrieves all projects associated with a specific user.
func (d *Db) GetProjectsForUser(username string) ([]types.Project, error) { func (d *Db) GetProjectsForUser(username string) ([]types.Project, error) {
var projects []types.Project var projects []types.Project
err := d.Select(&projects, getProjectsForUser, username) err := d.Select(&projects, getProjectsForUser, username)
return projects, err return projects, err
} }
// GetAllProjects retrieves all projects from the database.
func (d *Db) GetAllProjects() ([]types.Project, error) { func (d *Db) GetAllProjects() ([]types.Project, error) {
var projects []types.Project var projects []types.Project
err := d.Select(&projects, "SELECT * FROM projects") err := d.Select(&projects, "SELECT * FROM projects")
return projects, err return projects, err
} }
// GetProject retrieves a specific project by its ID.
func (d *Db) GetProject(projectId int) (types.Project, error) { func (d *Db) GetProject(projectId int) (types.Project, error) {
var project types.Project var project types.Project
err := d.Select(&project, "SELECT * FROM projects WHERE id = ?") err := d.Select(&project, "SELECT * FROM projects WHERE id = ?")
return project, err return project, err
} }
func (d *Db) AddTimeReport(projectName string, userName string, start time.Time, end time.Time) error { // WIP // AddTimeReport adds a time report for a specific project and user.
_, err := d.Exec(addTimeReport, userName, projectName, start, end) func (d *Db) AddTimeReport(projectName string, userName string, activityType string, start time.Time, end time.Time) error { // WIP
_, err := d.Exec(addTimeReport, userName, projectName, activityType, start, end)
return err return err
} }
// AddUserToProject adds a user to a project with a specified role.
func (d *Db) AddUserToProject(username string, projectname string, role string) error { // WIP func (d *Db) AddUserToProject(username string, projectname string, role string) error { // WIP
var userid int var userid int
userid, err := d.GetUserId(username) userid, err := d.GetUserId(username)
@ -129,23 +134,28 @@ func (d *Db) AddUserToProject(username string, projectname string, role string)
return err3 return err3
} }
// ChangeUserRole changes the role of a user within a project.
func (d *Db) ChangeUserRole(username string, projectname string, role string) error { func (d *Db) ChangeUserRole(username string, projectname string, role string) error {
// Get the user ID
var userid int var userid int
userid, err := d.GetUserId(username) userid, err := d.GetUserId(username)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// Get the project ID
var projectid int var projectid int
projectid, err2 := d.GetProjectId(projectname) projectid, err2 := d.GetProjectId(projectname)
if err2 != nil { if err2 != nil {
panic(err2) panic(err2)
} }
// Execute the SQL query to change the user's role
_, err3 := d.Exec(changeUserRole, role, userid, projectid) _, err3 := d.Exec(changeUserRole, role, userid, projectid)
return err3 return err3
} }
// GetUserRole retrieves the role of a user within a project.
func (d *Db) GetUserRole(username string, projectname string) (string, error) { func (d *Db) GetUserRole(username string, projectname string) (string, error) {
var role string var role string
err := d.Get(&role, "SELECT p_role FROM user_roles WHERE user_id = (SELECT id FROM users WHERE username = ?) AND project_id = (SELECT id FROM projects WHERE name = ?)", username, projectname) err := d.Get(&role, "SELECT p_role FROM user_roles WHERE user_id = (SELECT id FROM users WHERE username = ?) AND project_id = (SELECT id FROM projects WHERE name = ?)", username, projectname)

View file

@ -112,7 +112,7 @@ func TestAddTimeReport(t *testing.T) {
var now = time.Now() var now = time.Now()
var then = now.Add(time.Hour) var then = now.Add(time.Hour)
err = db.AddTimeReport("testproject", "testuser", now, then) err = db.AddTimeReport("testproject", "testuser", "activity", now, then)
if err != nil { if err != nil {
t.Error("AddTimeReport failed:", err) t.Error("AddTimeReport failed:", err)
} }
@ -137,7 +137,7 @@ func TestAddUserToProject(t *testing.T) {
var now = time.Now() var now = time.Now()
var then = now.Add(time.Hour) var then = now.Add(time.Hour)
err = db.AddTimeReport("testproject", "testuser", now, then) err = db.AddTimeReport("testproject", "testuser", "activity", now, then)
if err != nil { if err != nil {
t.Error("AddTimeReport failed:", err) t.Error("AddTimeReport failed:", err)
} }

View file

@ -2,10 +2,12 @@ CREATE TABLE IF NOT EXISTS time_reports (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
project_id INTEGER NOT NULL, project_id INTEGER NOT NULL,
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
activity_type TEXT NOT NULL,
start DATETIME NOT NULL, start DATETIME NOT NULL,
end DATETIME NOT NULL, end DATETIME NOT NULL,
FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
FOREIGN KEY (activity_type) REFERENCES activity_types (name) ON DELETE CASCADE
); );
CREATE TRIGGER IF NOT EXISTS time_reports_start_before_end CREATE TRIGGER IF NOT EXISTS time_reports_start_before_end

View file

@ -1,7 +1,7 @@
-- It is unclear weather this table will be used -- It is unclear weather this table will be used
-- Create the table to store hash salts -- Create the table to store hash salts
CREATE TABLE salts ( CREATE TABLE IF NOT EXISTS salts (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
salt TEXT NOT NULL salt TEXT NOT NULL
); );

View file

@ -0,0 +1,10 @@
CREATE TABLE IF NOT EXISTS activity_types (
name TEXT PRIMARY KEY
);
INSERT OR IGNORE INTO activity_types (name) VALUES ('Development');
INSERT OR IGNORE INTO activity_types (name) VALUES ('Meeting');
INSERT OR IGNORE INTO activity_types (name) VALUES ('Administration');
INSERT OR IGNORE INTO activity_types (name) VALUES ('Own Work');
INSERT OR IGNORE INTO activity_types (name) VALUES ('Studies');
INSErt OR IGNORE INTO activity_types (name) VALUES ('Testing');

9
backend/tygo.yaml Normal file
View file

@ -0,0 +1,9 @@
packages:
- path: "ttime/internal/types"
output_path: "../frontend/src/Types/goTypes.ts"
type_mappings:
time.Time: "string /* RFC3339 */"
null.String: "null | string"
null.Bool: "null | boolean"
uuid.UUID: "string /* uuid */"
uuid.NullUUID: "null | string /* uuid */"

View file

@ -9,7 +9,7 @@ module.exports = {
'plugin:react-hooks/recommended', 'plugin:react-hooks/recommended',
'plugin:prettier/recommended', 'plugin:prettier/recommended',
], ],
ignorePatterns: ['dist', '.eslintrc.cjs', 'tailwind.config.js', 'postcss.config.js', 'jest.config.cjs'], ignorePatterns: ['dist', '.eslintrc.cjs', 'tailwind.config.js', 'postcss.config.js', 'jest.config.cjs', 'goTypes.ts'],
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
plugins: ['react-refresh', 'prettier'], plugins: ['react-refresh', 'prettier'],
rules: { rules: {

View file

@ -1,57 +1,120 @@
import { NewProject, Project } from "../Types/Project"; import { NewProject, Project } from "../Types/Project";
import { NewUser, User } from "../Types/Users"; import { NewUser, User } from "../Types/Users";
// This type of pattern should be hard to misuse
interface APIResponse<T> {
success: boolean;
message?: string;
data?: T;
}
// Note that all protected routes also require a token
// Defines all the methods that an instance of the API must implement // Defines all the methods that an instance of the API must implement
interface API { interface API {
/** Register a new user */ /** Register a new user */
registerUser(user: NewUser): Promise<User>; registerUser(user: NewUser): Promise<APIResponse<User>>;
/** Remove a user */ /** Remove a user */
removeUser(username: string): Promise<User>; removeUser(username: string, token: string): Promise<APIResponse<User>>;
/** Create a project */ /** Create a project */
createProject(project: NewProject): Promise<Project>; createProject(
project: NewProject,
token: string,
): Promise<APIResponse<Project>>;
/** Renew the token */ /** Renew the token */
renewToken(token: string): Promise<string>; renewToken(token: string): Promise<APIResponse<string>>;
} }
// Export an instance of the API // Export an instance of the API
export const api: API = { export const api: API = {
async registerUser(user: NewUser): Promise<User> { async registerUser(user: NewUser): Promise<APIResponse<User>> {
return fetch("/api/register", { try {
const response = await fetch("/api/register", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify(user), body: JSON.stringify(user),
}).then((res) => res.json() as Promise<User>); });
if (!response.ok) {
return { success: false, message: "Failed to register user" };
} else {
const data = (await response.json()) as User;
return { success: true, data };
}
} catch (e) {
return { success: false, message: "Failed to register user" };
}
}, },
async removeUser(username: string): Promise<User> { async removeUser(
return fetch("/api/userdelete", { username: string,
method: "POST", token: string,
headers: { ): Promise<APIResponse<User>> {
"Content-Type": "application/json", try {
}, const response = await fetch("/api/userdelete", {
body: JSON.stringify(username),
}).then((res) => res.json() as Promise<User>);
},
async createProject(project: NewProject): Promise<Project> {
return fetch("/api/project", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(project),
}).then((res) => res.json() as Promise<Project>);
},
async renewToken(token: string): Promise<string> {
return fetch("/api/loginrenew", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: "Bearer " + token, Authorization: "Bearer " + token,
}, },
}).then((res) => res.json() as Promise<string>); body: JSON.stringify(username),
});
if (!response.ok) {
return { success: false, message: "Failed to remove user" };
} else {
const data = (await response.json()) as User;
return { success: true, data };
}
} catch (e) {
return { success: false, message: "Failed to remove user" };
}
},
async createProject(
project: NewProject,
token: string,
): Promise<APIResponse<Project>> {
try {
const response = await fetch("/api/project", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
body: JSON.stringify(project),
});
if (!response.ok) {
return { success: false, message: "Failed to create project" };
} else {
const data = (await response.json()) as Project;
return { success: true, data };
}
} catch (e) {
return { success: false, message: "Failed to create project" };
}
},
async renewToken(token: string): Promise<APIResponse<string>> {
try {
const response = await fetch("/api/loginrenew", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
});
if (!response.ok) {
return { success: false, message: "Failed to renew token" };
} else {
const data = (await response.json()) as string;
return { success: true, data };
}
} catch (e) {
return { success: false, message: "Failed to renew token" };
}
}, },
}; };

View file

@ -1,14 +1,17 @@
function Button({ function Button({
text, text,
onClick, onClick,
type,
}: { }: {
text: string; text: string;
onClick: () => void; onClick: () => void;
type: "submit" | "button" | "reset";
}): JSX.Element { }): JSX.Element {
return ( return (
<button <button
onClick={onClick} onClick={onClick}
className="inline-block py-1 px-8 font-bold bg-orange-500 text-white border-2 border-black rounded-full cursor-pointer mt-5 mb-5 transition-colors duration-10 hover:bg-orange-600 hover:text-gray-300 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-size: 4vh;" className="inline-block py-1 px-8 font-bold bg-orange-500 text-white border-2 border-black rounded-full cursor-pointer mt-5 mb-5 transition-colors duration-10 hover:bg-orange-600 hover:text-gray-300 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-size: 4vh;"
type={type}
> >
{text} {text}
</button> </button>

View file

@ -1,6 +1,8 @@
import { useState } from "react"; import { useState } from "react";
import { NewUser } from "../Types/Users"; import { NewUser } from "../Types/Users";
import { api } from "../API/API"; import { api } from "../API/API";
import Logo from "../assets/Logo.svg";
import Button from "./Button";
export default function Register(): JSX.Element { export default function Register(): JSX.Element {
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");
@ -12,25 +14,32 @@ export default function Register(): JSX.Element {
}; };
return ( return (
<div> <div className="flex flex-col h-screen w-screen items-center justify-center">
<div className="w-full max-w-xs"> <div className="border-4 border-black bg-white flex flex-col items-center justify-center h-fit w-fit rounded-3xl content-center pl-20 pr-20">
<form <form
className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" className="bg-white rounded px-8 pt-6 pb-8 mb-4 items-center justify-center flex flex-col w-fit h-fit"
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
void handleRegister(); void handleRegister();
}} }}
> >
<h3 className="pb-2">Register new user</h3> <img
src={Logo}
className="logo w-[7vw] mb-10 mt-10"
alt="TTIME Logo"
/>
<h3 className="pb-4 mb-2 text-center font-bold text-[18px]">
Register New User
</h3>
<div className="mb-4"> <div className="mb-4">
<label <label
className="block text-gray-700 text-sm font-bold mb-2" className="block text-gray-700 text-sm font-sans font-bold mb-2"
htmlFor="username" htmlFor="username"
> >
Username Username
</label> </label>
<input <input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" className="appearance-none border-2 border-black rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
id="username" id="username"
type="text" type="text"
placeholder="Username" placeholder="Username"
@ -42,13 +51,13 @@ export default function Register(): JSX.Element {
</div> </div>
<div className="mb-6"> <div className="mb-6">
<label <label
className="block text-gray-700 text-sm font-bold mb-2" className="block text-gray-700 text-sm font-sans font-bold mb-2"
htmlFor="password" htmlFor="password"
> >
Password Password
</label> </label>
<input <input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" className="appearance-none border-2 border-black rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
id="password" id="password"
type="password" type="password"
placeholder="Choose your password" placeholder="Choose your password"
@ -59,12 +68,13 @@ export default function Register(): JSX.Element {
/> />
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<button <Button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" text="Register"
onClick={(): void => {
return;
}}
type="submit" type="submit"
> />
Register
</button>
</div> </div>
</form> </form>
<p className="text-center text-gray-500 text-xs"></p> <p className="text-center text-gray-500 text-xs"></p>

View file

@ -11,12 +11,14 @@ function AdminAddProject(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,12 +11,14 @@ function AdminAddUser(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,12 +11,14 @@ function AdminChangeUsername(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,12 +11,14 @@ function AdminManageProjects(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,12 +11,14 @@ function AdminManageUsers(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,12 +11,14 @@ function AdminProjectAddMember(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,12 +11,14 @@ function AdminProjectChangeUserRole(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,12 +11,14 @@ function AdminProjectManageMembers(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,12 +11,14 @@ function AdminProjectPage(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,6 +11,7 @@ function AdminProjectStatistics(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,12 +11,14 @@ function AdminProjectViewMemberInfo(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,12 +11,14 @@ function AdminViewUserInfo(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -67,6 +67,7 @@ function LoginPage(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</Link> </Link>
<Link to="/register"> <Link to="/register">
@ -75,6 +76,7 @@ function LoginPage(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</Link> </Link>
</div> </div>

View file

@ -11,12 +11,14 @@ function ChangeRole(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,6 +11,7 @@ function PMOtherUsersTR(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,18 +11,21 @@ function PMProjectMembers(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Time / Role" text="Time / Role"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -29,6 +29,7 @@ function PMProjectPage(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -19,6 +19,7 @@ function PMTotalTimeActivity(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,6 +11,7 @@ function PMTotalTimeRole(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,6 +11,7 @@ function PMUnsignedReports(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -19,18 +19,21 @@ function PMViewUnsignedReport(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Save" text="Save"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -17,12 +17,14 @@ function UserEditTimeReportPage(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -18,6 +18,7 @@ function UserNewTimeReportPage(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Link to="/project"> <Link to="/project">
<Button <Button
@ -25,6 +26,7 @@ function UserNewTimeReportPage(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</Link> </Link>
</> </>

View file

@ -27,6 +27,7 @@ function UserProjectPage(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</Link> </Link>
</> </>

View file

@ -11,6 +11,7 @@ function UserViewTimeReportsPage(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );