<!doctype html>
<html>
<head>
<meta http-equiv="Permissions-Policy" content="clipboard-read=(self), clipboard-write=(self)">
<link rel="stylesheet" href="lib/style.css">
</head>
<body>
<main>
<h1>
My Projects
<span class="project-count"></span>
</h1>
<section class="view-projects">
<div class="toolbar">
<div class="toolbar-left">
<div class="project-search">
<div class="project-search-icon" id="project-search-icon">
<span class="icon icon-search"></span>
</div>
<button class="btn btn-icon hidden" id="project-search-reset">
<span class="icon icon-close">Reset</span>
</button>
<input type="input" class="project-search-input" id="project-search" placeholder="Search projects">
</div>
</div>
<div class="toolbar-center">
<button id="btn-add-project" class="btn btn-icon" title="Add">
<span class="icon icon-add">Add</span>
</button>
<button id="btn-reload-projects" class="btn btn-icon" title="Reload">
<span class="icon icon-refresh">Reload</span>
</button>
<button id="btn-import-projects" class="btn btn-icon" title="Import JSON">
<span class="icon icon-import"></span>Import
<input type="file" id="import-projects" hidden/>
</button>
<button id="btn-import-projects-csv" class="btn btn-icon" title="Import CSV">
<span class="icon icon-csv"></span>Import CSV
<input type="file" id="import-projects-csv" hidden/>
</button>
<a href="javascript:void(0)" id="btn-export-projects" class="btn btn-icon" title="Download">
<span class="icon icon-export"></span>Download
</a>
<button id="btn-import-projects" class="btn btn-icon" title="Import PMs">
<span class="icon icon-pms"></span>Import PMs
<input type="file" id="import-pms" hidden/>
</button>
</div>
<div class="toolbar-right">
<button id="test" class="btn btn-text" onclick="console.log([projects])">Project Dump</button>
</div>
</div>
<div class="view-projects-table-container">
<table class="view-projects-table">
<thead>
<tr>
<th class="name">Name</th>
<th>Description</th>
<th>Size</th>
<th class="date">Design Due</th>
<th class="date">Design Started</th>
<th class="date">Spec Due</th>
<th>Worked</th>
<th>Available</th>
<th>Status</th>
<th>PM</th>
<th class="url">Story</th>
<th class="url">RM</th>
<th class="url">Confluence</th>
<th class="url">Figma</th>
<th class="url">Figjam</th>
<tr>
</thead>
<tbody class="view-projects-table-body">
</tbody>
</table>
</div>
<div class="output-projects-csv"></div>
</section>
<!-- End Display Projects in table -->
<div class="modal add-project">
<div class="modal-header">
<h2>Add Project</h2>
<button class="btn btn-icon btn-close-add-project">
<span class="icon icon-close">
Close
</span>
</button>
</div>
<div class="modal-body add-project-form">
<form>
<div class="form-row">
<label for="add-project-id">
Project ID
</label>
<input type="text" id="add-project-id" readonly>
</div>
<div class="form-row">
<label for="add-project-name">
Project Name
</label>
<input type="text" id="add-project-name">
</div>
<div class="form-row">
<label for="add-project-desc">
Description
</label>
<input type="text" id="add-project-desc">
</div>
<div class="form-row">
<label for="add-project-size">
Size
</label>
<select id="add-project-size">
<option value="">--Select--</option>
<option value="S">S - Small</option>
<option value="M">M - Medium</option>
<option value="L">L - Large</option>
<option value="XL">XL - Extra Large</option>
</select>
</div>
<div class="form-row">
<label for="add-project-design-due">
Design Due
</label>
<input type="date" id="add-project-design-due">
</div>
<div class="form-row">
<label for="add-project-design-started">
Design Started
</label>
<input type="date" id="add-project-design-started">
</div>
<div class="form-row">
<label for="add-project-spec-due">
Spec Due
</label>
<input type="date" id="add-project-spec-due">
</div>
<div class="form-row">
<label for="add-project-worked-hours">
Worked Hours
</label>
<input type="number" id="add-project-worked-hours">
</div>
<div class="form-row">
<label for="add-project-available-hours">
Available Hours
</label>
<input type="number" id="add-project-available-hours">
</div>
<div class="form-row">
<label for="add-project-status">
Status
</label>
<select id="add-project-status">
<option value="">--Select--</option>
<option value="Open">Open</option>
<option value="In Progress">In Progress</option>
<option value="Product Review">Product Review</option>
<option value="Ready for Clarity">Ready for Clarity</option>
<option value="Product Acceptance">Product Acceptance</option>
<option value="On Hold">On Hold</option>
<option value="Closed">Closed</option>
</select>
</div>
<div class="form-row">
<label for="add-project-pm">
PM
</label>
<select id="add-project-pm">
</select>
</div>
<div class="form-row">
<label for="add-project-url">
Design Story
</label>
<input type="text" id="add-project-url">
</div>
<div class="form-row">
<label for="add-project-rm">
RM
</label>
<input type="text" id="add-project-rm">
</div>
<div class="form-row">
<label for="add-project-confluence">
Confluence
</label>
<input type="text" id="add-project-confluence">
</div>
<div class="form-row">
<label for="add-project-figma">
Figma
</label>
<input type="text" id="add-project-figma">
</div>
<div class="form-row">
<label for="add-project-figjam">
FigJam
</label>
<input type="text" id="add-project-figjam">
</div>
<div class="form-row">
<label for="add-project-notes">
Notes
</label>
<textarea type="text" id="add-project-notes"></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<div class="modal-footer-left">
</div>
<div class="modal-footer-right">
<button class="btn btn-outline btn-close-add-project">Cancel</button>
<button class="btn btn-primary" id="add-project-btn-save">Save</button>
</div>
</div>
</div>
<!-- End Add Project -->
<div class="modal edit-project">
<div class="modal-header">
<h2>Edit Project</h2>
<button class="btn btn-icon btn-close-edit-project">
<span class="icon icon-close">
Close
</span>
</button>
</div>
<div class="modal-body edit-project-form">
<form>
<div class="form-row">
<label for="edit-project-name">
Project ID
</label>
<input type="text" id="edit-project-id" readonly>
</div>
<div class="form-row">
<label for="edit-project-name">
Project Name
</label>
<input type="text" id="edit-project-name">
</div>
<div class="form-row">
<label for="edit-project-desc">
Description
</label>
<input type="text" id="edit-project-desc">
</div>
<div class="form-row">
<label for="edit-project-size">
Size
</label>
<select id="edit-project-size">
<option value="">--Select--</option>
<option value="S">S - Small</option>
<option value="M">M - Medium</option>
<option value="L">L - Large</option>
<option value="XL">XL - Extra Large</option>
</select>
</div>
<div class="form-row">
<label for="edit-project-design-due">
Design Due
</label>
<input type="date" id="edit-project-design-due" class="input-date">
</div>
<div class="form-row">
<label for="edit-project-design-started">
Design Started
</label>
<input type="date" id="edit-project-design-started" class="input-date">
</div>
<div class="form-row">
<label for="edit-project-spec-due">
Spec Due
</label>
<input type="date" id="edit-project-spec-due" class="input-date">
</div>
<div class="form-row">
<label for="edit-project-worked-hours">
Worked Hours
</label>
<input type="number" id="edit-project-worked-hours" class="input-hours">
</div>
<div class="form-row">
<label for="edit-project-available-hours">
Available Hours
</label>
<input type="number" id="edit-project-available-hours" class="input-hours">
</div>
<div class="form-row">
<label for="edit-project-status">
Status
</label>
<select id="edit-project-status">
<option value="">--Select--</option>
<option value="Open">Open</option>
<option value="In Progress">In Progress</option>
<option value="Product Review">Product Review</option>
<option value="Ready for Clarity">Ready for Clarity</option>
<option value="Product Acceptance">Product Acceptance</option>
<option value="On Hold">On Hold</option>
<option value="Closed">Closed</option>
</select>
</div>
<div class="form-row">
<label for="edit-project-pm">
PM
</label>
<select id="edit-project-pm">
</select>
</div>
<div class="form-row">
<label for="edit-project-url">
Design Story
</label>
<input type="text" id="edit-project-url">
</div>
<div class="form-row">
<label for="edit-project-rm">
RM
</label>
<input type="text" id="edit-project-rm">
</div>
<div class="form-row">
<label for="edit-project-confluence">
Confluence
</label>
<input type="text" id="edit-project-confluence">
</div>
<div class="form-row">
<label for="edit-project-figma">
Figma
</label>
<input type="text" id="edit-project-figma">
</div>
<div class="form-row">
<label for="edit-project-figjam">
FigJam
</label>
<input type="text" id="edit-project-figjam">
</div>
<div class="form-row">
<label for="edit-project-notes">
Notes
</label>
<textarea type="text" id="edit-project-notes"></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<div class="modal-footer-left">
<button class="btn btn-text" id="edit-project-btn-delete">Delete</button>
</div>
<div class="modal-footer-right">
<button class="btn btn-outline btn-close-edit-project">Cancel</button>
<button class="btn btn-primary" id="edit-project-btn-save">Save</button>
</div>
</div>
</div>
</main>
<!-- End Edit Project -->
<div class="scrim"></div>
<div class="snackbar hidden">
Test messages
<div>
<script src="lib/script.js"></script>
</body>
</html>
/* High Charts Colors */
:root {
--bright-pink-crayola: #ef476fff;
--sunglow: #ffd166ff;
--emerald: #06d6a0ff;
--blue-ncs: #118ab2ff;
--midnight-green: #073b4cff;
--color-gray-10: #f8f9faff;
--color-gray-20: #e9ecefff;
--color-gray-30: #dee2e6ff;
--color-gray-40: #ced4daff;
--color-gray-50: #adb5bdff;
--color-gray-60: #6c757dff;
--color-gray-70: #495057ff;
--color-gray-80: #343a40ff;
--color-gray-90: #212529ff;
--color-light-teal: #91e8e1;
--color-light-turquoise: #2ee0ca;
--color-turquoise: #00e272;
--color-light-blue: #2caffe;
--color-purple-gray: #6b8abc;
--color-violet: #d568fb;
--color-yellow: #feb56a;
--color-orange: #fe6a35;
--color-crimson: #fa4b42;
--color-dark-purple: #38365a;
--color-text-primary: var(--color-gray-80);
--color-text-secondary: var(--color-gray-60);
--color-text-disabled: var(--color-gray-50);
--color-link: var(--blue-ncs);
--color-bg: var(--blue-ncs);
--color-bg-hover: var(--midnight-green);
--color-hover-1: var(--color-gray-10);
--color-hover-2: var(--color-gray-20);
--color-border: var(--color-gray-40);
--color-base: #fff;
--color-inverse: #000;
--border-radius-1: 0.25rem;
--border-radius-2: 0.5rem;
--border-radius-circle: 50%;
--elevation-table-sticky: 2;
--elevation-toolbar-sticky: 4;
--elevation-scrim: 6;
--elevation-modal: 8;
--elevation-snackbar: 10;
--font-size-button: 0.95rem;
--font-size-input: 1.1rem;
--size-icon: 1.5rem;
}
body {
display: block;
width: 100vw;
height: 100vh;
overflow: hidden;
font-family: sans-serif;
color: var(--color-text-primary);
}
main {
display: block;
width: 100%;
height: 100%;
overflow-y: auto;
}
h1,h2,h3,h4,h5 {
margin: 0;
}
a {
text-decoration: none;
color: var(--color-link);
}
a:hover {
text-decoration: underline;
}
label {
color: var(--color-text-secondary);
}
table {
padding: 0;
border-collapse: collapse;
border-bottom: 1px solid var(--color-gray-20);
margin-bottom: 2rem;
}
th,td {
padding: 8px 12px;
}
th {
text-align: left;
white-space: nowrap;
font-size: 0.8rem;
}
td {
border-top: 1px solid var(--color-gray-20);
color: var(--color-text-secondary);
}
th.date,
td.date {
text-align: right;
}
tbody tr:hover td,
tbody tr:hover th {
background-color: var(--color-hover-1);
}
tr.closed td {
color: var(--color-text-disabled);
}
form {
width: 100%;
}
input,
select,
textarea {
padding: 0.5rem;
border: 1px solid var(--color-border);
border-radius: var(--border-radius-2);
font-family: inherit;
font-size: var(--font-size-input);
color: var(--color-text-primary);
}
input[type="number"],
input[type="date"] {
width: 10rem;
}
input[readonly] {
border-color: var(--color-gray-10);
background-color: var(--color-gray-10);
color: var(--color-gray-60);
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--color-bg);
cursor: pointer;
font-size: var(--font-size-button);
text-decoration: none;
transition: background-color 0.2s ease-in,
color 0.2s ease-in;
}
.btn:hover {
background-color: var(--color-bg);
text-decoration: none;
color: var(--color-base);
}
.btn-primary,
.btn-outline {
padding: 0.75rem 1.5rem;
border: 1px solid var(--color-bg);
border-radius: var(--border-radius-1);
font-size: var(--font-size-button);
}
.btn-primary {
background-color: var(--color-bg);
color: var(--color-base);
}
.btn-outline {
background-color: transparent;
color: var(--color-bg);
}
.btn-primary:hover {
border-color: var(--color-bg-hover);
background-color: var(--color-bg-hover);
}
.btn-text {
padding: 0;
background-color: transparent;
border: none;
}
.btn-text:hover {
border-color: transparent;
background-color: transparent;
text-decoration: underline;
color: var(--color-bg);
}
.btn-icon {
position: relative;
width: 2.5rem;
height: 2.5rem;
padding: 0;
border: 0;
border-radius: var(--border-radius-circle);
background-color: transparent;
background-repeat: no-repeat;
background-position: center center;
background-size: 1.5rem;
text-indent: -9999px;
}
.btn-icon:hover {
border-radius: var(--border-radius-circle);
background-color: var(--color-hover-1);
}
.btn-icon:active {
background-color: var(--color-gray-30);
}
.icon {
display: inline-flex;
width: var(--size-icon);
height: var(--size-icon);
background-color: transparent;
background-repeat: no-repeat;
background-position: center center;
background-size: var(--size-icon);
text-indent: -9999px;
}
.icon-add {
background-image: url(../img/add.svg);
}
.icon-close {
background-image: url(../img/close.svg);
}
.icon-pms {
background-image: url(../img/contacts.svg);
}
.icon-copy {
background-image: url(../img/copy.svg);
}
.icon-csv {
background-image: url(../img/csv.svg);
}
.icon-delete {
background-image: url(../img/delete.svg);
}
.icon-export {
background-image: url(../img/download.svg);
}
.icon-refresh {
background-image: url(../img/refresh.svg);
}
.icon-search {
background-image: url(../img/search.svg);
}
.icon-import {
background-image: url(../img/upload.svg);
}
.toolbar {
display: flex;
align-items: center;
justify-content: space-between;
position: sticky;
left: 0;
top: 0;
z-index: var(--elevation-toolbar-sticky);
width: calc(100% - 0.5rem);
margin: 1rem 0;
padding: 0.25rem;
background-color: var(--color-gray-10);
}
.toolbar .btn-icon:hover {
background-color: var(--color-hover-2);
}
.toolbar .btn-icon:active {
background-color: var(--color-gray-30);
}
.toolbar-left,
.toolbar-right,
.toolbar-center {
display: flex;
align-items: center;
flex-grow: 1;
}
.toolbar-right {
justify-content: flex-end;
}
.project-search {
display: flex;
align-items: center;
border: 1px solid var(--color-border);
border-radius: var(--border-radius-2);
background-color: var(--color-base);
}
.project-search:active {
border-color: var(--color-light-blue);
}
.project-search .project-search-input {
border: none;
width: 30rem;
outline: none;
}
.project-search .project-search-input::placeholder {
color: var(--color-text-disabled);
}
.project-search .project-search-icon {
display: flex;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
}
.scrim {
display: none;
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: var(--elevation-scrim);
background-color: rgba(0,0,0,.5);
}
.scrim.show {
display: block;
}
.modal {
position: absolute;
left: 0;
top: 0;
z-index: var(--elevation-modal);
width: calc(100vw - 8rem);
max-height: calc(100vh - 8rem);
display: none;
flex-direction: column;
justify-content: space-between;
gap: 0;
margin: 4rem;
border: 1px solid var(--color-border);
background-color: var(--color-base);
border-radius: var(--border-radius-2);
}
.modal.show {
display: flex;
}
.modal-header,
.modal-body,
.modal-footer {
display: flex;
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 4rem;
padding: 1rem;
border-bottom: 1px solid var(--color-gray-20);
border-top-left-radius: var(--border-radius-2);
border-top-right-radius: var(--border-radius-2);
}
.modal-header h1 {
display: flex;
align-items: center;
line-height: 1;
}
.modal-body {
display: flex;
flex-direction: column;
flex-grow: 1;
max-height: calc(95vh - 8rem);
overflow: auto;
padding: 1rem;
}
.modal-footer {
height: 4rem;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 1rem;
border-top: 1px solid var(--color-gray-20);
border-bottom-left-radius: var(--border-radius-2);
border-bottom-right-radius: var(--border-radius-2);
}
.modal-footer-left,
.modal-footer-right {
display: flex;
align-items: center;
gap: 1rem;
}
.modal-footer-right {
justify-content: flex-end;
}
.form-row {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.5rem 0;
}
.form-row:hover {
background-color: var(--color-hover);
}
.form-row label {
display: block;
width: 10rem;
}
.form-row input,
.form-row select,
.form-row textarea {
flex-grow: 1;
}
.form-row input[type="number"],
.form-row input[type="date"] {
flex-grow: 0;
}
#add-project-size,
#add-project-status {
width: 14rem;
flex-grow: 0;
}
.edit-projects {
display: none;
}
.csv-output {
padding: 4px;
}
.csv-output td {
background-color: #f0f0f0;
}
.add-project {
display: none;
}
.boxes {
display: flex;
margin: 2rem 0;
gap: 1rem;
flex-wrap: wrap;
}
.box {
width: 4rem;
height: 4rem;
display: flex;
}
h1 {
display: flex;
align-items: center;
gap: 0.5rem;
}
.project-count {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.25rem;
border-radius: var(--border-radius-1);
background-color: var(--color-gray-30);
font-size: 0.85rem;
}
.view-projects-table-container {
width: 100%;
overflow-x: auto;
}
.view-projects-table {
position: relative;
width: 100%;
}
.view-projects-table thead th.name,
.view-projects-table tbody th {
position: sticky;
z-index: var(--elevation-table-sticky);
left: 0;
background-color: var(--color-base);
box-shadow: 1px 0 0 0 rgba(0,0,0,.2);
}
.view-projects-table tbody th,
.view-projects-table tbody td {
font-size: 1rem;
font-weight: 500;
border-top: 1px solid var(--color-gray-20);
}
.view-projects-table-body .link {
display: flex;
align-items: center;
line-height: 1;
white-space: nowrap;
}
.view-projects-table-body .empty {
color: var(--color-gray-30);
}
.view-projects-table-body .status,
.view-projects-table-body .pm {
white-space: nowrap;
}
.view-projects-table-body .link .btn-icon{
visibility: hidden;
}
.view-projects-table-body .link:hover .btn-icon{
visibility: visible;
}
.snackbar {
position: absolute;
top: 0.5rem;
right: 0.5rem;
z-index: var(--elevation-snackbar);
display: flex;
align-items: center;
justify-content: flex-end;
margin-right: 0.5rem;
margin-top: 0.5rem;
padding: 0.5rem 0.75rem;
border-radius: var(--border-radius-1);
background-color: var(--color-gray-60);
color: var(--color-base);
opacity: 1;
transition: 0.5s opacity;
}
.snackbar.hidden {
display: none;
}
.snackbar.fade-out {
opacity: 0;
}
.hidden {
display: none !important;
}
let projects = [];
let projectsCsv = [];
let projectCount = 0;
const pms = localStorage.pms ? JSON.parse(localStorage.pms) : [];
const projectFileInput = document.querySelector("#import-projects");
const projectCsvFileInput = document.querySelector("#import-projects-csv");
// --- end initialize variables and constants ---
class Project {
constructor(id,name,desc,size,designDue,designStarted,specDue,workedHours,availableHours,status,pm,url,rm,confluence,figma,figjam,notes) {
if (arguments.length !== 0) {
this.id = id;
this.name = name; //0
this.desc = desc; //1
this.size = size; //2
this.designDue = designDue; //3
this.designStarted = designStarted; //4
this.specDue = specDue; //5
this.workedHours = workedHours; //6
this.availableHours = availableHours; //7
this.status = status; //8
this.pm = pm; //9
this.url = url; //10
this.rm = rm; //11
this.confluence = confluence; //12
this.figma = figma; //13
this.figjam = figjam; //14
this.notes = notes || ''; //15
}
else
{
this.id = 999; //@TODO:Setting a weird default value until I can figure out a better solution
this.name = ""; //0
this.desc = ""; //1
this.size = ""; //2
this.designDue = ""; //3
this.designStarted = ""; //4
this.specDue = ""; //5
this.workedHours = 0; //6
this.availableHours = 0; //7
this.status = "Open"; //8
this.pm = ""; //9
this.url = ""; //10
this.rm = ""; //11
this.confluence = ""; //12
this.figma = ""; //13
this.figjam = ""; //14
this.notes = ""; //15
}
}
}
// --- end Project class ---
function getProjects() {
projects = localStorage.projects ? JSON.parse(localStorage.projects) : [];
displayProjects(projects);
showSnackBar("Loaded projects from localStorage");
}
// --- end getProjects ---
function saveProjects() {
localStorage.projects = "";
projectsToString = JSON.stringify(projects);
localStorage.projects = projectsToString;
showSnackBar("Saved projects to localStorage");
}
// --- end saveProjects ---
function importProjects() {
const file = projectFileInput.files[0];
const reader = new FileReader();
reader.addEventListener(
"load",
() => {
projects = JSON.parse(reader.result);
projects.forEach((project) => {
});
saveProjects(); //Save to localStorage
// getProjects(); //Load from localStorage
displayProjects(projects);
},
false,
);
if (file) {
reader.readAsText(file);
}
}
// --- end importProjects ---
function importProjectsCsv(event) {
projects = [];
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
const content = e.target.result;
let projectsCsv = content.split('\n');
projectsCsv.forEach((row) => {
const rowData = row.split(',');
const project = new Project(
rowData[0].trim(),
rowData[1].trim(),
rowData[2].trim(),
rowData[3].trim(),
rowData[4].trim(),
rowData[5].trim(),
rowData[6].trim(),
rowData[7].trim(),
rowData[8].trim(),
rowData[9].trim(),
rowData[10].trim(),
rowData[11].trim(),
rowData[12].trim(),
rowData[13].trim(),
rowData[14].trim(),
rowData[15].trim(),
);
projects.push(project);
});
saveProjects(); //Save to localStorage
getProjects(); //Load from localStorage
displayProjects(projects); //render data
}
reader.readAsText(file);
}
}
// --- end importProjectAsCsv ---
function exportProjects() {
let dataStr =
"data:text/json;charset=utf-8," +
encodeURIComponent(JSON.stringify(projects));
let dlAnchorElem = document.getElementById('btn-export-projects');
dlAnchorElem.setAttribute("href", dataStr);
dlAnchorElem.setAttribute("download", "projects.json");
}
// --- end exportProjects ---
function displayProjects(projectsToDisplay) {
let display = ``;
function tableCellWithUrl(content,contentType) {
if (content === '') {
return `<td class="empty">N/A</td>`;
} else {
return `<td>
<div class="link" title="${content}">
<a href="${content}" target="_blank">${contentType}</a>
<button class="btn btn-icon" data-url="${content}" onclick="copyTextToClipboard('${content}')">
<span class="icon icon-copy">Copy</span>
</button>
</div>
</td>`;
}
}
function tableCellWithDate(content) {
if (content) {
if (content.toUpperCase() === 'N/A') {
return content;
} else {
let contentToDate = content.split('-');
if (contentToDate.length === 3) {
const Month = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec',];
return Month[Number(contentToDate[1].trim()) - 1]
+ "-"
+ Number(contentToDate[2]);
} else {
return "";
}
}
} else {
return "";
}
}
projectsToDisplay.forEach( (project) => {
if (project.status && project.status.toLowerCase() === 'closed') {
display += '<tr class="closed">';
} else {
display += '<tr>';
}
display += `
<th class="name">
<a href="javascript:void(0)"
data-id="${project.id}"
class="btn-edit-project">
${project.name}</a>
</th>
<td>${project.desc}</td>
<td>${project.size}</td>
<td>${tableCellWithDate(project.designDue)}</td>
<td>${tableCellWithDate(project.designStarted)}</td>
<td>${tableCellWithDate(project.specDue)}</td>
<td class="hours">${project.workedHours}</td>
<td class="hours">${project.availableHours}</td>
<td class="status">${project.status}</td>
<td class="pm">${project.pm}</td>
${tableCellWithUrl(project.url, "Story")}
${tableCellWithUrl(project.rm, "RM")}
${tableCellWithUrl(project.confluence, "Confluence")}
${tableCellWithUrl(project.figma, "Figma")}
${tableCellWithUrl(project.figjam, "FigJam")}
<td>${project.notes || ''}</td>
`;
display += '</tr>';
});
document.querySelector('.view-projects-table-body').innerHTML = display;
document.querySelectorAll('.btn-edit-project').forEach((item)=> {
item.addEventListener('click', (e)=> {
editProject(Number(e.target.dataset.id));
});
});
}
// --- end displayProjects ---
function addProject() {
document.querySelector('.add-project').classList.add('show');
document.querySelector('.scrim').classList.add('show');
let tempProject = new Project();
let maxProjectId = 0;
projects.forEach( (project) => {
maxProjectId = Math.max(maxProjectId, project.id) + 1;
});
document.querySelector('#add-project-id').value = maxProjectId;
let selectPM = document.querySelector("#add-project-pm");
pms.forEach((pm) => {
let opt = document.createElement('option');
opt.value = pm.name;
opt.innerHTML = pm.name;
selectPM.appendChild(opt);
});
function closeAddProjectModal() {
document.querySelector('.add-project').classList.remove('show');
document.querySelector('.scrim').classList.remove('show');
document.querySelector('#add-project-name').value = "";
document.querySelector('#add-project-desc').value = "";
document.querySelector('#add-project-size').value = "";
document.querySelector('#add-project-design-due').value = "";
document.querySelector('#add-project-design-started').value = "";
document.querySelector('#add-project-spec-due').value = "";
document.querySelector('#add-project-worked-hours').value = "";
document.querySelector('#add-project-available-hours').value = "";
document.querySelector('#add-project-pm').value = "";
document.querySelector('#add-project-rm').value = "";
document.querySelector('#add-project-url').value = "";
document.querySelector('#add-project-confluence').value = "";
document.querySelector('#add-project-figma').value = "";
document.querySelector('#add-project-figjam').value = "";
document.querySelector('#add-project-notes').value = "";
}
document.querySelector('#add-project-btn-save')
.addEventListener('click', () => {
tempProject.id = maxProjectId;
tempProject.name = document.querySelector('#add-project-name').value;
tempProject.desc = document.querySelector('#add-project-desc').value;
tempProject.size = document.querySelector('#add-project-size').value;
tempProject.designDue = document.querySelector('#add-project-design-due').value;
tempProject.designStarted = document.querySelector('#add-project-design-started').value;
tempProject.specDue = document.querySelector('#add-project-spec-due').value;
tempProject.workedHours = document.querySelector('#add-project-worked-hours').value;
tempProject.availableHours = document.querySelector('#add-project-available-hours').value;
tempProject.status = document.querySelector('#add-project-status').value;
tempProject.pm = document.querySelector('#add-project-pm').value;
tempProject.rm = document.querySelector('#add-project-rm').value;
tempProject.url = document.querySelector('#add-project-url').value;
tempProject.confluence = document.querySelector('#add-project-confluence').value;
tempProject.figma = document.querySelector('#add-project-figma').value;
tempProject.figjam = document.querySelector('#add-project-figjam').value;
tempProject.notes = document.querySelector('#add-project-notes').value;
if (tempProject.name !== '') {
projects.push(tempProject);
}
saveProjects();
getProjects();
displayProjects(projects);
closeAddProjectModal();
showSnackBar(`${tempProject.name} added`);
});
document.querySelectorAll('.btn-close-add-project').forEach( (btn) => {
btn.addEventListener('click', () => {
closeAddProjectModal();
});
});
}
// --- end addProject ---
function editProject(id) {
const editProjectModal = document.querySelector('.edit-project');
editProjectModal.style.top = window.scrollY + 'px';
editProjectModal.classList.add('show');
document.querySelector('.scrim').classList.add('show');
projects.forEach((project) => {
if (project.id === id) {
document.querySelector('#edit-project-id').value = project.id;
document.querySelector('#edit-project-name').value = project.name;
document.querySelector('#edit-project-desc').value = project.desc;
document.querySelector('#edit-project-design-due').value = project.designDue;
document.querySelector('#edit-project-design-started').value = project.designStarted;
document.querySelector('#edit-project-spec-due').value = project.specDue;
document.querySelector('#edit-project-worked-hours').value = project.workedHours;
document.querySelector('#edit-project-available-hours').value = project.availableHours;
document.querySelector('#edit-project-rm').value = project.rm;
document.querySelector('#edit-project-url').value = project.url;
document.querySelector('#edit-project-confluence').value = project.confluence;
document.querySelector('#edit-project-figma').value = project.figma;
document.querySelector('#edit-project-figjam').value = project.figjam;
document.querySelector('#edit-project-notes').value = project.notes;
let selectPM = document.querySelector("#edit-project-pm");
pms.forEach((pm) => {
let opt = document.createElement('option');
opt.value = pm.name;
opt.innerHTML = pm.name;
if (pm.name.toLowerCase() === project.pm.toLowerCase()) {
opt.selected = true;
}
selectPM.appendChild(opt);
});
} // --- end found matching project.id ---
}); // -- end loop through projects ---
document.querySelector('#edit-project-btn-save')
.addEventListener('click', () => {
projects.forEach((project) => {
if (project.id === id) {
project.name = document.querySelector('#edit-project-name').value;
project.desc = document.querySelector('#edit-project-desc').value;
project.size = document.querySelector('#edit-project-size').value;
project.designDue = document.querySelector('#edit-project-design-due').value;
project.designStarted = document.querySelector('#edit-project-design-started').value;
project.specDue = document.querySelector('#edit-project-spec-due').value;
project.workedHours = document.querySelector('#edit-project-worked-hours').value;
project.availableHours = document.querySelector('#edit-project-available-hours').value;
project.pm = document.querySelector('#edit-project-pm').value;
project.rm = document.querySelector('#edit-project-rm').value;
project.url = document.querySelector('#edit-project-url').value;
project.confluence = document.querySelector('#edit-project-confluence').value;
project.figma = document.querySelector('#edit-project-figma').value;
project.figjam = document.querySelector('#edit-project-figjam').value;
showSnackBar(`${project.name} updated`);
}
});
saveProjects();
getProjects();
displayProjects(projects);
document.querySelector('.edit-project').classList.remove('show');
document.querySelector('.scrim').classList.remove('show');
});
document.querySelectorAll('.btn-close-edit-project').forEach( (btn) => {
btn.addEventListener('click', () => {
document.querySelector('.edit-project').classList.remove('show');
document.querySelector('.scrim').classList.remove('show');
document.querySelector('#edit-project-name').value = "";
document.querySelector('#edit-project-desc').value = "";
document.querySelector('#edit-project-size').value = "";
document.querySelector('#edit-project-design-due').value = "";
document.querySelector('#edit-project-design-started').value = "";
document.querySelector('#edit-project-spec-due').value = "";
document.querySelector('#edit-project-worked-hours').value = "";
document.querySelector('#edit-project-available-hours').value = "";
document.querySelector('#edit-project-status').value = "";
document.querySelector('#edit-project-pm').value = "";
document.querySelector('#edit-project-rm').value = "";
document.querySelector('#edit-project-url').value = "";
document.querySelector('#edit-project-confluence').value = "";
document.querySelector('#edit-project-figma').value = "";
document.querySelector('#edit-project-figjam').value = "";
document.querySelector('#edit-project-notes').value = "";
});
});
document.querySelector('#edit-project-btn-delete')
.addEventListener('click', () => {
deleteProject(id);
document.querySelector('.edit-project').classList.remove('show');
document.querySelector('.scrim').classList.remove('show');
});
}
// --- end editProject ---
function deleteProject(id) {
projects.forEach((project, index) => {
if (project.id === id) {
projects.splice(index,1); //Delete project from projects array
saveProjects();
getProjects();
displayProjects(projects);
showSnackBar(`Project #${project.id} deleted`);
}
});
}
// --- end deleteProject ---
function showSnackBar(text) {
const snackbar = document.querySelector('.snackbar');
snackbar.style.top = window.scrollY + 'px';
snackbar.innerHTML = text;
snackbar.classList.remove('hidden');
setTimeout( () => {
const snackbar = document.querySelector('.snackbar');
snackbar.classList.add('fade-out');
}, 2000);
setTimeout( () => {
const snackbar = document.querySelector('.snackbar');
snackbar.classList.remove('fade-out');
snackbar.classList.add('hidden');
}, 2500);
}
async function copyTextToClipboard(textToCopy) {
try {
await navigator.clipboard.writeText(textToCopy);
} catch (err) {
console.error('Failed to copy text: ', err);
}
showSnackBar('Copied to clipboard');
}
function searchProjects(e) {
let results = [];
let searchTerm = e.target.value.toLowerCase();
if (searchTerm === "") {
results = projects;
}
else if (searchTerm.length >= 2) {
projects.forEach( (project) => {
let projectToSearch = JSON.stringify(project).toLowerCase();
if (projectToSearch.indexOf(searchTerm) >= 0) {
results.push(project);
}
});
}
displayProjects(results);
}
// ------------------
// --- INITIALIZE ---
// ------------------
window.onload = ()=> {
document.querySelector('#project-search')
.addEventListener('keyup', (e) => {
document.querySelector('#project-search-reset').classList.remove('hidden');
document.querySelector('#project-search-icon').classList.add('hidden');
searchProjects(e);
});
document.querySelector('#project-search-reset')
.addEventListener('click', () => {
document.querySelector('#project-search').value = "";
displayProjects(projects);
document.querySelector('#project-search-reset').classList.add('hidden');
document.querySelector('#project-search-icon').classList.remove('hidden');
});
document.querySelector('#btn-add-project')
.addEventListener('click', ()=> {
addProject();
});
document
.getElementById('btn-export-projects')
.addEventListener('click',exportProjects);
//import Projects in JSON format
document
.querySelector('#btn-import-projects')
.addEventListener('click', ()=> {
projectFileInput.click();
});
projectFileInput
.addEventListener('change', importProjects);
//import Projects in CSV format
document
.querySelector('#btn-import-projects-csv')
.addEventListener('click', ()=> {
projectCsvFileInput.click();
});
projectCsvFileInput
.addEventListener('change', importProjectsCsv);
document.querySelector('#btn-reload-projects')
.addEventListener('click', ()=> {
getProjects();
});
//Initiate screen
getProjects(); //load projects from localStorage
exportProjects(); //generate export Projects button
displayProjects(projects);
// editProject(1); //For testing only
document.querySelector('.project-count').innerHTML = projects.length;
}
// --- end window.onload ---
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#777"><path d="M360-240q-33 0-56.5-23.5T280-320v-480q0-33 23.5-56.5T360-880h360q33 0 56.5 23.5T800-800v480q0 33-23.5 56.5T720-240H360Zm0-80h360v-480H360v480ZM200-80q-33 0-56.5-23.5T120-160v-560h80v560h440v80H200Zm160-240v-480 480Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#434343"><path d="M440-440H200v-80h240v-240h80v240h240v80H520v240h-80v-240Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#434343"><path d="M230-360h120v-60H250v-120h100v-60H230q-17 0-28.5 11.5T190-560v160q0 17 11.5 28.5T230-360Zm156 0h120q17 0 28.5-11.5T546-400v-60q0-17-11.5-31.5T506-506h-60v-34h100v-60H426q-17 0-28.5 11.5T386-560v60q0 17 11.5 30.5T426-456h60v36H386v60Zm264 0h60l70-240h-60l-40 138-40-138h-60l70 240ZM160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm0-80h640v-480H160v480Zm0 0v-480 480Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#434343"><path d="M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520ZM360-280h80v-360h-80v360Zm160 0h80v-360h-80v360ZM280-720v520-520Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#434343"><path d="M480-320 280-520l56-58 104 104v-326h80v326l104-104 56 58-200 200ZM240-160q-33 0-56.5-23.5T160-240v-120h80v120h480v-120h80v120q0 33-23.5 56.5T720-160H240Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#434343"><path d="M440-320v-326L336-542l-56-58 200-200 200 200-56 58-104-104v326h-80ZM240-160q-33 0-56.5-23.5T160-240v-120h80v120h480v-120h80v120q0 33-23.5 56.5T720-160H240Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#434343"><path d="M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#434343"><path d="M480-160q-134 0-227-93t-93-227q0-134 93-227t227-93q69 0 132 28.5T720-690v-110h80v280H520v-80h168q-32-56-87.5-88T480-720q-100 0-170 70t-70 170q0 100 70 170t170 70q77 0 139-44t87-116h84q-28 106-114 173t-196 67Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#434343"><path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#434343"><path d="M185-80q-17 0-29.5-12.5T143-122v-105q0-90 56-159t144-88q-40 28-62 70.5T259-312v190q0 11 3 22t10 20h-87Zm147 0q-17 0-29.5-12.5T290-122v-190q0-70 49.5-119T459-480h189q70 0 119 49t49 119v64q0 70-49 119T648-80H332Zm148-484q-66 0-112-46t-46-112q0-66 46-112t112-46q66 0 112 46t46 112q0 66-46 112t-112 46Z"/></svg>