Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

158 show edit projects #196

Merged
merged 9 commits into from
Jan 7, 2025
Merged
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
6 changes: 3 additions & 3 deletions frontend/src/admin/delete-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ import {Button} from "primereact/button";
import {FunctionComponent, useState} from "react";
import {noop} from "lodash";

export const DeleteButton: FunctionComponent<{surveyId: any, onDelete?: (surveyId: any) => void}> = ({surveyId, onDelete = noop}) => {
export const DeleteButton: FunctionComponent<{type: string, id: any, onDelete?: (type: string , id: any) => void}> = ({type, id, onDelete = noop}) => {
const [pending, setPending] = useState(false)
const deleteSurvey = async () => {
setPending(true)
try {
if (!confirm('Uitvraag verwijderen?')) {
return
}
await fetch(`${import.meta.env.VITE_ZTOR_URL}/company-surveys/${surveyId}`, {
await fetch(`${import.meta.env.VITE_ZTOR_URL}/${type}/${id}`, {
method: 'DELETE',
credentials: 'include',
})
onDelete(surveyId)
onDelete(id)
} catch (error) {
alert((error as Error).message)
} finally {
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/admin/edit-button.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {Link} from "react-router-dom";

export const EditButton = ({surveyId}: {surveyId: string}) => (
<Link className="p-button p-button-success" to={`/bedrijven-uitvraag/${surveyId}`} css={{
export const EditButton = ({type, id}: {type: string, id: string}) => (
<Link className="p-button p-button-success" to={`/${type}/${id}`} css={{
textDecoration: 'none',
whiteSpace: 'nowrap',
}}>
<i className="pi pi-pencil"></i>
<i className="pi pi-eye"></i>
</Link>
)
137 changes: 137 additions & 0 deletions frontend/src/admin/project-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import React, { FormEvent, FunctionComponent, useEffect, useState } from "react";
import { useParams, useNavigate } from "react-router-dom";
import { PrimeReactProvider } from "primereact/api";
import { InputText } from "primereact/inputtext";
import { Button } from "primereact/button";
import { Project } from "zero-zummon";
import { redirectToLogin } from "./use-projects";

export const ProjectForm: FunctionComponent = () => {
const {projectId} = useParams<{ projectId: string }>();
const [project, setProject] = useState<Project | null>(null);
const [originalData, setOriginalData] = useState<Project | null>(null);

const [loading, setLoading] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const navigate = useNavigate();

const handleCancel = () => {
if (originalData) {
setProject(originalData); // Revert to original data
}
setIsEditing(false);
};

const handleEditToggle = () => {
setIsEditing(true);
};

const handleInputChange =(e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setProject((prev) => ({ ...prev, [name]: value } as Project));
};

useEffect(() => {
if (projectId) {
const fetchProject = async () => {
setLoading(true);
try {
const response = await fetch(`${import.meta.env.VITE_ZTOR_URL}/projects/${projectId}`, {
credentials: "include",
});
if (response.status === 401) {
redirectToLogin();
return;
}
if (response.ok) {
const projectData = await response.json();
setProject(projectData);
setOriginalData(projectData);
} else {
alert(`Error fetching project: ${response.statusText}`);
}
} catch (error) {
alert((error as Error).message);
} finally {
setLoading(false);
}
};
fetchProject();
} else {
setIsEditing(true);
}
}, [projectId]);
macano marked this conversation as resolved.
Show resolved Hide resolved

const handleSubmit = async (event: FormEvent) => {
event.preventDefault();
setLoading(true);
try {
const method = projectId ? "PUT" : "POST";
const url = projectId
? `${import.meta.env.VITE_ZTOR_URL}/projects/${projectId}`
: `${import.meta.env.VITE_ZTOR_URL}/projects`;
const response = await fetch(url, {
method,
headers: {
"Content-Type": "application/json",
},
credentials: "include",
body: JSON.stringify(project),
});
if (response.status === 401) {
redirectToLogin();
return;
}

if (response.ok) {
navigate(`/projects`);
} else {
const errorData = await response.json();
alert(`Error: ${errorData.message}`);
}
} finally {
setIsEditing(false);
setLoading(false);
}
};

return (
<PrimeReactProvider>
<div style={{ padding: "20px", maxWidth: "500px", margin: "0 auto" }}>
<h3>{projectId ? "Edit Project" : "Add Project"}</h3>
<form
onSubmit={handleSubmit}
style={{ display: "flex", flexDirection: "column", gap: "10px" }}
>
<label htmlFor="name">Name:</label>
<InputText
id="name"
name="name"
value={project?.name || ""}
onChange={handleInputChange}
disabled={!isEditing}
/>
<label htmlFor="energiekeRegioId">Energieke Regio ID:</label>
<InputText
id="energiekeRegioId"
name="energiekeRegioId"
value={project?.energiekeRegioId?.toString() || ""}
onChange={handleInputChange}
disabled={!isEditing}
/>

<div style={{ display: "flex", justifyContent: "space-between", marginTop: "10px" }}>
{isEditing ? (
<>
<Button label="Cancel" onClick={handleCancel} type="button" disabled={loading} />
<Button label={loading ? "Saving..." : "Save"} type="submit" disabled={loading} />
</>
) : (
<Button label="Edit" onClick={handleEditToggle} type="button" disabled={loading} />
)}
</div>
</form>
</div>
</PrimeReactProvider>
);
};
59 changes: 59 additions & 0 deletions frontend/src/admin/projects.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React, {FunctionComponent} from "react";
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import {useProjects} from "./use-projects";
import {PrimeReactProvider} from "primereact/api";
import {Project} from "zero-zummon"

import "primereact/resources/themes/lara-light-cyan/theme.css"
import 'primeicons/primeicons.css'
import {DeleteButton} from "./delete-button";
import {EditButton} from "./edit-button";
import {Button} from "primereact/button";
import {useNavigate} from "react-router-dom"

export const Projects: FunctionComponent = () => {
const {loadingProjects, projects, changeProject, removeProject} = useProjects()
const navigate = useNavigate();

return (
<PrimeReactProvider>
<div css={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '1em 1em',
boxShadow: '1px solid #ddd'
}}>
<h3>Projects List</h3>
<Button
label="Nieuw"
icon="pi pi-pencil"
onClick={(event) => navigate(`/projects/new-project`)}
/>
</div>
<DataTable
value={projects}
loading={loadingProjects}
sortField="created"
sortOrder={-1}
filterDisplay="row"
>
<Column field="name" header="Name" sortable filter/>
<Column field="energiekeRegioId" header="Energie Regio Id" sortable filter/>

<Column body={(project: Project) => (
<div css={{
display: 'flex',
'> *': {
margin: `${1 / 6}rem`
},
}}>
<DeleteButton type="projects" id={project.id} onDelete={removeProject}/>
<EditButton type="projects" id={project.id}/>
</div>
)}/>
</DataTable>
</PrimeReactProvider>
)
}
6 changes: 3 additions & 3 deletions frontend/src/admin/surveys.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {FunctionComponent} from "react";
import React, {FunctionComponent} from "react";
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import {useSurveys} from "./use-surveys";
Expand Down Expand Up @@ -70,8 +70,8 @@ export const Surveys: FunctionComponent = () => {
},
}}>
<JsonButton surveyId={survey.id}/>
<DeleteButton surveyId={survey.id} onDelete={removeSurvey}/>
<EditButton surveyId={survey.id}/>
<DeleteButton type="company-surveys" id={survey.id} onDelete={removeSurvey}/>
<EditButton type="company-surveys" id={survey.id}/>
<DeeplinkButton surveyId={survey.id}/>
</div>
)}/>
Expand Down
61 changes: 61 additions & 0 deletions frontend/src/admin/use-projects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {useState} from "react";
import {useOnce} from "../hooks/use-once";
import {Project, projectsFromJson } from "zero-zummon"
import {useNavigate} from "react-router-dom";

type UseProjectReturn = {
loadingProjects: boolean,
projects: Project[],
changeProject: (newProject: Project) => void,
removeProject: (projectId: string) => void,
}

type UseProjectData = {
loadingProject: boolean,
project: Project,
}

export const useProjects = (): UseProjectReturn => {
const [loadingProjects, setLoading] = useState(true)
const [projects, setProjects] = useState<Project[]>([])

const changeProject = (newProject: Project) => {
setProjects(projects.map(project => project.id.toString() === newProject.id.toString() ? newProject : project))
}

useOnce(async () => {
try {
const response = await fetch(import.meta.env.VITE_ZTOR_URL + '/projects', {
credentials: 'include',
})
if (response.status === 401) {
redirectToLogin()
return
}
if (response.status === 500) {
return
}

setProjects(projectsFromJson(await response.text()))
} catch (error) {
alert((error as Error).message)
} finally {
setLoading(false)
}
})

const removeProject = (projectId: any) => {
setProjects(projects.filter(project => project.id.toString() !== projectId.toString()))
}

return {
loadingProjects,
projects,
changeProject,
removeProject,
}
}

export const redirectToLogin = () => {
window.location.href = import.meta.env.VITE_ZTOR_URL + '/login?redirectUrl=' + encodeURIComponent(window.location.href)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {fetchBuurtcodesByProject} from "../../panden-select/fetch-buurtcodes"
import {ztorFetch} from "../../services/ztor-fetch";
import {Project} from "zero-zummon"

export type FrontendProjectConfiguration = {
name: ProjectName,
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {ThankYou} from './components/thank-you'
import {LoginWidget} from "./user/login";
import {BedrijvenFormV1} from "./components/bedrijven-form-v1";
import {Surveys} from "./admin/surveys";
import {Projects} from "./admin/projects";
import {ProjectForm} from "./admin/project-form";
import {fetchSurveyById, SurveyById, SurveyByIdLoaderData} from "./components/company-survey-v2/survey-by-id"
import {Intro} from "./components/intro"
import {ExcelImport} from "./excel-import/excel-import"
Expand All @@ -21,6 +23,9 @@ export const router = createBrowserRouter([
children: [
{path: "", element: <Intro />},
{path: "/surveys", element: <Surveys />},
{path: "/projects", element: <Projects />},
{path: "/projects/new-project", element: <ProjectForm />},
{path: "/projects/:projectId", element: <ProjectForm />},
{path: "/simulation", element: <Simulation />},
],
},
Expand Down
Loading
Loading