Skip to content

Commit

Permalink
DDAH View (#581)
Browse files Browse the repository at this point in the history
* Add react-based DDAH acknowledge page
  • Loading branch information
siefkenj authored May 16, 2021
1 parent 16b32d9 commit 8ec6441
Show file tree
Hide file tree
Showing 10 changed files with 522 additions and 5 deletions.
22 changes: 22 additions & 0 deletions backend/app/controllers/public/ddahs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,28 @@ def accept
render_success {}
end

# /public/ddahs/<ddah_id>/details
def details
return unless valid_ddah?(url_token: params[:ddah_id])

render_success(
{
approved_date: @ddah.approved_date,
accepted_date: @ddah.accepted_date,
revised_date: @ddah.revised_date,
emailed_date: @ddah.emailed_date,
position_code: @ddah.assignment.position.position_code,
position_title: @ddah.assignment.position.position_title,
status:
if @ddah.accepted_date.blank?
'unacknowledged'
else
'acknowledged'
end
}
)
end

# /public/ddahs/<ddah_id>/view
def view
return unless valid_ddah?(url_token: params[:ddah_id])
Expand Down
4 changes: 2 additions & 2 deletions backend/app/mailers/ddah_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ def generate_vars(ddah)
@ta_coordinator_email = Rails.application.config.ta_coordinator_email
# TODO: This seems too hard-coded. Is there another way to get the route?
@url =
"#{Rails.application.config.base_url}/public/ddahs/#{
"#{Rails.application.config.base_url}/#/public/ddahs/#{
ddah.url_token
}/view"
}"

@subs = {
ddah: @ddah,
Expand Down
4 changes: 2 additions & 2 deletions backend/app/services/offer_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ def subs
ta_coordinator_email: @offer.ta_coordinator_email,
# TODO: This seems too hard-coded. Is there another way to get the route?
url:
"#{Rails.application.config.base_url}/public/contracts/#{
"#{Rails.application.config.base_url}/#/public/contracts/#{
@offer.url_token
}/view",
}",
nag_count: @offer.nag_count,
status_message: status_message,
changes_summary: changes_from_previous
Expand Down
3 changes: 3 additions & 0 deletions backend/app/views/ddahs/ddah-template.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
text-align: right;
font-weight: normal;
}
.appointment-summary td {
text-align: left;
}
.letter-head p {
margin-bottom: 0.5in;
}
Expand Down
1 change: 1 addition & 0 deletions backend/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ def matches?(request)
end
resources :ddahs, format: nil, only: %i[show] do
get :view, format: false, to: 'ddahs#view'
get :details, format: false, to: 'ddahs#details'
post :accept, format: false, to: 'ddahs#accept'
end
resources :postings, only: %i[show] do
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/api/defs/types/raw-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ export interface RawDdah extends HasId {
duties: RawDuty[];
}

export interface RawDdahDetails {
approved_date: string | null;
accepted_date: string | null;
revised_date: string | null;
emailed_date: string | null;
position_code: string;
position_title: string | null;
status: "acknowledged" | "unacknowledged";
}

export interface RawPosting extends HasId {
name: string;
intro_text: string | null;
Expand Down
9 changes: 8 additions & 1 deletion frontend/src/api/mockAPI/public-routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ export const publicRoutes = {
summary: "View a ddah with an accept dialog",
returns: wrappedPropTypes.any,
}),
"/public/ddahs/:token/details": documentCallback({
func: () => {
throw new Error("Not implemented in Mock API");
},
summary: "Get JSON information about a DDAH",
returns: wrappedPropTypes.any,
}),
"/public/contracts/:token": documentCallback({
func: () => {
throw new Error("Not implemented in Mock API");
Expand Down Expand Up @@ -54,7 +61,7 @@ export const publicRoutes = {
}),
},
post: {
"/public/ddahs/:ddah_id/accept": documentCallback({
"/public/ddahs/:token/accept": documentCallback({
func: () => {
throw new Error("Not implemented in Mock API");
},
Expand Down
196 changes: 196 additions & 0 deletions frontend/src/views/public/ddahs/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import React from "react";
import { Button, Modal, Spinner } from "react-bootstrap";
import { useParams } from "react-router-dom";
import { RawDdahDetails } from "../../../api/defs/types";
import { apiGET, apiPOST } from "../../../libs/api-utils";

import "./view-ddah.css";

function capitalize(text: string) {
return text
.split(/\s+/)
.map((word) => word.charAt(0).toLocaleUpperCase() + word.slice(1))
.join(" ");
}

export function DdahView() {
const params = useParams<{ url_token?: string } | null>();
const url_token = params?.url_token;
const [ddah, setDdah] = React.useState<RawDdahDetails | null>(null);
const [decision, setDecision] = React.useState<"accept" | null>(null);
const [signature, setSignature] = React.useState("");
const [
confirmationDialogVisible,
setConfirmationDialogVisible,
] = React.useState(false);
const [waiting, setWaiting] = React.useState(false);

// If the offer's status has been set to accepted/rejected/withdrawn,
// no further interaction with the offer is permitted.
const frozen = ["acknowledged"].includes(ddah?.status || "");

React.useEffect(() => {
async function fetchOffer() {
try {
const details = await apiGET(
`/public/ddahs/${url_token}/details`,
true
);
setDdah(details);
} catch (e) {
console.warn(e);
}
}
fetchOffer();
}, [setDdah, url_token]);

async function submitDecision() {
if (decision == null) {
throw new Error("Cannot submit a `null` decision");
}
const data = { decision, signature: signature || null };
await apiPOST(`/public/ddahs/${url_token}/${decision}`, data, true);
}
async function confirmClicked() {
setWaiting(true);
await submitDecision();
setWaiting(false);
window.location.reload(true);
}

if (url_token == null) {
return <React.Fragment>Unknown URL token.</React.Fragment>;
}

if (ddah == null) {
return <React.Fragment>Loading...</React.Fragment>;
}

const position_code = ddah.position_code;
const status = ddah.status;

return (
<div className="contract-page">
<div className="header">
<h1>
Description of Duties and Allocation of Hours for{" "}
{position_code}
</h1>
</div>
<div className="content">
<div className="decision">
<h3>
<Button
href={`/public/ddahs/${url_token}.pdf`}
role="button"
>
Download PDF
</Button>
</h3>
<h1>
Status:
<span className={`${status} capitalize`}>
{" "}
{capitalize(status)}
</span>
</h1>
<form id="decision">
<h3>
Please acknowledge receipt of this Description of
Duties and Allocation of Hours form below. If there
are any issues with your described duties or you
need further clarification, please contact your
course supervisor(s).
</h3>
<div className="decision-container">
<input
checked={decision === "accept"}
onChange={() => setDecision("accept")}
type="radio"
value="accept"
id="radio-accept"
name="decision"
disabled={frozen}
/>
<label htmlFor="radio-accept">Acknowledge</label>
<div className="signature">
<div>
<label htmlFor="signature_name">
<p>
To confirm your acknowledgement,
please type your name below.
</p>
</label>
<input
type="text"
name="signature_name"
id="signature_name"
maxLength={300}
value={signature}
onChange={(e) =>
setSignature(e.target.value)
}
/>
<div className="input-placeholder">.</div>
</div>
</div>
<Button
disabled={
decision == null ||
(decision === "accept" && signature === "")
}
title={
decision == null
? "You must choose to accept or reject the contract in order to submit"
: decision === "accept" &&
signature === ""
? "You must sign your name to accept the offer"
: "Submit your decision"
}
onClick={() =>
setConfirmationDialogVisible(true)
}
>
Submit
</Button>
</div>
</form>
<div className="admonishment"></div>
</div>
<div className="contract-view">
<iframe
title="Contract"
src={`/public/ddahs/${url_token}`}
></iframe>
</div>
</div>
<Modal
show={confirmationDialogVisible}
onHide={() => setConfirmationDialogVisible(false)}
>
<Modal.Header closeButton>
<Modal.Title>Acknowledge DDAH</Modal.Title>
</Modal.Header>
<Modal.Body>
Are you sure you want to <b>acknowledge</b> the DDAH?
</Modal.Body>
<Modal.Footer>
<Button
variant="secondary"
onClick={() => setConfirmationDialogVisible(false)}
>
Cancel
</Button>
<Button onClick={confirmClicked}>
{waiting ? (
<span className="spinner-surround">
<Spinner animation="border" size="sm" />
</span>
) : null}
Acknowledge DDAH
</Button>
</Modal.Footer>
</Modal>
</div>
);
}
Loading

0 comments on commit 8ec6441

Please sign in to comment.