mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-10 02:24:35 +08:00
feat(issue-management): Implement interactive issue management command with CRUD operations
- Added `/issue:manage` command for interactive issue management via CLI. - Implemented features for listing, viewing, editing, deleting, and bulk operations on issues. - Integrated GitHub issue fetching and text description parsing for issue creation. - Enhanced user experience with menu-driven interface and structured output. - Created helper functions for parsing user input and managing issue data. - Added error handling and related command references for better usability. feat(issue-creation): Introduce structured issue creation from GitHub URL or text description - Added `/issue:new` command to create structured issues from GitHub issues or text descriptions. - Implemented parsing logic for extracting key elements from issue descriptions. - Integrated user confirmation for issue creation with options to edit title and priority. - Ensured proper writing of issues to `.workflow/issues/issues.jsonl` with metadata. - Included examples and error handling for various input scenarios.
This commit is contained in:
@@ -663,9 +663,11 @@ async function queueAction(subAction: string | undefined, issueId: string | unde
|
||||
for (const item of queue.queue) {
|
||||
const statusColor = {
|
||||
'pending': chalk.gray,
|
||||
'ready': chalk.cyan,
|
||||
'executing': chalk.yellow,
|
||||
'completed': chalk.green,
|
||||
'failed': chalk.red
|
||||
'failed': chalk.red,
|
||||
'blocked': chalk.magenta
|
||||
}[item.status] || chalk.white;
|
||||
|
||||
console.log(
|
||||
|
||||
@@ -21,6 +21,7 @@ const MODULE_FILES = [
|
||||
'dashboard-js/components/tabs-other.js',
|
||||
'dashboard-js/components/carousel.js',
|
||||
'dashboard-js/components/notifications.js',
|
||||
'dashboard-js/components/cli-stream-viewer.js',
|
||||
'dashboard-js/components/global-notifications.js',
|
||||
'dashboard-js/components/cli-status.js',
|
||||
'dashboard-js/components/cli-history.js',
|
||||
|
||||
@@ -88,7 +88,8 @@ const MODULE_CSS_FILES = [
|
||||
'29-help.css',
|
||||
'30-core-memory.css',
|
||||
'31-api-settings.css',
|
||||
'32-issue-manager.css'
|
||||
'32-issue-manager.css',
|
||||
'33-cli-stream-viewer.css'
|
||||
];
|
||||
|
||||
// Modular JS files in dependency order
|
||||
@@ -109,6 +110,7 @@ const MODULE_FILES = [
|
||||
'components/flowchart.js',
|
||||
'components/carousel.js',
|
||||
'components/notifications.js',
|
||||
'components/cli-stream-viewer.js',
|
||||
'components/global-notifications.js',
|
||||
'components/task-queue-sidebar.js',
|
||||
'components/cli-status.js',
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
/* Issue Header */
|
||||
.issue-header {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
/* View Toggle (Issues/Queue) */
|
||||
.issue-view-toggle {
|
||||
display: inline-flex;
|
||||
@@ -60,17 +65,59 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.issues-empty-state {
|
||||
.issues-empty-state,
|
||||
.issue-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 160px;
|
||||
min-height: 200px;
|
||||
text-align: center;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.issue-empty svg {
|
||||
margin-bottom: 1rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Issue Filters */
|
||||
.issue-filters {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.issue-filter-btn {
|
||||
padding: 0.375rem 0.75rem;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
color: hsl(var(--muted-foreground));
|
||||
background: hsl(var(--muted));
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.issue-filter-btn:hover {
|
||||
background: hsl(var(--muted) / 0.8);
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.issue-filter-btn.active {
|
||||
background: hsl(var(--primary));
|
||||
color: hsl(var(--primary-foreground));
|
||||
}
|
||||
|
||||
/* Issue Card */
|
||||
.issue-card {
|
||||
position: relative;
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -78,6 +125,7 @@
|
||||
.issue-card:hover {
|
||||
border-color: hsl(var(--primary));
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px hsl(var(--foreground) / 0.08);
|
||||
}
|
||||
|
||||
.issue-card-header {
|
||||
@@ -93,14 +141,16 @@
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.issue-card-title {
|
||||
.issue-card-title,
|
||||
.issue-title {
|
||||
font-weight: 600;
|
||||
font-size: 0.9375rem;
|
||||
line-height: 1.4;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.issue-card-meta {
|
||||
.issue-card-meta,
|
||||
.issue-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
@@ -141,6 +191,11 @@
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.issue-status.planning {
|
||||
background: hsl(38 92% 50% / 0.15);
|
||||
color: hsl(38 92% 50%);
|
||||
}
|
||||
|
||||
.issue-status.planned {
|
||||
background: hsl(217 91% 60% / 0.15);
|
||||
color: hsl(217 91% 60%);
|
||||
@@ -210,13 +265,30 @@
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.queue-empty-state {
|
||||
.queue-empty-state,
|
||||
.queue-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 200px;
|
||||
text-align: center;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.queue-empty svg {
|
||||
margin-bottom: 1rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Issue ID */
|
||||
.issue-id {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.75rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
background: hsl(var(--muted));
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
/* Execution Group */
|
||||
@@ -977,3 +1049,713 @@
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
TOOLBAR & SEARCH
|
||||
========================================== */
|
||||
|
||||
.issue-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
padding: 0.75rem;
|
||||
background: hsl(var(--muted) / 0.3);
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.issue-search {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
.issue-search > i:first-child {
|
||||
position: absolute;
|
||||
left: 0.75rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.issue-search input {
|
||||
width: 100%;
|
||||
padding: 0.5rem 2rem 0.5rem 2.25rem;
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.375rem;
|
||||
background: hsl(var(--background));
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--foreground));
|
||||
transition: border-color 0.15s ease;
|
||||
}
|
||||
|
||||
.issue-search input:focus {
|
||||
outline: none;
|
||||
border-color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.issue-search input::placeholder {
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.issue-search-clear {
|
||||
position: absolute;
|
||||
right: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
border: none;
|
||||
border-radius: 9999px;
|
||||
background: hsl(var(--muted));
|
||||
color: hsl(var(--muted-foreground));
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.issue-search-clear:hover {
|
||||
background: hsl(var(--destructive) / 0.1);
|
||||
color: hsl(var(--destructive));
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
CREATE BUTTON
|
||||
========================================== */
|
||||
|
||||
.issue-create-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background: hsl(var(--primary));
|
||||
color: hsl(var(--primary-foreground));
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.issue-create-btn:hover {
|
||||
background: hsl(var(--primary) / 0.9);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.issue-create-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
ISSUE STATS
|
||||
========================================== */
|
||||
|
||||
.issue-stats {
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
EMPTY STATE (CENTERED)
|
||||
========================================== */
|
||||
|
||||
.issue-empty-container {
|
||||
grid-column: 1 / -1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.issue-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.issue-empty > i,
|
||||
.issue-empty > svg {
|
||||
color: hsl(var(--muted-foreground));
|
||||
opacity: 0.5;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.issue-empty-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.issue-empty-hint {
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.issue-empty-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.625rem 1.25rem;
|
||||
background: hsl(var(--primary));
|
||||
color: hsl(var(--primary-foreground));
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.issue-empty-btn:hover {
|
||||
background: hsl(var(--primary) / 0.9);
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
MODAL STYLES
|
||||
========================================== */
|
||||
|
||||
.issue-modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.issue-modal.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.issue-modal-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: hsl(var(--foreground) / 0.5);
|
||||
animation: fadeIn 0.15s ease-out;
|
||||
}
|
||||
|
||||
.issue-modal-content {
|
||||
position: relative;
|
||||
width: 90%;
|
||||
max-width: 480px;
|
||||
background: hsl(var(--background));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 20px 40px hsl(var(--foreground) / 0.15);
|
||||
animation: modalSlideIn 0.2s ease-out;
|
||||
}
|
||||
|
||||
@keyframes modalSlideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px) scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.issue-modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1rem 1.25rem;
|
||||
border-bottom: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.issue-modal-header h3 {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.issue-modal-body {
|
||||
padding: 1.25rem;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.issue-modal-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 0.75rem;
|
||||
padding: 1rem 1.25rem;
|
||||
border-top: 1px solid hsl(var(--border));
|
||||
background: hsl(var(--muted) / 0.3);
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
FORM STYLES
|
||||
========================================== */
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-group:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
color: hsl(var(--foreground));
|
||||
margin-bottom: 0.375rem;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group textarea,
|
||||
.form-group select {
|
||||
width: 100%;
|
||||
padding: 0.625rem 0.75rem;
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.375rem;
|
||||
background: hsl(var(--background));
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--foreground));
|
||||
transition: border-color 0.15s ease;
|
||||
}
|
||||
|
||||
.form-group input:focus,
|
||||
.form-group textarea:focus,
|
||||
.form-group select:focus {
|
||||
outline: none;
|
||||
border-color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.form-group input::placeholder,
|
||||
.form-group textarea::placeholder {
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.form-group textarea {
|
||||
resize: vertical;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.form-group select {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
BUTTON STYLES
|
||||
========================================== */
|
||||
|
||||
.btn-primary {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background: hsl(var(--primary));
|
||||
color: hsl(var(--primary-foreground));
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: hsl(var(--primary) / 0.9);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background: hsl(var(--muted));
|
||||
color: hsl(var(--foreground));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: hsl(var(--muted) / 0.8);
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
color: hsl(var(--muted-foreground));
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.btn-icon:hover {
|
||||
background: hsl(var(--muted));
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
DETAIL PANEL ENHANCEMENTS
|
||||
========================================== */
|
||||
|
||||
.issue-detail-content {
|
||||
padding: 1.25rem;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
display: block;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: hsl(var(--muted-foreground));
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.detail-editable {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
flex: 1;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.detail-context {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.detail-pre {
|
||||
font-family: inherit;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.6;
|
||||
color: hsl(var(--foreground));
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
padding: 0.75rem;
|
||||
background: hsl(var(--muted) / 0.3);
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
color: hsl(var(--muted-foreground));
|
||||
border: none;
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.btn-edit:hover {
|
||||
background: hsl(var(--muted));
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
/* Solutions List */
|
||||
.solutions-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.solution-item {
|
||||
padding: 0.75rem;
|
||||
background: hsl(var(--muted) / 0.3);
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.solution-item:hover {
|
||||
border-color: hsl(var(--primary) / 0.5);
|
||||
}
|
||||
|
||||
.solution-item.bound {
|
||||
border-color: hsl(var(--success));
|
||||
background: hsl(var(--success) / 0.05);
|
||||
}
|
||||
|
||||
.solution-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.solution-id {
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.solution-bound-badge {
|
||||
display: inline-flex;
|
||||
padding: 0.125rem 0.375rem;
|
||||
background: hsl(var(--success) / 0.15);
|
||||
color: hsl(var(--success));
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 500;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.solution-tasks {
|
||||
margin-left: auto;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.solution-tasks-list {
|
||||
margin-top: 0.75rem;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid hsl(var(--border) / 0.5);
|
||||
}
|
||||
|
||||
/* Tasks List */
|
||||
.tasks-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.task-item-detail {
|
||||
padding: 0.75rem;
|
||||
background: hsl(var(--muted) / 0.3);
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
.task-title-detail {
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--foreground));
|
||||
margin-top: 0.375rem;
|
||||
}
|
||||
|
||||
/* Edit Mode */
|
||||
.edit-input,
|
||||
.edit-textarea {
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: 1px solid hsl(var(--primary));
|
||||
border-radius: 0.375rem;
|
||||
background: hsl(var(--background));
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.edit-input:focus,
|
||||
.edit-textarea:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.edit-textarea {
|
||||
resize: vertical;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.edit-actions {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-save,
|
||||
.btn-cancel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
background: hsl(var(--success) / 0.15);
|
||||
color: hsl(var(--success));
|
||||
}
|
||||
|
||||
.btn-save:hover {
|
||||
background: hsl(var(--success) / 0.25);
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background: hsl(var(--muted));
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.btn-cancel:hover {
|
||||
background: hsl(var(--destructive) / 0.1);
|
||||
color: hsl(var(--destructive));
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
QUEUE ENHANCEMENTS
|
||||
========================================== */
|
||||
|
||||
.queue-info {
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.queue-items {
|
||||
padding: 0.75rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.queue-items.parallel {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.queue-items.parallel .queue-item {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.queue-group-type {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.queue-group-type.parallel {
|
||||
color: hsl(142 71% 45%);
|
||||
}
|
||||
|
||||
.queue-group-type.sequential {
|
||||
color: hsl(262 83% 58%);
|
||||
}
|
||||
|
||||
/* Queue Item Status Colors */
|
||||
.queue-item.ready {
|
||||
border-color: hsl(199 89% 48%);
|
||||
}
|
||||
|
||||
.queue-item.executing {
|
||||
border-color: hsl(45 93% 47%);
|
||||
background: hsl(45 93% 47% / 0.05);
|
||||
}
|
||||
|
||||
.queue-item.completed {
|
||||
border-color: hsl(var(--success));
|
||||
background: hsl(var(--success) / 0.05);
|
||||
}
|
||||
|
||||
.queue-item.failed {
|
||||
border-color: hsl(var(--destructive));
|
||||
background: hsl(var(--destructive) / 0.05);
|
||||
}
|
||||
|
||||
.queue-item.blocked {
|
||||
border-color: hsl(262 83% 58%);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* Priority indicator */
|
||||
.issue-priority {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.125rem;
|
||||
}
|
||||
|
||||
/* Conflicts list */
|
||||
.conflicts-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.conflict-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: hsl(45 93% 47% / 0.1);
|
||||
border: 1px solid hsl(45 93% 47% / 0.3);
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
.conflict-file {
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.conflict-tasks {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.conflict-status {
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 500;
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.conflict-status.pending {
|
||||
background: hsl(45 93% 47% / 0.15);
|
||||
color: hsl(45 93% 47%);
|
||||
}
|
||||
|
||||
.conflict-status.resolved {
|
||||
background: hsl(var(--success) / 0.15);
|
||||
color: hsl(var(--success));
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
RESPONSIVE TOOLBAR
|
||||
========================================== */
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.issue-toolbar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.issue-search {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.issue-filters {
|
||||
justify-content: flex-start;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ function renderStreamTabs() {
|
||||
<span class="cli-stream-tab-mode">${exec.mode}</span>
|
||||
<button class="cli-stream-tab-close ${canClose ? '' : 'disabled'}"
|
||||
onclick="event.stopPropagation(); closeStream('${id}')"
|
||||
title="${canClose ? t('cliStream.close') : t('cliStream.cannotCloseRunning')}"
|
||||
title="${canClose ? _streamT('cliStream.close') : _streamT('cliStream.cannotCloseRunning')}"
|
||||
${canClose ? '' : 'disabled'}>×</button>
|
||||
</div>
|
||||
`;
|
||||
@@ -238,8 +238,8 @@ function renderStreamContent(executionId) {
|
||||
contentContainer.innerHTML = `
|
||||
<div class="cli-stream-empty">
|
||||
<i data-lucide="terminal"></i>
|
||||
<div class="cli-stream-empty-title" data-i18n="cliStream.noStreams">${t('cliStream.noStreams')}</div>
|
||||
<div class="cli-stream-empty-hint" data-i18n="cliStream.noStreamsHint">${t('cliStream.noStreamsHint')}</div>
|
||||
<div class="cli-stream-empty-title" data-i18n="cliStream.noStreams">${_streamT('cliStream.noStreams')}</div>
|
||||
<div class="cli-stream-empty-hint" data-i18n="cliStream.noStreamsHint">${_streamT('cliStream.noStreamsHint')}</div>
|
||||
</div>
|
||||
`;
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
@@ -279,10 +279,10 @@ function renderStreamStatus(executionId) {
|
||||
: formatDuration(Date.now() - exec.startTime);
|
||||
|
||||
const statusLabel = exec.status === 'running'
|
||||
? t('cliStream.running')
|
||||
? _streamT('cliStream.running')
|
||||
: exec.status === 'completed'
|
||||
? t('cliStream.completed')
|
||||
: t('cliStream.error');
|
||||
? _streamT('cliStream.completed')
|
||||
: _streamT('cliStream.error');
|
||||
|
||||
statusContainer.innerHTML = `
|
||||
<div class="cli-stream-status-info">
|
||||
@@ -296,15 +296,15 @@ function renderStreamStatus(executionId) {
|
||||
</div>
|
||||
<div class="cli-stream-status-item">
|
||||
<i data-lucide="file-text"></i>
|
||||
<span>${exec.output.length} ${t('cliStream.lines') || 'lines'}</span>
|
||||
<span>${exec.output.length} ${_streamT('cliStream.lines') || 'lines'}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cli-stream-status-actions">
|
||||
<button class="cli-stream-toggle-btn ${autoScrollEnabled ? 'active' : ''}"
|
||||
onclick="toggleAutoScroll()"
|
||||
title="${t('cliStream.autoScroll')}">
|
||||
title="${_streamT('cliStream.autoScroll')}">
|
||||
<i data-lucide="arrow-down-to-line"></i>
|
||||
<span data-i18n="cliStream.autoScroll">${t('cliStream.autoScroll')}</span>
|
||||
<span data-i18n="cliStream.autoScroll">${_streamT('cliStream.autoScroll')}</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
@@ -428,10 +428,15 @@ function escapeHtml(text) {
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Translation helper with fallback
|
||||
function t(key) {
|
||||
if (typeof window.t === 'function') {
|
||||
return window.t(key);
|
||||
// Translation helper with fallback (uses global t from i18n.js)
|
||||
function _streamT(key) {
|
||||
// First try global t() from i18n.js
|
||||
if (typeof t === 'function' && t !== _streamT) {
|
||||
try {
|
||||
return t(key);
|
||||
} catch (e) {
|
||||
// Fall through to fallbacks
|
||||
}
|
||||
}
|
||||
// Fallback values
|
||||
const fallbacks = {
|
||||
|
||||
@@ -1729,6 +1729,50 @@ const i18n = {
|
||||
'nav.issues': 'Issues',
|
||||
'nav.issueManager': 'Manager',
|
||||
'title.issueManager': 'Issue Manager',
|
||||
// issues.* keys (used by issue-manager.js)
|
||||
'issues.title': 'Issue Manager',
|
||||
'issues.description': 'Manage issues, solutions, and execution queue',
|
||||
'issues.viewIssues': 'Issues',
|
||||
'issues.viewQueue': 'Queue',
|
||||
'issues.filterStatus': 'Status',
|
||||
'issues.filterAll': 'All',
|
||||
'issues.noIssues': 'No issues found',
|
||||
'issues.createHint': 'Click "Create" to add your first issue',
|
||||
'issues.priority': 'Priority',
|
||||
'issues.tasks': 'tasks',
|
||||
'issues.solutions': 'solutions',
|
||||
'issues.boundSolution': 'Bound',
|
||||
'issues.queueEmpty': 'Queue is empty',
|
||||
'issues.reorderHint': 'Drag items within a group to reorder',
|
||||
'issues.parallelGroup': 'Parallel',
|
||||
'issues.sequentialGroup': 'Sequential',
|
||||
'issues.dependsOn': 'Depends on',
|
||||
// Create & Search
|
||||
'issues.create': 'Create',
|
||||
'issues.createTitle': 'Create New Issue',
|
||||
'issues.issueId': 'Issue ID',
|
||||
'issues.issueTitle': 'Title',
|
||||
'issues.issueContext': 'Context',
|
||||
'issues.issuePriority': 'Priority',
|
||||
'issues.titlePlaceholder': 'Brief description of the issue',
|
||||
'issues.contextPlaceholder': 'Detailed description, requirements, etc.',
|
||||
'issues.priorityLowest': 'Lowest',
|
||||
'issues.priorityLow': 'Low',
|
||||
'issues.priorityMedium': 'Medium',
|
||||
'issues.priorityHigh': 'High',
|
||||
'issues.priorityCritical': 'Critical',
|
||||
'issues.searchPlaceholder': 'Search issues...',
|
||||
'issues.showing': 'Showing',
|
||||
'issues.of': 'of',
|
||||
'issues.issues': 'issues',
|
||||
'issues.tryDifferentFilter': 'Try adjusting your search or filters',
|
||||
'issues.createFirst': 'Create First Issue',
|
||||
'issues.idRequired': 'Issue ID is required',
|
||||
'issues.titleRequired': 'Title is required',
|
||||
'issues.created': 'Issue created successfully',
|
||||
'issues.confirmDelete': 'Are you sure you want to delete this issue?',
|
||||
'issues.deleted': 'Issue deleted',
|
||||
// issue.* keys (legacy)
|
||||
'issue.viewIssues': 'Issues',
|
||||
'issue.viewQueue': 'Queue',
|
||||
'issue.filterAll': 'All',
|
||||
@@ -3508,6 +3552,50 @@ const i18n = {
|
||||
'nav.issues': '议题',
|
||||
'nav.issueManager': '管理器',
|
||||
'title.issueManager': '议题管理器',
|
||||
// issues.* keys (used by issue-manager.js)
|
||||
'issues.title': '议题管理器',
|
||||
'issues.description': '管理议题、解决方案和执行队列',
|
||||
'issues.viewIssues': '议题',
|
||||
'issues.viewQueue': '队列',
|
||||
'issues.filterStatus': '状态',
|
||||
'issues.filterAll': '全部',
|
||||
'issues.noIssues': '暂无议题',
|
||||
'issues.createHint': '点击"创建"添加您的第一个议题',
|
||||
'issues.priority': '优先级',
|
||||
'issues.tasks': '任务',
|
||||
'issues.solutions': '解决方案',
|
||||
'issues.boundSolution': '已绑定',
|
||||
'issues.queueEmpty': '队列为空',
|
||||
'issues.reorderHint': '在组内拖拽项目以重新排序',
|
||||
'issues.parallelGroup': '并行',
|
||||
'issues.sequentialGroup': '顺序',
|
||||
'issues.dependsOn': '依赖于',
|
||||
// Create & Search
|
||||
'issues.create': '创建',
|
||||
'issues.createTitle': '创建新议题',
|
||||
'issues.issueId': '议题ID',
|
||||
'issues.issueTitle': '标题',
|
||||
'issues.issueContext': '上下文',
|
||||
'issues.issuePriority': '优先级',
|
||||
'issues.titlePlaceholder': '简要描述议题',
|
||||
'issues.contextPlaceholder': '详细描述、需求等',
|
||||
'issues.priorityLowest': '最低',
|
||||
'issues.priorityLow': '低',
|
||||
'issues.priorityMedium': '中',
|
||||
'issues.priorityHigh': '高',
|
||||
'issues.priorityCritical': '紧急',
|
||||
'issues.searchPlaceholder': '搜索议题...',
|
||||
'issues.showing': '显示',
|
||||
'issues.of': '共',
|
||||
'issues.issues': '条议题',
|
||||
'issues.tryDifferentFilter': '尝试调整搜索或筛选条件',
|
||||
'issues.createFirst': '创建第一个议题',
|
||||
'issues.idRequired': '议题ID为必填',
|
||||
'issues.titleRequired': '标题为必填',
|
||||
'issues.created': '议题创建成功',
|
||||
'issues.confirmDelete': '确定要删除此议题吗?',
|
||||
'issues.deleted': '议题已删除',
|
||||
// issue.* keys (legacy)
|
||||
'issue.viewIssues': '议题',
|
||||
'issue.viewQueue': '队列',
|
||||
'issue.filterAll': '全部',
|
||||
|
||||
@@ -10,6 +10,7 @@ var issueData = {
|
||||
selectedIssue: null,
|
||||
selectedSolution: null,
|
||||
statusFilter: 'all',
|
||||
searchQuery: '',
|
||||
viewMode: 'issues' // 'issues' | 'queue'
|
||||
};
|
||||
var issueLoading = false;
|
||||
@@ -91,10 +92,20 @@ function renderIssueView() {
|
||||
if (!container) return;
|
||||
|
||||
const issues = issueData.issues || [];
|
||||
const filteredIssues = issueData.statusFilter === 'all'
|
||||
// Apply both status and search filters
|
||||
let filteredIssues = issueData.statusFilter === 'all'
|
||||
? issues
|
||||
: issues.filter(i => i.status === issueData.statusFilter);
|
||||
|
||||
if (issueData.searchQuery) {
|
||||
const query = issueData.searchQuery.toLowerCase();
|
||||
filteredIssues = filteredIssues.filter(i =>
|
||||
i.id.toLowerCase().includes(query) ||
|
||||
(i.title && i.title.toLowerCase().includes(query)) ||
|
||||
(i.context && i.context.toLowerCase().includes(query))
|
||||
);
|
||||
}
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="issue-manager">
|
||||
<!-- Header -->
|
||||
@@ -110,16 +121,24 @@ function renderIssueView() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- View Toggle -->
|
||||
<div class="issue-view-toggle">
|
||||
<button class="${issueData.viewMode === 'issues' ? 'active' : ''}" onclick="switchIssueView('issues')">
|
||||
<i data-lucide="list" class="w-4 h-4 mr-1"></i>
|
||||
${t('issues.viewIssues') || 'Issues'}
|
||||
</button>
|
||||
<button class="${issueData.viewMode === 'queue' ? 'active' : ''}" onclick="switchIssueView('queue')">
|
||||
<i data-lucide="git-branch" class="w-4 h-4 mr-1"></i>
|
||||
${t('issues.viewQueue') || 'Queue'}
|
||||
<div class="flex items-center gap-3">
|
||||
<!-- Create Button -->
|
||||
<button class="issue-create-btn" onclick="showCreateIssueModal()">
|
||||
<i data-lucide="plus" class="w-4 h-4"></i>
|
||||
<span>${t('issues.create') || 'Create'}</span>
|
||||
</button>
|
||||
|
||||
<!-- View Toggle -->
|
||||
<div class="issue-view-toggle">
|
||||
<button class="${issueData.viewMode === 'issues' ? 'active' : ''}" onclick="switchIssueView('issues')">
|
||||
<i data-lucide="list" class="w-4 h-4 mr-1"></i>
|
||||
${t('issues.viewIssues') || 'Issues'}
|
||||
</button>
|
||||
<button class="${issueData.viewMode === 'queue' ? 'active' : ''}" onclick="switchIssueView('queue')">
|
||||
<i data-lucide="git-branch" class="w-4 h-4 mr-1"></i>
|
||||
${t('issues.viewQueue') || 'Queue'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -128,6 +147,47 @@ function renderIssueView() {
|
||||
|
||||
<!-- Detail Panel -->
|
||||
<div id="issueDetailPanel" class="issue-detail-panel hidden"></div>
|
||||
|
||||
<!-- Create Issue Modal -->
|
||||
<div id="createIssueModal" class="issue-modal hidden">
|
||||
<div class="issue-modal-backdrop" onclick="hideCreateIssueModal()"></div>
|
||||
<div class="issue-modal-content">
|
||||
<div class="issue-modal-header">
|
||||
<h3>${t('issues.createTitle') || 'Create New Issue'}</h3>
|
||||
<button class="btn-icon" onclick="hideCreateIssueModal()">
|
||||
<i data-lucide="x" class="w-5 h-5"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="issue-modal-body">
|
||||
<div class="form-group">
|
||||
<label>${t('issues.issueId') || 'Issue ID'}</label>
|
||||
<input type="text" id="newIssueId" placeholder="e.g., GH-123 or TASK-001" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>${t('issues.issueTitle') || 'Title'}</label>
|
||||
<input type="text" id="newIssueTitle" placeholder="${t('issues.titlePlaceholder') || 'Brief description of the issue'}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>${t('issues.issueContext') || 'Context'} (${t('common.optional') || 'optional'})</label>
|
||||
<textarea id="newIssueContext" rows="4" placeholder="${t('issues.contextPlaceholder') || 'Detailed description, requirements, etc.'}"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>${t('issues.issuePriority') || 'Priority'}</label>
|
||||
<select id="newIssuePriority">
|
||||
<option value="1">1 - ${t('issues.priorityLowest') || 'Lowest'}</option>
|
||||
<option value="2">2 - ${t('issues.priorityLow') || 'Low'}</option>
|
||||
<option value="3" selected>3 - ${t('issues.priorityMedium') || 'Medium'}</option>
|
||||
<option value="4">4 - ${t('issues.priorityHigh') || 'High'}</option>
|
||||
<option value="5">5 - ${t('issues.priorityCritical') || 'Critical'}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="issue-modal-footer">
|
||||
<button class="btn-secondary" onclick="hideCreateIssueModal()">${t('common.cancel') || 'Cancel'}</button>
|
||||
<button class="btn-primary" onclick="createIssue()">${t('issues.create') || 'Create'}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -147,11 +207,26 @@ function switchIssueView(mode) {
|
||||
// ========== Issue List Section ==========
|
||||
function renderIssueListSection(issues) {
|
||||
const statuses = ['all', 'registered', 'planning', 'planned', 'queued', 'executing', 'completed', 'failed'];
|
||||
const totalIssues = issueData.issues?.length || 0;
|
||||
|
||||
return `
|
||||
<!-- Filters -->
|
||||
<div class="issue-filters mb-4">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<!-- Toolbar: Search + Filters -->
|
||||
<div class="issue-toolbar mb-4">
|
||||
<div class="issue-search">
|
||||
<i data-lucide="search" class="w-4 h-4"></i>
|
||||
<input type="text"
|
||||
id="issueSearchInput"
|
||||
placeholder="${t('issues.searchPlaceholder') || 'Search issues...'}"
|
||||
value="${issueData.searchQuery}"
|
||||
oninput="handleIssueSearch(this.value)" />
|
||||
${issueData.searchQuery ? `
|
||||
<button class="issue-search-clear" onclick="clearIssueSearch()">
|
||||
<i data-lucide="x" class="w-3 h-3"></i>
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
<div class="issue-filters">
|
||||
<span class="text-sm text-muted-foreground">${t('issues.filterStatus') || 'Status'}:</span>
|
||||
${statuses.map(status => `
|
||||
<button class="issue-filter-btn ${issueData.statusFilter === status ? 'active' : ''}"
|
||||
@@ -162,13 +237,30 @@ function renderIssueListSection(issues) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Issues Stats -->
|
||||
<div class="issue-stats mb-4">
|
||||
<span class="text-sm text-muted-foreground">
|
||||
${t('issues.showing') || 'Showing'} <strong>${issues.length}</strong> ${t('issues.of') || 'of'} <strong>${totalIssues}</strong> ${t('issues.issues') || 'issues'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Issues Grid -->
|
||||
<div class="issues-grid">
|
||||
${issues.length === 0 ? `
|
||||
<div class="issue-empty">
|
||||
<i data-lucide="inbox" class="w-12 h-12 text-muted-foreground mb-4"></i>
|
||||
<p class="text-muted-foreground">${t('issues.noIssues') || 'No issues found'}</p>
|
||||
<p class="text-sm text-muted-foreground mt-2">${t('issues.createHint') || 'Create issues using: ccw issue init <id>'}</p>
|
||||
<div class="issue-empty-container">
|
||||
<div class="issue-empty">
|
||||
<i data-lucide="inbox" class="w-16 h-16"></i>
|
||||
<p class="issue-empty-title">${t('issues.noIssues') || 'No issues found'}</p>
|
||||
<p class="issue-empty-hint">${issueData.searchQuery || issueData.statusFilter !== 'all'
|
||||
? (t('issues.tryDifferentFilter') || 'Try adjusting your search or filters')
|
||||
: (t('issues.createHint') || 'Click "Create" to add your first issue')}</p>
|
||||
${!issueData.searchQuery && issueData.statusFilter === 'all' ? `
|
||||
<button class="issue-empty-btn" onclick="showCreateIssueModal()">
|
||||
<i data-lucide="plus" class="w-4 h-4"></i>
|
||||
${t('issues.createFirst') || 'Create First Issue'}
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
` : issues.map(issue => renderIssueCard(issue)).join('')}
|
||||
</div>
|
||||
@@ -702,3 +794,122 @@ async function updateTaskStatus(issueId, taskId, status) {
|
||||
showNotification('Failed to update task status', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Search Functions ==========
|
||||
function handleIssueSearch(value) {
|
||||
issueData.searchQuery = value;
|
||||
renderIssueView();
|
||||
}
|
||||
|
||||
function clearIssueSearch() {
|
||||
issueData.searchQuery = '';
|
||||
renderIssueView();
|
||||
}
|
||||
|
||||
// ========== Create Issue Modal ==========
|
||||
function showCreateIssueModal() {
|
||||
const modal = document.getElementById('createIssueModal');
|
||||
if (modal) {
|
||||
modal.classList.remove('hidden');
|
||||
lucide.createIcons();
|
||||
// Focus on first input
|
||||
setTimeout(() => {
|
||||
document.getElementById('newIssueId')?.focus();
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
function hideCreateIssueModal() {
|
||||
const modal = document.getElementById('createIssueModal');
|
||||
if (modal) {
|
||||
modal.classList.add('hidden');
|
||||
// Clear form
|
||||
const idInput = document.getElementById('newIssueId');
|
||||
const titleInput = document.getElementById('newIssueTitle');
|
||||
const contextInput = document.getElementById('newIssueContext');
|
||||
const prioritySelect = document.getElementById('newIssuePriority');
|
||||
if (idInput) idInput.value = '';
|
||||
if (titleInput) titleInput.value = '';
|
||||
if (contextInput) contextInput.value = '';
|
||||
if (prioritySelect) prioritySelect.value = '3';
|
||||
}
|
||||
}
|
||||
|
||||
async function createIssue() {
|
||||
const idInput = document.getElementById('newIssueId');
|
||||
const titleInput = document.getElementById('newIssueTitle');
|
||||
const contextInput = document.getElementById('newIssueContext');
|
||||
const prioritySelect = document.getElementById('newIssuePriority');
|
||||
|
||||
const issueId = idInput?.value?.trim();
|
||||
const title = titleInput?.value?.trim();
|
||||
const context = contextInput?.value?.trim();
|
||||
const priority = parseInt(prioritySelect?.value || '3');
|
||||
|
||||
if (!issueId) {
|
||||
showNotification(t('issues.idRequired') || 'Issue ID is required', 'error');
|
||||
idInput?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!title) {
|
||||
showNotification(t('issues.titleRequired') || 'Title is required', 'error');
|
||||
titleInput?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/issues?path=' + encodeURIComponent(projectPath), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
id: issueId,
|
||||
title: title,
|
||||
context: context,
|
||||
priority: priority,
|
||||
source: 'dashboard'
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok || result.error) {
|
||||
showNotification(result.error || 'Failed to create issue', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
showNotification(t('issues.created') || 'Issue created successfully', 'success');
|
||||
hideCreateIssueModal();
|
||||
|
||||
// Reload data and refresh view
|
||||
await loadIssueData();
|
||||
renderIssueView();
|
||||
} catch (err) {
|
||||
console.error('Failed to create issue:', err);
|
||||
showNotification('Failed to create issue', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Delete Issue ==========
|
||||
async function deleteIssue(issueId) {
|
||||
if (!confirm(t('issues.confirmDelete') || 'Are you sure you want to delete this issue?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/issues/' + encodeURIComponent(issueId) + '?path=' + encodeURIComponent(projectPath), {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to delete');
|
||||
|
||||
showNotification(t('issues.deleted') || 'Issue deleted', 'success');
|
||||
closeIssueDetail();
|
||||
|
||||
// Reload data and refresh view
|
||||
await loadIssueData();
|
||||
renderIssueView();
|
||||
} catch (err) {
|
||||
showNotification('Failed to delete issue', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user