Add issue type dropdown and Save to Dashboard from lookup

- Replace issue type text input with dropdown of STEAM project types (Story default)
- Add Save to Dashboard button on lookup results to link existing Jira tickets locally
- Make cve_id and vendor optional on local POST /api/jira-tickets endpoint
- Fix: use normalized values in local ticket INSERT query
This commit is contained in:
Jordan Ramos
2026-05-21 16:01:31 -06:00
parent dff1fa3cc9
commit 758a300f67
2 changed files with 72 additions and 8 deletions

View File

@@ -182,6 +182,9 @@ export default function JiraPage() {
const [lookupResult, setLookupResult] = useState(null);
const [lookupLoading, setLookupLoading] = useState(false);
const [lookupError, setLookupError] = useState(null);
const [linkingSaving, setLinkingSaving] = useState(false);
const [linkingError, setLinkingError] = useState(null);
const [linkingSuccess, setLinkingSuccess] = useState(null);
// Add/Edit modal
const [showForm, setShowForm] = useState(false);
@@ -287,6 +290,37 @@ export default function JiraPage() {
}
};
// ---------------------------------------------------------------------------
// Link existing Jira ticket — save to local DB without recreating in Jira
// ---------------------------------------------------------------------------
const linkExistingTicket = async (issue) => {
setLinkingError(null);
setLinkingSuccess(null);
setLinkingSaving(true);
try {
const jiraUrl = `https://jira.charter.com/browse/${issue.key}`;
const res = await fetch(`${API_BASE}/jira-tickets`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({
ticket_key: issue.key,
url: jiraUrl,
summary: issue.summary || '',
status: issue.status === 'Open' || issue.status === 'In Progress' || issue.status === 'Closed' ? issue.status : 'Open',
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
setLinkingSuccess(`${issue.key} saved to dashboard.`);
fetchTickets();
} catch (err) {
setLinkingError(err.message);
} finally {
setLinkingSaving(false);
}
};
// ---------------------------------------------------------------------------
// CRUD — save (create or update)
@@ -715,6 +749,18 @@ export default function JiraPage() {
<div><strong>Priority:</strong> {lookupResult.priority}</div>
<div><strong>Assignee:</strong> {lookupResult.assignee || 'Unassigned'}</div>
<div><strong>Updated:</strong> {lookupResult.updated ? new Date(lookupResult.updated).toLocaleString() : '-'}</div>
{canWrite() && (
<button
style={{ ...STYLES.btn, ...STYLES.btnSuccess, marginTop: '0.75rem' }}
onClick={() => linkExistingTicket(lookupResult)}
disabled={linkingSaving}
>
{linkingSaving ? <Loader size={14} className="animate-spin" /> : <Plus size={14} />}
Save to Dashboard
</button>
)}
{linkingError && <div style={{ color: '#FCA5A5', fontSize: '0.75rem', marginTop: '0.5rem' }}>{linkingError}</div>}
{linkingSuccess && <div style={{ color: '#6EE7B7', fontSize: '0.75rem', marginTop: '0.5rem' }}>{linkingSuccess}</div>}
</div>
)}
</div>
@@ -828,8 +874,16 @@ export default function JiraPage() {
<input style={STYLES.input} placeholder="Uses .env default" value={createJiraForm.project_key} onChange={e => setCreateJiraForm(f => ({ ...f, project_key: e.target.value.toUpperCase() }))} />
</div>
<div>
<label style={{ fontSize: '0.75rem', color: '#94A3B8', display: 'block', marginBottom: '0.25rem' }}>Issue Type (optional)</label>
<input style={STYLES.input} placeholder="Task" value={createJiraForm.issue_type} onChange={e => setCreateJiraForm(f => ({ ...f, issue_type: e.target.value }))} />
<label style={{ fontSize: '0.75rem', color: '#94A3B8', display: 'block', marginBottom: '0.25rem' }}>Issue Type</label>
<select style={{ ...STYLES.input, cursor: 'pointer' }} value={createJiraForm.issue_type} onChange={e => setCreateJiraForm(f => ({ ...f, issue_type: e.target.value }))}>
<option value="">Story (default)</option>
<option value="Story">Story</option>
<option value="Epic">Epic</option>
<option value="Program">Program</option>
<option value="Project">Project</option>
<option value="Reservation">Reservation</option>
<option value="Automation Maintenance">Automation Maintenance</option>
</select>
</div>
</div>
<button style={{ ...STYLES.btn, ...STYLES.btnSuccess, justifyContent: 'center', marginTop: '0.5rem' }} onClick={createInJira} disabled={createJiraSaving}>