Job Summary : use client;
- import React useEffect useMemo useState from react;
- import Card CardHeader CardTitle CardContent from @ / components / ui / card;
- import Button from @ / components / ui / button;
- import Input from @ / components / ui / input;
- import Badge from @ / components / ui / badge;
import
GripVertical
Plus
Download
Trash2
RefreshCw
Edit3
Check
Save
from lucide-react;import Textarea from @ / components / ui / textarea;import saveAs from file-saver;
const APIBASE ;
/ / Types
export type OutlineNode
id : string;title : string;description : string;children : OutlineNode;type BackendSubsection
subname : string;subdescription : string;type BackendSection
sectionname : string;sectiondescription : string;subsections : BackendSubsection;type BackendOutline
opportunityname : string;submittedtoname : string;submittedtoemail : string;submittedtophone : string;submittedbyemail : string;submittedbyaddress : string;coverletterdate : string;coverletterclientcontact : string;coverlettersubjectline : string;coverletterbody : string;proposaltype : string;sections : BackendSection;tableofcontents : string;k : string : any;export type OutlineBuilderProps
proposalId : string;onGenerate : (outlineText : string) >
void;isLoading : boolean;/ / Generic helpers
const cloneSafe (v : T) : T >
try
return ((v)) as T;catch
return v;const findAndRemoveNode (tree : OutlineNode id : string) : OutlineNode null OutlineNode >
let removed : OutlineNode null null;const walk (list : OutlineNode) : OutlineNode >
let changed false;const out : OutlineNode ;for (let i 0; i < ; i)
const n listi;if ( id)
removed n;changed true;continue;if (() && )
const newChildren walk();if (newChildren ! )
changed true;( ...n children : newChildren );else
(n);else
(n);return changed out : list;const newTree walk(tree);return removed newTree;const insertNodeAt (tree : OutlineNode nodeToInsert : OutlineNode parentId : string null index : number) : OutlineNode >
if (parentId null)
const safeIndex (0 ( index));return ...(0 safeIndex) nodeToInsert ...(safeIndex);let inserted false;const walk (list : OutlineNode) : OutlineNode >
let changed false;const out : OutlineNode ;for (let i 0; i < ; i)
const n listi;if ( parentId)
const children () ... : ;const safe (0 ( index));(safe 0 nodeToInsert);inserted true;changed true;( ...n children );continue;if (() && )
const nchild walk();if (nchild ! )
changed true;( ...n children : nchild );else
(n);else
(n);return changed out : list;const newTree walk(tree);return inserted newTree : tree;const findNode (tree : OutlineNode id : string) : OutlineNode null >
const walk (list : OutlineNode) : OutlineNode null >
for (let i 0; i < ; i)
const n listi;if ( id) return n;if (() && )
const f walk();if (f) return f;return null;return walk(tree);const numberOutline (nodes : OutlineNode null prefix : number ) : no : string; title : string >
const out : no : string; title : string ;const arr (nodes) nodes : ;((n idx) >
const no ...prefix idx (.);( no title : );if ()
const sub numberOutline( ...prefix idx 1);(...sub););return out;const outlineToText (nodes : OutlineNode null) : string >
numberOutline(nodes).map((x) >
$ $).join(n);
const OutlineBuilder : ( proposalId onGenerate isLoading ) >
const nodes setNodes useState();const outlineJson setOutlineJson useState(null);const loadingOutline setLoadingOutline useState(true);const saving setSaving useState(false);const deleting setDeleting useState(false);const draggedId setDraggedId useState(null);const dropHint setDropHint useState(null);const selectedTemplate setSelectedTemplate useState();
const meta setMeta useState(
opportunityname : submittedtoname :
submittedtoemail :
submittedtophone :
coverletterdate :
coverletterclientcontact :
coverlettersubjectline :
coverletterbody :
);
const mapBackendToNodes (outline : BackendOutline null) : OutlineNode >
if (!outline) return ;const sections () : ;return ((s idx) >
id : ()
title : Section $idx 1
description : children : ()
((sub j) >
id : ()
title : Subsection $j 1
description : children : ()
))
));const mapNodesToBackendSections (list : OutlineNode) : BackendSection >
return ((list) list : ).map((n) >
sectionname : sectiondescription :
subsections : ()
((c) >
subname : subdescription :
))
));const fetchOutline async () >
if (!proposalId !APIBASE) return;try
setLoadingOutline(true);const res await fetch($APIBASE / api / proposals / $proposalId / outline method : GET );if (!)
const txt await ().catch(() >
);throw new Error(GET failed : $ $txt);const data await ();const outline : BackendOutline data;setOutlineJson(outline);setMeta(
opportunityname : submittedtoname :
submittedtoemail :
submittedtophone :
coverletterdate :
coverletterclientcontact :
coverlettersubjectline :
coverletterbody :
);const mapped mapBackendToNodes(outline);setNodes(mapped);catch (err)
(fetchOutline error : err);alert(Failed to load outline.);finally
setLoadingOutline(false);useEffect(() >
fetchOutline();/ / eslint-disable-next-line react-hooks / exhaustive-deps
proposalId);
const outlineText useMemo(() >
outlineToText(nodes) nodes);
const removeNodeById (id : string) >
const newTree findAndRemoveNode(nodes as any id);setNodes(newTree as any);const updateTitle (id : string text : string) >
const node findNode(nodes as any id);if (node)
text;setNodes(...nodes);const updateDescription (id : string desc : string) >
const node findNode(nodes id);if (node)
const updatedNode ...node description : desc ;const newNodes ((n) >
( id updatedNode : n));setNodes(newNodes);const addChild (parentId : string) >
const newChild : OutlineNode
id : ()
title : New Subsection
description : children : ()
const parentNode findNode(nodes as any parentId);const newIndex 0;const newTree insertNodeAt(nodes as any newChild as any parentId newIndex);if (newTree ! nodes) setNodes(newTree as any);const addRoot () >
setNodes(...nodes id : () title : New Section description : );const onDragStart (e : id : string) >
setDraggedId(id);(text / plain id);const onDragOverGeneric (e : ) >
();const endDragCleanup () >
setDraggedId(null);setDropHint(null);useEffect(() >
const cleanup () >
endDragCleanup();(dragend cleanup);return () >
(dragend cleanup););
const findParentAndIndex (tree : OutlineNode id : string) : parentId : string null; index : number null >
const walk (arr : OutlineNode parentId : string null) : parentId : string null; index : number null >
for (let i 0; i < ; i)
const n arri;if ( id) return parentId index : i ;if ()
const found walk( );if (found) return found;return null;return walk(tree null);const handleDragLeaveRoot (e : ) >
const to as HTMLElement null;if (!to !(.outline-left-column))
setDropHint(null);const onDropBetween (e : parentId : string null index : number) >
();let dragged (text / plain);if (!dragged && draggedId) dragged draggedId;if (!dragged) return endDragCleanup();
const origin findParentAndIndex(nodes dragged);const originParent null;const originIndex -1;let targetIndex index;if (originParent parentId && originIndex
if (originParent parentId && targetIndex originIndex) return endDragCleanup();
const removed afterRemove findAndRemoveNode(nodes as any dragged);if (!removed) return endDragCleanup();
const newTree insertNodeAt(afterRemove as any removed as any parentId targetIndex);if (newTree ! nodes) setNodes(newTree as any);endDragCleanup();const onDropInto (e : parentId : string null) >
();let dragged (text / plain);if (!dragged && draggedId) dragged draggedId;if (!dragged) return endDragCleanup();
if (parentId && isDescendant(nodes dragged parentId)) return endDragCleanup();
const origin findParentAndIndex(nodes dragged);const originParent null;const originIndex -1;const removed afterRemove findAndRemoveNode(nodes as any dragged);if (!removed) return endDragCleanup();
const parentNode parentId findNode(afterRemove as any parentId) : null;let newIndex ;if (originParent parentId && originIndex
const newTree insertNodeAt(afterRemove as any removed as any parentId newIndex);if (newTree ! nodes) setNodes(newTree as any);endDragCleanup();const TitleInline : ( id value ) >
const editing setEditing useState(false);const val setVal useState(value);
useEffect(() >
setVal(value) value);
return (
editing (
setVal() / >
updateTitle(id val value);setEditing(false);setVal(value);setEditing(false);) : (
value
setEditing(true)>
);return (
Proposal Outline
Type : $ : Reload
saving (
Saving
) : (
Save Outline
onGenerate(outlineText) disabled!>
Generate Draft
downloadDocx(selectedTemplate) disabled!>
Download DOCX
Delete Outline
0 (
) : (
No sections found. Add or reload.
Add Top-Level Section
Cover & Metadata
Opportunity Name
setMeta( ...meta opportunityname : )
/ >
Submitted To (Name)
setMeta( ...meta submittedtoname : )
/ >
Email
setMeta( ...meta submittedtoemail : )
/ >
Phone
setMeta( ...meta submittedtophone : )
/ >
Cover Letter Date
setMeta( ...meta coverletterdate : )
/ >
Client Contact
setMeta(
...metacoverletterclientcontact :
/ >
Subject Line
setMeta(
...metacoverlettersubjectline :
/ >
Cover Letter Body
setMeta(
...metacoverletterbody : );
Template
Choose Template
setSelectedTemplate()
Tip : Edit outline on left. Edit cover details here. Save to Firestore or Export DOCX anytime.
);export default OutlineBuilder;Location : Richmond Virginia United States
Responsibilities :
Develop and maintain a detailed project plan schedule and risk register for assessment activities.Coordinate efforts between teams and other stakeholders.Define project scope milestones deliverables and performance metrics.Ensure all assessment activities align with SEC530-01.2 and RA-3 risk assessment requirements.Oversee evaluation of Access Control family controls across designated applications.Validate that project deliverables meet cybersecurity standards.Facilitate project meetings with leadership.Prepare progress summaries status dashboards and risk issue logs.Serve as primary point of contact between teams.Oversee development and delivery of draft and final risk assessment reports.Review findings track remediation actions and ensure timely completion of corrective measures.Support de-briefing and final presentation of assessment outcomes to leadership.Identify and monitor project risks including schedule resource and compliance-related issues.Escalate unresolved risks or control deficiencies to leadership for timely mitigation.Ensure proper documentation of all issues lessons learned and risk mitigations for audit traceability.Required Skills & Certifications :
8 years of experience in project management with at least 3 years in information security or risk management.Proven track record managing medium to large-scale cybersecurity or audit projects in regulated environments.Experience coordinating risk assessments compliance audits or security control reviews under NIST 800-53 ISO 27001 or SEC530 frameworks.Excellent organizational communication and stakeholder engagement skills.CISSP Certified Information Systems Security Professional ORCISM Certified Information Security ManagerPMP Project Management ProfessionalPreferred Skills & Certifications :
None specified.Special Considerations :
None specified.Scheduling :
Not specified.Key Skills
Project Management Methodology,Project / Program Management,Construction Estimating,Construction Experience,PMBOK,Visio,Construction Management,Project Management,Project Management Software,Microsoft Project,Project Management Lifecycle,Contracts
Employment Type : Full Time
Experience : years
Vacancy : 1