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
X
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 < index) targetIndex index - 1;
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 < newIndex) newIndex (0 newIndex - 1);
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(
...meta
coverletterclientcontact:
)
/>
Subject Line
setMeta(
...meta
coverlettersubjectline:
)
/>
Cover Letter Body
setMeta(
...meta
coverletterbody:
)
/>
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:
- Support the development and scoping of the risk assessment plan.
- Identify assess and document applicable security controls.
- Collaborate with stakeholders to collect data and evidence.
- Perform detailed analysis of access control measures.
- Review security documentation and system procedures.
- Prepare draft and final risk assessment reports.
- Collect and review artifacts demonstrating compliance with applicable security controls.
- Incorporate feedback and deliver a final report.
Required Skills & Certifications:
- Bachelors degree in Cybersecurity Information Technology Computer Science or related field.
- Minimum 5 years of experience in cybersecurity risk assessment or compliance projects.
- Strong understanding of NIST 800-53 and SEC530 Information Security Standards.
- Hands-on experience with access control evaluations and documentation of compliance evidence.
- Excellent analytical reporting and communication skills.
Preferred Skills & Certifications:
- Certifications such as CISSP CISA CISM CRISC or Security.
- Experience working with state or federal agencies or similar regulatory environments.
Special Considerations:
- N/A
Scheduling:
- N/A
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 GripVert...
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
X
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 < index) targetIndex index - 1;
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 < newIndex) newIndex (0 newIndex - 1);
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(
...meta
coverletterclientcontact:
)
/>
Subject Line
setMeta(
...meta
coverlettersubjectline:
)
/>
Cover Letter Body
setMeta(
...meta
coverletterbody:
)
/>
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:
- Support the development and scoping of the risk assessment plan.
- Identify assess and document applicable security controls.
- Collaborate with stakeholders to collect data and evidence.
- Perform detailed analysis of access control measures.
- Review security documentation and system procedures.
- Prepare draft and final risk assessment reports.
- Collect and review artifacts demonstrating compliance with applicable security controls.
- Incorporate feedback and deliver a final report.
Required Skills & Certifications:
- Bachelors degree in Cybersecurity Information Technology Computer Science or related field.
- Minimum 5 years of experience in cybersecurity risk assessment or compliance projects.
- Strong understanding of NIST 800-53 and SEC530 Information Security Standards.
- Hands-on experience with access control evaluations and documentation of compliance evidence.
- Excellent analytical reporting and communication skills.
Preferred Skills & Certifications:
- Certifications such as CISSP CISA CISM CRISC or Security.
- Experience working with state or federal agencies or similar regulatory environments.
Special Considerations:
- N/A
Scheduling:
- N/A
View more
View less