@autocode-cli/autocode 0.0.44 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +5 -5
- package/LICENSE.fr.md +203 -0
- package/README.md +57 -20
- package/dist/cli/commands/init.js +1 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/parser.d.ts +1 -1
- package/dist/cli/parser.d.ts.map +1 -1
- package/dist/cli/parser.js +5 -4
- package/dist/cli/parser.js.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/server/api.d.ts.map +1 -1
- package/dist/server/api.js +47 -7
- package/dist/server/api.js.map +1 -1
- package/dist/server/dashboard/index.d.ts +10 -0
- package/dist/server/dashboard/index.d.ts.map +1 -0
- package/dist/server/dashboard/index.js +16 -0
- package/dist/server/dashboard/index.js.map +1 -0
- package/dist/server/dashboard/pages/column-edit.d.ts +8 -0
- package/dist/server/dashboard/pages/column-edit.d.ts.map +1 -0
- package/dist/server/dashboard/pages/column-edit.js +303 -0
- package/dist/server/dashboard/pages/column-edit.js.map +1 -0
- package/dist/server/dashboard/pages/column-prompt.d.ts +8 -0
- package/dist/server/dashboard/pages/column-prompt.d.ts.map +1 -0
- package/dist/server/dashboard/pages/column-prompt.js +228 -0
- package/dist/server/dashboard/pages/column-prompt.js.map +1 -0
- package/dist/server/dashboard/pages/column-terminal.d.ts +8 -0
- package/dist/server/dashboard/pages/column-terminal.d.ts.map +1 -0
- package/dist/server/dashboard/pages/column-terminal.js +529 -0
- package/dist/server/dashboard/pages/column-terminal.js.map +1 -0
- package/dist/server/dashboard/pages/index.d.ts +12 -0
- package/dist/server/dashboard/pages/index.d.ts.map +1 -0
- package/dist/server/dashboard/pages/index.js +12 -0
- package/dist/server/dashboard/pages/index.js.map +1 -0
- package/dist/server/dashboard/pages/main-dashboard.d.ts +11 -0
- package/dist/server/dashboard/pages/main-dashboard.d.ts.map +1 -0
- package/dist/server/dashboard/pages/main-dashboard.js +209 -0
- package/dist/server/dashboard/pages/main-dashboard.js.map +1 -0
- package/dist/server/dashboard/pages/shared.d.ts +8 -0
- package/dist/server/dashboard/pages/shared.d.ts.map +1 -0
- package/dist/server/dashboard/pages/shared.js +58 -0
- package/dist/server/dashboard/pages/shared.js.map +1 -0
- package/dist/server/dashboard/pages/ticket-view.d.ts +8 -0
- package/dist/server/dashboard/pages/ticket-view.d.ts.map +1 -0
- package/dist/server/dashboard/pages/ticket-view.js +1364 -0
- package/dist/server/dashboard/pages/ticket-view.js.map +1 -0
- package/dist/server/dashboard/scripts/index.d.ts +11 -0
- package/dist/server/dashboard/scripts/index.d.ts.map +1 -0
- package/dist/server/dashboard/scripts/index.js +1325 -0
- package/dist/server/dashboard/scripts/index.js.map +1 -0
- package/dist/server/dashboard/styles/base.d.ts +5 -0
- package/dist/server/dashboard/styles/base.d.ts.map +1 -0
- package/dist/server/dashboard/styles/base.js +110 -0
- package/dist/server/dashboard/styles/base.js.map +1 -0
- package/dist/server/dashboard/styles/board.d.ts +5 -0
- package/dist/server/dashboard/styles/board.d.ts.map +1 -0
- package/dist/server/dashboard/styles/board.js +168 -0
- package/dist/server/dashboard/styles/board.js.map +1 -0
- package/dist/server/dashboard/styles/comments.d.ts +5 -0
- package/dist/server/dashboard/styles/comments.d.ts.map +1 -0
- package/dist/server/dashboard/styles/comments.js +249 -0
- package/dist/server/dashboard/styles/comments.js.map +1 -0
- package/dist/server/dashboard/styles/components.d.ts +5 -0
- package/dist/server/dashboard/styles/components.d.ts.map +1 -0
- package/dist/server/dashboard/styles/components.js +190 -0
- package/dist/server/dashboard/styles/components.js.map +1 -0
- package/dist/server/dashboard/styles/footer.d.ts +5 -0
- package/dist/server/dashboard/styles/footer.d.ts.map +1 -0
- package/dist/server/dashboard/styles/footer.js +32 -0
- package/dist/server/dashboard/styles/footer.js.map +1 -0
- package/dist/server/dashboard/styles/index.d.ts +8 -0
- package/dist/server/dashboard/styles/index.d.ts.map +1 -0
- package/dist/server/dashboard/styles/index.js +27 -0
- package/dist/server/dashboard/styles/index.js.map +1 -0
- package/dist/server/dashboard/styles/logs.d.ts +5 -0
- package/dist/server/dashboard/styles/logs.d.ts.map +1 -0
- package/dist/server/dashboard/styles/logs.js +89 -0
- package/dist/server/dashboard/styles/logs.js.map +1 -0
- package/dist/server/dashboard/styles/notifications.d.ts +5 -0
- package/dist/server/dashboard/styles/notifications.d.ts.map +1 -0
- package/dist/server/dashboard/styles/notifications.js +51 -0
- package/dist/server/dashboard/styles/notifications.js.map +1 -0
- package/dist/server/dashboard/styles/variables.d.ts +5 -0
- package/dist/server/dashboard/styles/variables.d.ts.map +1 -0
- package/dist/server/dashboard/styles/variables.js +29 -0
- package/dist/server/dashboard/styles/variables.js.map +1 -0
- package/dist/server/dashboard/utils.d.ts +8 -0
- package/dist/server/dashboard/utils.d.ts.map +1 -0
- package/dist/server/dashboard/utils.js +14 -0
- package/dist/server/dashboard/utils.js.map +1 -0
- package/dist/server/dashboard.d.ts +5 -21
- package/dist/server/dashboard.d.ts.map +1 -1
- package/dist/server/dashboard.js +5 -4945
- package/dist/server/dashboard.js.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +51 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/websocket.d.ts +12 -0
- package/dist/server/websocket.d.ts.map +1 -1
- package/dist/server/websocket.js +19 -0
- package/dist/server/websocket.js.map +1 -1
- package/dist/services/claude.d.ts.map +1 -1
- package/dist/services/claude.js +4 -1
- package/dist/services/claude.js.map +1 -1
- package/dist/utils/config.d.ts +29 -21
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +13 -11
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/version-check.d.ts +26 -0
- package/dist/utils/version-check.d.ts.map +1 -0
- package/dist/utils/version-check.js +234 -0
- package/dist/utils/version-check.js.map +1 -0
- package/package.json +2 -2
- package/templates/columns/00_backlog.en.md +1 -1
- package/templates/columns/00_backlog.fr.md +1 -1
- package/templates/columns/01_ready.en.md +1 -1
- package/templates/columns/01_ready.fr.md +1 -1
- package/templates/columns/02_in-progress.en.md +1 -1
- package/templates/columns/02_in-progress.fr.md +1 -1
- package/templates/columns/{07_testing-playwright.en.md → 03_testing-playwright.en.md} +1 -1
- package/templates/columns/{07_testing-playwright.fr.md → 03_testing-playwright.fr.md} +1 -1
- package/templates/columns/{08_testing-cypress.en.md → 04_testing-cypress.en.md} +1 -1
- package/templates/columns/{08_testing-cypress.fr.md → 04_testing-cypress.fr.md} +1 -1
- package/templates/columns/{03_review-best-practices.en.md → 05_review-best-practices.en.md} +1 -1
- package/templates/columns/{03_review-best-practices.fr.md → 05_review-best-practices.fr.md} +1 -1
- package/templates/columns/{04_review-no-duplication.en.md → 06_review-no-duplication.en.md} +1 -1
- package/templates/columns/{04_review-no-duplication.fr.md → 06_review-no-duplication.fr.md} +1 -1
- package/templates/columns/{05_review-consistency.en.md → 07_review-consistency.en.md} +1 -1
- package/templates/columns/{05_review-consistency.fr.md → 07_review-consistency.fr.md} +1 -1
- package/templates/columns/{06_review-security.en.md → 08_review-security.en.md} +1 -1
- package/templates/columns/{06_review-security.fr.md → 08_review-security.fr.md} +1 -1
- package/templates/columns/09_retest-playwright.en.md +30 -0
- package/templates/columns/09_retest-playwright.fr.md +30 -0
- package/templates/columns/10_retest-cypress.en.md +29 -0
- package/templates/columns/10_retest-cypress.fr.md +29 -0
- package/templates/columns/{09_update-docs.en.md → 11_update-docs.en.md} +1 -1
- package/templates/columns/{09_update-docs.fr.md → 11_update-docs.fr.md} +1 -1
- package/templates/columns/{10_deploy-staging.en.md → 12_deploy-staging.en.md} +1 -1
- package/templates/columns/{10_deploy-staging.fr.md → 12_deploy-staging.fr.md} +1 -1
- package/templates/columns/{11_validate-staging.en.md → 13_validate-staging.en.md} +1 -1
- package/templates/columns/{11_validate-staging.fr.md → 13_validate-staging.fr.md} +1 -1
- package/templates/columns/{12_done.en.md → 14_done.en.md} +1 -1
- package/templates/columns/{12_done.fr.md → 14_done.fr.md} +1 -1
|
@@ -0,0 +1,1325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard client-side JavaScript
|
|
3
|
+
*
|
|
4
|
+
* This file contains all interactive functionality for the dashboard.
|
|
5
|
+
* The code is kept in a single function to maintain shared state and scope.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Get all JavaScript for dashboard interactivity
|
|
9
|
+
*/
|
|
10
|
+
export function getScript() {
|
|
11
|
+
return `
|
|
12
|
+
// ========================================
|
|
13
|
+
// i18n TRANSLATIONS
|
|
14
|
+
// ========================================
|
|
15
|
+
const translations = {
|
|
16
|
+
en: {
|
|
17
|
+
// Filter
|
|
18
|
+
'filter.allPriorities': 'All priorities',
|
|
19
|
+
'filter.search': 'Search...',
|
|
20
|
+
// Buttons
|
|
21
|
+
'btn.newTicket': '+ New ticket',
|
|
22
|
+
'btn.createTicket': 'Create ticket',
|
|
23
|
+
'btn.update': 'Update',
|
|
24
|
+
'btn.cancel': 'Cancel',
|
|
25
|
+
'btn.archive': 'Archive',
|
|
26
|
+
'btn.next': 'Next',
|
|
27
|
+
'btn.add': 'Add',
|
|
28
|
+
'btn.edit': 'Edit',
|
|
29
|
+
'btn.save': 'Save',
|
|
30
|
+
'btn.reload': 'Reload',
|
|
31
|
+
// Modal
|
|
32
|
+
'modal.newTicket': 'New ticket',
|
|
33
|
+
'modal.editTicket': 'Edit',
|
|
34
|
+
'modal.title': 'Title *',
|
|
35
|
+
'modal.titlePlaceholder': 'E.g.: Fix the login bug',
|
|
36
|
+
'modal.description': 'Description',
|
|
37
|
+
'modal.descriptionPlaceholder': 'Describe the context and details...',
|
|
38
|
+
'modal.priority': 'Priority',
|
|
39
|
+
'modal.releaseType': 'Release type',
|
|
40
|
+
'modal.labels': 'Labels',
|
|
41
|
+
'modal.selectLabel': 'Select a label...',
|
|
42
|
+
'modal.acceptanceCriteria': 'Acceptance criteria',
|
|
43
|
+
'modal.addCriteria': 'Add criteria',
|
|
44
|
+
'modal.attachments': 'Attachments',
|
|
45
|
+
'modal.addAttachment': 'Add file',
|
|
46
|
+
'modal.noAttachments': 'No attachments',
|
|
47
|
+
'modal.comments': 'Comments',
|
|
48
|
+
'modal.noComments': 'No comments',
|
|
49
|
+
'modal.addCommentPlaceholder': 'Add a comment...',
|
|
50
|
+
'modal.claudeTerminal': 'Claude Terminal',
|
|
51
|
+
// Priority
|
|
52
|
+
'priority.p0': 'P0 - Critical',
|
|
53
|
+
'priority.p1': 'P1 - High',
|
|
54
|
+
'priority.p2': 'P2 - Normal',
|
|
55
|
+
'priority.p3': 'P3 - Low',
|
|
56
|
+
// Semver
|
|
57
|
+
'semver.patch': 'Patch (x.x.X) - Bug fix',
|
|
58
|
+
'semver.minor': 'Minor (x.X.0) - Feature',
|
|
59
|
+
'semver.major': 'Major (X.0.0) - Breaking',
|
|
60
|
+
'semver.none': 'No deployment',
|
|
61
|
+
// Status
|
|
62
|
+
'status.waiting': 'Waiting',
|
|
63
|
+
'status.processing': 'Running...',
|
|
64
|
+
'status.completed': 'Completed',
|
|
65
|
+
'status.failed': 'Failed',
|
|
66
|
+
'status.connected': 'Connected',
|
|
67
|
+
// Stats
|
|
68
|
+
'stats.total': 'Total',
|
|
69
|
+
// Board
|
|
70
|
+
'board.empty': 'Empty',
|
|
71
|
+
// Action modal
|
|
72
|
+
'action.instructions': 'Instructions',
|
|
73
|
+
'action.noInstructions': 'No instructions available',
|
|
74
|
+
'action.noFile': 'No ACTION file. Click "Edit" to create it.',
|
|
75
|
+
'action.modifiedOn': 'Modified on',
|
|
76
|
+
// Notifications
|
|
77
|
+
'notify.titleRequired': 'Title required',
|
|
78
|
+
'notify.titleMandatory': 'Title is mandatory',
|
|
79
|
+
'notify.ticketUpdated': 'Ticket updated',
|
|
80
|
+
'notify.ticketCreated': 'Ticket created',
|
|
81
|
+
'notify.ticketAdvanced': 'Ticket advanced',
|
|
82
|
+
'notify.ticketArchived': 'Ticket archived',
|
|
83
|
+
'notify.ticketMoved': 'moved',
|
|
84
|
+
'notify.moveTo': 'To',
|
|
85
|
+
'notify.commentAdded': 'Comment added',
|
|
86
|
+
'notify.actionUpdated': 'updated',
|
|
87
|
+
'notify.error': 'Error',
|
|
88
|
+
'notify.unableToSave': 'Unable to save',
|
|
89
|
+
'notify.loadingError': 'Loading error',
|
|
90
|
+
'notify.claudeStarted': 'Claude started',
|
|
91
|
+
'notify.claudeFinished': 'Claude finished',
|
|
92
|
+
'notify.claudeFailed': 'Claude failed',
|
|
93
|
+
'notify.processingSuccess': 'Processing successful',
|
|
94
|
+
'notify.checkLogs': 'Check logs',
|
|
95
|
+
// Confirm
|
|
96
|
+
'confirm.archive': 'Archive',
|
|
97
|
+
// Button states
|
|
98
|
+
'btn.updating': 'Updating...',
|
|
99
|
+
'btn.creating': 'Creating...',
|
|
100
|
+
'btn.moving': 'Moving...',
|
|
101
|
+
'btn.archiving': 'Archiving...',
|
|
102
|
+
'btn.sending': 'Sending...',
|
|
103
|
+
'btn.saving': 'Saving...',
|
|
104
|
+
},
|
|
105
|
+
fr: {
|
|
106
|
+
// Filter
|
|
107
|
+
'filter.allPriorities': 'Toutes priorités',
|
|
108
|
+
'filter.search': 'Rechercher...',
|
|
109
|
+
// Buttons
|
|
110
|
+
'btn.newTicket': '+ Nouveau ticket',
|
|
111
|
+
'btn.createTicket': 'Créer le ticket',
|
|
112
|
+
'btn.update': 'Mettre à jour',
|
|
113
|
+
'btn.cancel': 'Annuler',
|
|
114
|
+
'btn.archive': 'Archiver',
|
|
115
|
+
'btn.next': 'Suivant',
|
|
116
|
+
'btn.add': 'Ajouter',
|
|
117
|
+
'btn.edit': 'Modifier',
|
|
118
|
+
'btn.save': 'Enregistrer',
|
|
119
|
+
'btn.reload': 'Recharger',
|
|
120
|
+
// Modal
|
|
121
|
+
'modal.newTicket': 'Nouveau ticket',
|
|
122
|
+
'modal.editTicket': 'Modifier',
|
|
123
|
+
'modal.title': 'Titre *',
|
|
124
|
+
'modal.titlePlaceholder': 'Ex: Corriger le bug de connexion',
|
|
125
|
+
'modal.description': 'Description',
|
|
126
|
+
'modal.descriptionPlaceholder': 'Décrivez le contexte et les détails...',
|
|
127
|
+
'modal.priority': 'Priorité',
|
|
128
|
+
'modal.releaseType': 'Type de release',
|
|
129
|
+
'modal.labels': 'Labels',
|
|
130
|
+
'modal.selectLabel': 'Sélectionner un label...',
|
|
131
|
+
'modal.acceptanceCriteria': 'Critères d\\'acceptation',
|
|
132
|
+
'modal.addCriteria': 'Ajouter un critère',
|
|
133
|
+
'modal.attachments': 'Pièces jointes',
|
|
134
|
+
'modal.addAttachment': 'Ajouter un fichier',
|
|
135
|
+
'modal.noAttachments': 'Aucune pièce jointe',
|
|
136
|
+
'modal.comments': 'Commentaires',
|
|
137
|
+
'modal.noComments': 'Aucun commentaire',
|
|
138
|
+
'modal.addCommentPlaceholder': 'Ajouter un commentaire...',
|
|
139
|
+
'modal.claudeTerminal': 'Terminal Claude',
|
|
140
|
+
// Priority
|
|
141
|
+
'priority.p0': 'P0 - Critique',
|
|
142
|
+
'priority.p1': 'P1 - Haute',
|
|
143
|
+
'priority.p2': 'P2 - Normale',
|
|
144
|
+
'priority.p3': 'P3 - Basse',
|
|
145
|
+
// Semver
|
|
146
|
+
'semver.patch': 'Patch (x.x.X) - Bug fix',
|
|
147
|
+
'semver.minor': 'Minor (x.X.0) - Fonctionnalité',
|
|
148
|
+
'semver.major': 'Major (X.0.0) - Breaking',
|
|
149
|
+
'semver.none': 'Aucun déploiement',
|
|
150
|
+
// Status
|
|
151
|
+
'status.waiting': 'En attente',
|
|
152
|
+
'status.processing': 'En cours...',
|
|
153
|
+
'status.completed': 'Terminé',
|
|
154
|
+
'status.failed': 'Échoué',
|
|
155
|
+
'status.connected': 'Connecté',
|
|
156
|
+
// Stats
|
|
157
|
+
'stats.total': 'Total',
|
|
158
|
+
// Board
|
|
159
|
+
'board.empty': 'Vide',
|
|
160
|
+
// Action modal
|
|
161
|
+
'action.instructions': 'Instructions',
|
|
162
|
+
'action.noInstructions': 'Aucune instruction disponible',
|
|
163
|
+
'action.noFile': 'Aucun fichier ACTION. Cliquez sur "Modifier" pour le créer.',
|
|
164
|
+
'action.modifiedOn': 'Modifié le',
|
|
165
|
+
// Notifications
|
|
166
|
+
'notify.titleRequired': 'Titre requis',
|
|
167
|
+
'notify.titleMandatory': 'Le titre est obligatoire',
|
|
168
|
+
'notify.ticketUpdated': 'Ticket mis à jour',
|
|
169
|
+
'notify.ticketCreated': 'Ticket créé',
|
|
170
|
+
'notify.ticketAdvanced': 'Ticket avancé',
|
|
171
|
+
'notify.ticketArchived': 'Ticket archivé',
|
|
172
|
+
'notify.ticketMoved': 'déplacé',
|
|
173
|
+
'notify.moveTo': 'Vers',
|
|
174
|
+
'notify.commentAdded': 'Commentaire ajouté',
|
|
175
|
+
'notify.actionUpdated': 'mis à jour',
|
|
176
|
+
'notify.error': 'Erreur',
|
|
177
|
+
'notify.unableToSave': 'Impossible de sauvegarder',
|
|
178
|
+
'notify.loadingError': 'Erreur de chargement',
|
|
179
|
+
'notify.claudeStarted': 'Claude démarré',
|
|
180
|
+
'notify.claudeFinished': 'Claude terminé',
|
|
181
|
+
'notify.claudeFailed': 'Claude échoué',
|
|
182
|
+
'notify.processingSuccess': 'Traitement réussi',
|
|
183
|
+
'notify.checkLogs': 'Voir les logs',
|
|
184
|
+
// Confirm
|
|
185
|
+
'confirm.archive': 'Archiver',
|
|
186
|
+
// Button states
|
|
187
|
+
'btn.updating': 'Mise à jour...',
|
|
188
|
+
'btn.creating': 'Création...',
|
|
189
|
+
'btn.moving': 'Déplacement...',
|
|
190
|
+
'btn.archiving': 'Archivage...',
|
|
191
|
+
'btn.sending': 'Envoi...',
|
|
192
|
+
'btn.saving': 'Sauvegarde...',
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
let currentLang = localStorage.getItem('autocode-lang') || 'fr';
|
|
197
|
+
|
|
198
|
+
function t(key) {
|
|
199
|
+
return translations[currentLang][key] || translations['en'][key] || key;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function switchLanguage(lang) {
|
|
203
|
+
currentLang = lang;
|
|
204
|
+
currentActionLang = lang;
|
|
205
|
+
localStorage.setItem('autocode-lang', lang);
|
|
206
|
+
document.documentElement.lang = lang;
|
|
207
|
+
|
|
208
|
+
// Update lang switcher buttons
|
|
209
|
+
document.querySelectorAll('.lang-switcher .lang-btn').forEach(btn => {
|
|
210
|
+
btn.classList.toggle('active', btn.dataset.lang === lang);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Update all elements with data-i18n
|
|
214
|
+
document.querySelectorAll('[data-i18n]').forEach(el => {
|
|
215
|
+
const key = el.getAttribute('data-i18n');
|
|
216
|
+
if (translations[lang] && translations[lang][key]) {
|
|
217
|
+
el.textContent = translations[lang][key];
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Update placeholders
|
|
222
|
+
document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
|
|
223
|
+
const key = el.getAttribute('data-i18n-placeholder');
|
|
224
|
+
if (translations[lang] && translations[lang][key]) {
|
|
225
|
+
el.placeholder = translations[lang][key];
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Update select options
|
|
230
|
+
document.querySelectorAll('select option[data-i18n]').forEach(el => {
|
|
231
|
+
const key = el.getAttribute('data-i18n');
|
|
232
|
+
if (translations[lang] && translations[lang][key]) {
|
|
233
|
+
el.textContent = translations[lang][key];
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Re-render board to update dynamic content
|
|
238
|
+
render();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ========================================
|
|
242
|
+
// STATE
|
|
243
|
+
// ========================================
|
|
244
|
+
let filterPriority = '';
|
|
245
|
+
let filterSearch = '';
|
|
246
|
+
let selectedLabels = [];
|
|
247
|
+
let criteriaCount = 0;
|
|
248
|
+
let editingKey = null;
|
|
249
|
+
let currentComments = [];
|
|
250
|
+
let contextMenuTicket = null;
|
|
251
|
+
let draggedTicket = null;
|
|
252
|
+
let draggedFromColumn = null;
|
|
253
|
+
let currentActionSlug = null;
|
|
254
|
+
let currentActionLang = localStorage.getItem('autocode-lang') || 'fr';
|
|
255
|
+
let originalActionContent = '';
|
|
256
|
+
let claudeProcessingTickets = new Set();
|
|
257
|
+
|
|
258
|
+
// Initialize language switcher on page load
|
|
259
|
+
(function initLangSwitcher() {
|
|
260
|
+
document.querySelectorAll('.lang-switcher .lang-btn').forEach(btn => {
|
|
261
|
+
btn.addEventListener('click', () => switchLanguage(btn.dataset.lang));
|
|
262
|
+
});
|
|
263
|
+
switchLanguage(currentLang);
|
|
264
|
+
})();
|
|
265
|
+
|
|
266
|
+
// ========================================
|
|
267
|
+
// NOTIFICATIONS
|
|
268
|
+
// ========================================
|
|
269
|
+
function showNotification(type, title, message, duration = 5000) {
|
|
270
|
+
const container = document.getElementById('notifications');
|
|
271
|
+
const id = 'notif-' + Date.now();
|
|
272
|
+
const icons = { info: '\\u{1F535}', success: '\\u2705', warning: '\\u26A0\\uFE0F', error: '\\u274C', claude: '\\u{1F916}' };
|
|
273
|
+
|
|
274
|
+
const notif = document.createElement('div');
|
|
275
|
+
notif.className = 'notification ' + (type === 'claude' ? 'info' : type);
|
|
276
|
+
notif.id = id;
|
|
277
|
+
notif.innerHTML = '<span class="notification-icon">' + (icons[type] || icons.info) + '</span>' +
|
|
278
|
+
'<div class="notification-content"><div class="notification-title">' + title + '</div>' +
|
|
279
|
+
(message ? '<div class="notification-message">' + message + '</div>' : '') + '</div>' +
|
|
280
|
+
'<button class="notification-close" onclick="closeNotification(\\'' + id + '\\')">×</button>';
|
|
281
|
+
|
|
282
|
+
container.appendChild(notif);
|
|
283
|
+
setTimeout(() => closeNotification(id), duration);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function closeNotification(id) {
|
|
287
|
+
const notif = document.getElementById(id);
|
|
288
|
+
if (notif) notif.remove();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ========================================
|
|
292
|
+
// RENDER
|
|
293
|
+
// ========================================
|
|
294
|
+
function render() {
|
|
295
|
+
const board = document.getElementById('board');
|
|
296
|
+
board.innerHTML = '';
|
|
297
|
+
let stats = { total: 0, byPriority: { P0: 0, P1: 0, P2: 0, P3: 0 } };
|
|
298
|
+
|
|
299
|
+
COLUMNS.forEach(col => {
|
|
300
|
+
let tickets = TICKETS.filter(t => t.column_slug === col.slug);
|
|
301
|
+
if (filterPriority) tickets = tickets.filter(t => t.priority === filterPriority);
|
|
302
|
+
if (filterSearch) {
|
|
303
|
+
const s = filterSearch.toLowerCase();
|
|
304
|
+
tickets = tickets.filter(t => t.title.toLowerCase().includes(s) || t.key.toLowerCase().includes(s));
|
|
305
|
+
}
|
|
306
|
+
tickets.sort((a, b) => {
|
|
307
|
+
const p = { P0: 0, P1: 1, P2: 2, P3: 3 };
|
|
308
|
+
return p[a.priority] - p[b.priority];
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
stats.total += tickets.length;
|
|
312
|
+
tickets.forEach(t => stats.byPriority[t.priority] = (stats.byPriority[t.priority] || 0) + 1);
|
|
313
|
+
|
|
314
|
+
const div = document.createElement('div');
|
|
315
|
+
div.className = 'column';
|
|
316
|
+
div.ondragover = onDragOver;
|
|
317
|
+
div.ondragenter = onDragEnter;
|
|
318
|
+
div.ondragleave = onDragLeave;
|
|
319
|
+
div.ondrop = (e) => onDrop(e, col.slug, col.name);
|
|
320
|
+
|
|
321
|
+
div.innerHTML = '<div class="column-header">' +
|
|
322
|
+
'<span class="column-title">' + col.name + '</span>' +
|
|
323
|
+
'<div class="column-header-actions">' +
|
|
324
|
+
'<a class="btn-action" title="ACTION.md" href="/column/' + col.slug + '/edit">\\u{1F4D8}</a>' +
|
|
325
|
+
'<span class="column-count">' + tickets.length + '</span>' +
|
|
326
|
+
'</div></div>' +
|
|
327
|
+
'<div class="column-body">' +
|
|
328
|
+
(tickets.length ? tickets.map(tk => {
|
|
329
|
+
const isProcessing = claudeProcessingTickets.has(tk.key);
|
|
330
|
+
return '<div class="ticket' + (isProcessing ? ' claude-processing' : '') + '" draggable="true" ' +
|
|
331
|
+
'onclick="onTicketClick(\\'' + tk.key + '\\')" ' +
|
|
332
|
+
'oncontextmenu="showContextMenu(event,\\'' + tk.key + '\\')" ' +
|
|
333
|
+
'ondragstart="onDragStart(event,\\'' + tk.key + '\\',\\'' + col.slug + '\\')" ' +
|
|
334
|
+
'ondragend="onDragEnd(event)">' +
|
|
335
|
+
'<div class="ticket-key">' + tk.key + '</div>' +
|
|
336
|
+
'<div class="ticket-title">' + escapeHtml(tk.title) + '</div>' +
|
|
337
|
+
'<div class="ticket-meta"><span class="priority ' + tk.priority + '">' + tk.priority + '</span></div>' +
|
|
338
|
+
(isProcessing ? '<div class="ticket-claude-indicator"><span class="claude-dot"></span>\\u{1F916} ' + t('status.processing') + '</div>' : '') +
|
|
339
|
+
'</div>';
|
|
340
|
+
}).join('') : '<div class="empty">' + t('board.empty') + '</div>') +
|
|
341
|
+
'</div>';
|
|
342
|
+
|
|
343
|
+
board.appendChild(div);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
document.getElementById('stats').innerHTML =
|
|
347
|
+
'<span class="stat connected">\\u{1F7E2} ' + t('status.connected') + '</span>' +
|
|
348
|
+
'<span class="stat">' + t('stats.total') + ': ' + stats.total + '</span>' +
|
|
349
|
+
'<span class="stat P0">P0: ' + (stats.byPriority.P0 || 0) + '</span>' +
|
|
350
|
+
'<span class="stat P1">P1: ' + (stats.byPriority.P1 || 0) + '</span>' +
|
|
351
|
+
'<span class="stat P2">P2: ' + (stats.byPriority.P2 || 0) + '</span>';
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function escapeHtml(text) {
|
|
355
|
+
const div = document.createElement('div');
|
|
356
|
+
div.textContent = text;
|
|
357
|
+
return div.innerHTML;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ========================================
|
|
361
|
+
// FILTERS
|
|
362
|
+
// ========================================
|
|
363
|
+
document.getElementById('filter-priority').onchange = e => { filterPriority = e.target.value; render(); };
|
|
364
|
+
document.getElementById('filter-search').oninput = e => { filterSearch = e.target.value.toLowerCase(); render(); };
|
|
365
|
+
|
|
366
|
+
// ========================================
|
|
367
|
+
// MODAL TICKET
|
|
368
|
+
// ========================================
|
|
369
|
+
function openModal(key = null) {
|
|
370
|
+
editingKey = key;
|
|
371
|
+
document.getElementById('modal-overlay').classList.add('active');
|
|
372
|
+
document.body.style.overflow = 'hidden';
|
|
373
|
+
|
|
374
|
+
const modalTitle = document.getElementById('modal-title');
|
|
375
|
+
const saveBtn = document.getElementById('btn-save');
|
|
376
|
+
const nextBtn = document.getElementById('btn-next');
|
|
377
|
+
const archiveBtn = document.getElementById('btn-archive');
|
|
378
|
+
const commentsSection = document.getElementById('comments-section');
|
|
379
|
+
const attachmentsSection = document.getElementById('attachments-section');
|
|
380
|
+
|
|
381
|
+
if (key) {
|
|
382
|
+
modalTitle.textContent = t('modal.editTicket') + ' ' + key;
|
|
383
|
+
saveBtn.textContent = t('btn.update');
|
|
384
|
+
nextBtn.style.display = 'inline-block';
|
|
385
|
+
archiveBtn.style.display = 'inline-block';
|
|
386
|
+
commentsSection.style.display = 'block';
|
|
387
|
+
attachmentsSection.style.display = 'block';
|
|
388
|
+
loadTicketForEdit(key);
|
|
389
|
+
} else {
|
|
390
|
+
modalTitle.textContent = t('modal.newTicket');
|
|
391
|
+
saveBtn.textContent = t('btn.createTicket');
|
|
392
|
+
nextBtn.style.display = 'none';
|
|
393
|
+
archiveBtn.style.display = 'none';
|
|
394
|
+
commentsSection.style.display = 'none';
|
|
395
|
+
attachmentsSection.style.display = 'none';
|
|
396
|
+
resetForm();
|
|
397
|
+
resetComments();
|
|
398
|
+
resetAttachments();
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function closeModal() {
|
|
403
|
+
document.getElementById('modal-overlay').classList.remove('active');
|
|
404
|
+
document.body.style.overflow = '';
|
|
405
|
+
editingKey = null;
|
|
406
|
+
resetForm();
|
|
407
|
+
resetClaudeLog();
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function resetForm() {
|
|
411
|
+
document.getElementById('ticket-title').value = '';
|
|
412
|
+
document.getElementById('ticket-description').value = '';
|
|
413
|
+
document.getElementById('ticket-priority').value = 'P2';
|
|
414
|
+
document.getElementById('ticket-semver').value = 'patch';
|
|
415
|
+
document.getElementById('ticket-labels').value = '';
|
|
416
|
+
selectedLabels = [];
|
|
417
|
+
criteriaCount = 0;
|
|
418
|
+
document.getElementById('selected-labels').innerHTML = '';
|
|
419
|
+
document.getElementById('criteria-list').innerHTML = '';
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async function loadTicketForEdit(key) {
|
|
423
|
+
try {
|
|
424
|
+
const res = await fetch('/api/tickets/' + key);
|
|
425
|
+
if (!res.ok) throw new Error('Ticket not found');
|
|
426
|
+
const json = await res.json();
|
|
427
|
+
if (!json.success || !json.data) throw new Error('Ticket not found');
|
|
428
|
+
const ticket = json.data;
|
|
429
|
+
|
|
430
|
+
document.getElementById('ticket-title').value = ticket.title || '';
|
|
431
|
+
document.getElementById('ticket-description').value = ticket.description || '';
|
|
432
|
+
document.getElementById('ticket-priority').value = ticket.priority || 'P2';
|
|
433
|
+
document.getElementById('ticket-semver').value = ticket.semver || 'patch';
|
|
434
|
+
|
|
435
|
+
selectedLabels = Array.isArray(ticket.labels) ? [...ticket.labels] : [];
|
|
436
|
+
renderLabels();
|
|
437
|
+
|
|
438
|
+
criteriaCount = 0;
|
|
439
|
+
document.getElementById('criteria-list').innerHTML = '';
|
|
440
|
+
if (Array.isArray(ticket.acceptance_criteria)) {
|
|
441
|
+
ticket.acceptance_criteria.forEach(c => addCriteria(c));
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
renderComments(ticket.comments || []);
|
|
445
|
+
loadAttachments(key);
|
|
446
|
+
fetchLog(key);
|
|
447
|
+
} catch (e) {
|
|
448
|
+
console.error('Error:', e);
|
|
449
|
+
showNotification('error', 'Error', e.message);
|
|
450
|
+
closeModal();
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async function saveTicket() {
|
|
455
|
+
const title = document.getElementById('ticket-title').value.trim();
|
|
456
|
+
const description = document.getElementById('ticket-description').value.trim();
|
|
457
|
+
const priority = document.getElementById('ticket-priority').value;
|
|
458
|
+
const semver = document.getElementById('ticket-semver').value;
|
|
459
|
+
const criteria = getCriteria();
|
|
460
|
+
|
|
461
|
+
if (!title) {
|
|
462
|
+
showNotification('warning', t('notify.titleRequired'), t('notify.titleMandatory'));
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const btn = document.getElementById('btn-save');
|
|
467
|
+
btn.disabled = true;
|
|
468
|
+
|
|
469
|
+
try {
|
|
470
|
+
if (editingKey) {
|
|
471
|
+
btn.textContent = t('btn.updating');
|
|
472
|
+
await fetch('/api/tickets/' + editingKey, {
|
|
473
|
+
method: 'PATCH',
|
|
474
|
+
headers: { 'Content-Type': 'application/json' },
|
|
475
|
+
body: JSON.stringify({ title, description, priority, semver, labels: selectedLabels, acceptance_criteria: criteria })
|
|
476
|
+
});
|
|
477
|
+
showNotification('success', t('notify.ticketUpdated'), editingKey);
|
|
478
|
+
} else {
|
|
479
|
+
btn.textContent = t('btn.creating');
|
|
480
|
+
const res = await fetch('/api/tickets', {
|
|
481
|
+
method: 'POST',
|
|
482
|
+
headers: { 'Content-Type': 'application/json' },
|
|
483
|
+
body: JSON.stringify({ title, priority, labels: selectedLabels, description, semver, acceptance_criteria: criteria })
|
|
484
|
+
});
|
|
485
|
+
const data = await res.json();
|
|
486
|
+
showNotification('success', t('notify.ticketCreated'), data.key || '');
|
|
487
|
+
}
|
|
488
|
+
closeModal();
|
|
489
|
+
loadTicketsFromAPI();
|
|
490
|
+
} catch (e) {
|
|
491
|
+
showNotification('error', t('notify.error'), e.message);
|
|
492
|
+
btn.disabled = false;
|
|
493
|
+
btn.textContent = editingKey ? t('btn.update') : t('btn.createTicket');
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
async function advanceTicket() {
|
|
498
|
+
if (!editingKey) return;
|
|
499
|
+
const btn = document.getElementById('btn-next');
|
|
500
|
+
btn.disabled = true;
|
|
501
|
+
btn.textContent = t('btn.moving');
|
|
502
|
+
try {
|
|
503
|
+
await fetch('/api/tickets/' + editingKey + '/next', {
|
|
504
|
+
method: 'POST',
|
|
505
|
+
headers: { 'Content-Type': 'application/json' },
|
|
506
|
+
body: JSON.stringify({ lang: currentLang })
|
|
507
|
+
});
|
|
508
|
+
showNotification('info', t('notify.ticketAdvanced'), editingKey);
|
|
509
|
+
closeModal();
|
|
510
|
+
loadTicketsFromAPI();
|
|
511
|
+
} catch (e) {
|
|
512
|
+
showNotification('error', t('notify.error'), e.message);
|
|
513
|
+
btn.disabled = false;
|
|
514
|
+
btn.textContent = t('btn.next');
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
async function archiveTicket() {
|
|
519
|
+
if (!editingKey) return;
|
|
520
|
+
if (!confirm(t('confirm.archive') + ' ' + editingKey + '?')) return;
|
|
521
|
+
|
|
522
|
+
const btn = document.getElementById('btn-archive');
|
|
523
|
+
btn.disabled = true;
|
|
524
|
+
btn.textContent = t('btn.archiving');
|
|
525
|
+
try {
|
|
526
|
+
const lastColumn = COLUMNS[COLUMNS.length - 1];
|
|
527
|
+
await fetch('/api/tickets/' + editingKey + '/move', {
|
|
528
|
+
method: 'POST',
|
|
529
|
+
headers: { 'Content-Type': 'application/json' },
|
|
530
|
+
body: JSON.stringify({ column: lastColumn.name, force: true, lang: currentLang })
|
|
531
|
+
});
|
|
532
|
+
showNotification('info', t('notify.ticketArchived'), editingKey);
|
|
533
|
+
closeModal();
|
|
534
|
+
loadTicketsFromAPI();
|
|
535
|
+
} catch (e) {
|
|
536
|
+
showNotification('error', t('notify.error'), e.message);
|
|
537
|
+
btn.disabled = false;
|
|
538
|
+
btn.textContent = t('btn.archive');
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// ========================================
|
|
543
|
+
// LABELS
|
|
544
|
+
// ========================================
|
|
545
|
+
function addLabel(select) {
|
|
546
|
+
const value = select.value;
|
|
547
|
+
if (value && !selectedLabels.includes(value)) {
|
|
548
|
+
selectedLabels.push(value);
|
|
549
|
+
renderLabels();
|
|
550
|
+
}
|
|
551
|
+
select.value = '';
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function removeLabel(label) {
|
|
555
|
+
selectedLabels = selectedLabels.filter(l => l !== label);
|
|
556
|
+
renderLabels();
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function renderLabels() {
|
|
560
|
+
const container = document.getElementById('selected-labels');
|
|
561
|
+
container.innerHTML = selectedLabels.map(label =>
|
|
562
|
+
'<span class="label-tag">' + label + '<span class="remove-label" onclick="removeLabel(\\'' + label + '\\')">×</span></span>'
|
|
563
|
+
).join('');
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// ========================================
|
|
567
|
+
// CRITERIA
|
|
568
|
+
// ========================================
|
|
569
|
+
function addCriteria(value = '') {
|
|
570
|
+
criteriaCount++;
|
|
571
|
+
const id = criteriaCount;
|
|
572
|
+
const container = document.getElementById('criteria-list');
|
|
573
|
+
const div = document.createElement('div');
|
|
574
|
+
div.className = 'criteria-item';
|
|
575
|
+
div.id = 'criteria-' + id;
|
|
576
|
+
div.innerHTML = '<input type="text" placeholder="Ex: L\\'utilisateur peut..." value="' + escapeHtml(value) + '">' +
|
|
577
|
+
'<button type="button" class="btn-remove" onclick="removeCriteria(' + id + ')">×</button>';
|
|
578
|
+
container.appendChild(div);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function removeCriteria(id) {
|
|
582
|
+
const el = document.getElementById('criteria-' + id);
|
|
583
|
+
if (el) el.remove();
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function getCriteria() {
|
|
587
|
+
const items = document.querySelectorAll('.criteria-item input');
|
|
588
|
+
return Array.from(items).map(i => i.value.trim()).filter(Boolean);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// ========================================
|
|
592
|
+
// COMMENTS
|
|
593
|
+
// ========================================
|
|
594
|
+
function resetComments() {
|
|
595
|
+
currentComments = [];
|
|
596
|
+
document.getElementById('comments-list').innerHTML = '<div class="no-comments">' + t('modal.noComments') + '</div>';
|
|
597
|
+
document.getElementById('comments-count').textContent = '0';
|
|
598
|
+
document.getElementById('new-comment').value = '';
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function renderComments(comments) {
|
|
602
|
+
currentComments = comments || [];
|
|
603
|
+
const list = document.getElementById('comments-list');
|
|
604
|
+
const count = document.getElementById('comments-count');
|
|
605
|
+
count.textContent = currentComments.length;
|
|
606
|
+
|
|
607
|
+
if (currentComments.length === 0) {
|
|
608
|
+
list.innerHTML = '<div class="no-comments">' + t('modal.noComments') + '</div>';
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const sorted = [...currentComments].sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
|
613
|
+
list.innerHTML = sorted.map((comment, index) => {
|
|
614
|
+
const date = new Date(comment.created_at);
|
|
615
|
+
const dateStr = date.toLocaleDateString('en-US', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' });
|
|
616
|
+
const source = comment.source || 'user';
|
|
617
|
+
const sourceBadge = source === 'claude'
|
|
618
|
+
? '<span class="comment-source claude">Claude</span>'
|
|
619
|
+
: '<span class="comment-source user">User</span>';
|
|
620
|
+
return '<div class="comment" id="comment-' + index + '">' +
|
|
621
|
+
'<div class="comment-meta" onclick="toggleComment(' + index + ')">' + sourceBadge + '<span class="comment-column">' + (comment.column || 'N/A') + '</span>' +
|
|
622
|
+
'<span class="comment-date">' + dateStr + '</span></div>' +
|
|
623
|
+
'<div class="comment-text">' + renderMarkdown(comment.text) + '</div></div>';
|
|
624
|
+
}).join('');
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function toggleComment(index) {
|
|
628
|
+
const comments = document.querySelectorAll('.comment');
|
|
629
|
+
comments.forEach((comment, i) => {
|
|
630
|
+
if (i === index) {
|
|
631
|
+
comment.classList.toggle('expanded');
|
|
632
|
+
} else {
|
|
633
|
+
comment.classList.remove('expanded');
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function renderMarkdown(text) {
|
|
639
|
+
if (!text) return '';
|
|
640
|
+
let html = escapeHtml(text);
|
|
641
|
+
html = html.replace(/^### (.+)$/gm, '<h4>$1</h4>');
|
|
642
|
+
html = html.replace(/^## (.+)$/gm, '<h3>$1</h3>');
|
|
643
|
+
html = html.replace(/^# (.+)$/gm, '<h2>$1</h2>');
|
|
644
|
+
html = html.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');
|
|
645
|
+
html = html.replace(/\\*(.+?)\\*/g, '<em>$1</em>');
|
|
646
|
+
html = html.replace(/\`([^\`]+)\`/g, '<code>$1</code>');
|
|
647
|
+
html = html.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href="$2" target="_blank">$1</a>');
|
|
648
|
+
html = html.replace(/^- (.+)$/gm, '<li>$1</li>');
|
|
649
|
+
html = html.replace(/(<li>.*<\\/li>\\n?)+/g, '<ul>$&</ul>');
|
|
650
|
+
html = html.replace(/\\n/g, '<br>');
|
|
651
|
+
return html;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
async function addComment() {
|
|
655
|
+
if (!editingKey) return;
|
|
656
|
+
const textarea = document.getElementById('new-comment');
|
|
657
|
+
const text = textarea.value.trim();
|
|
658
|
+
if (!text) return;
|
|
659
|
+
|
|
660
|
+
const btn = document.querySelector('.btn-comment');
|
|
661
|
+
btn.disabled = true;
|
|
662
|
+
btn.textContent = t('btn.sending');
|
|
663
|
+
|
|
664
|
+
try {
|
|
665
|
+
const res = await fetch('/api/tickets/' + editingKey + '/comments', {
|
|
666
|
+
method: 'POST',
|
|
667
|
+
headers: { 'Content-Type': 'application/json' },
|
|
668
|
+
body: JSON.stringify({ text })
|
|
669
|
+
});
|
|
670
|
+
const result = await res.json();
|
|
671
|
+
textarea.value = '';
|
|
672
|
+
if (result.success && result.data && result.data.comments) {
|
|
673
|
+
renderComments(result.data.comments);
|
|
674
|
+
}
|
|
675
|
+
showNotification('success', t('notify.commentAdded'), '');
|
|
676
|
+
} catch (e) {
|
|
677
|
+
showNotification('error', t('notify.error'), e.message);
|
|
678
|
+
} finally {
|
|
679
|
+
btn.disabled = false;
|
|
680
|
+
btn.textContent = t('btn.add');
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// ========================================
|
|
685
|
+
// ATTACHMENTS
|
|
686
|
+
// ========================================
|
|
687
|
+
let currentAttachments = [];
|
|
688
|
+
|
|
689
|
+
function resetAttachments() {
|
|
690
|
+
currentAttachments = [];
|
|
691
|
+
document.getElementById('attachments-list').innerHTML = '<div class="no-attachments">' + t('modal.noAttachments') + '</div>';
|
|
692
|
+
document.getElementById('attachments-count').textContent = '0';
|
|
693
|
+
document.getElementById('file-input').value = '';
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
async function loadAttachments(key) {
|
|
697
|
+
try {
|
|
698
|
+
const res = await fetch('/api/tickets/' + key + '/attachments');
|
|
699
|
+
const json = await res.json();
|
|
700
|
+
if (json.success) {
|
|
701
|
+
currentAttachments = json.data || [];
|
|
702
|
+
renderAttachments();
|
|
703
|
+
}
|
|
704
|
+
} catch (e) {
|
|
705
|
+
console.error('Error loading attachments:', e);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function renderAttachments() {
|
|
710
|
+
const list = document.getElementById('attachments-list');
|
|
711
|
+
const count = document.getElementById('attachments-count');
|
|
712
|
+
count.textContent = currentAttachments.length;
|
|
713
|
+
|
|
714
|
+
if (currentAttachments.length === 0) {
|
|
715
|
+
list.innerHTML = '<div class="no-attachments">' + t('modal.noAttachments') + '</div>';
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
list.innerHTML = currentAttachments.map(filename => {
|
|
720
|
+
const ext = filename.split('.').pop().toLowerCase();
|
|
721
|
+
const icon = getFileIcon(ext);
|
|
722
|
+
return '<div class="attachment-item">' +
|
|
723
|
+
'<span class="attachment-icon">' + icon + '</span>' +
|
|
724
|
+
'<span class="attachment-name" title="' + escapeHtml(filename) + '">' + escapeHtml(filename) + '</span>' +
|
|
725
|
+
'<button class="attachment-delete" onclick="deleteAttachment(\\'' + escapeHtml(filename) + '\\')" title="Delete">×</button>' +
|
|
726
|
+
'</div>';
|
|
727
|
+
}).join('');
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function getFileIcon(ext) {
|
|
731
|
+
const icons = {
|
|
732
|
+
pdf: '\\u{1F4C4}', doc: '\\u{1F4DD}', docx: '\\u{1F4DD}', txt: '\\u{1F4DD}',
|
|
733
|
+
png: '\\u{1F5BC}', jpg: '\\u{1F5BC}', jpeg: '\\u{1F5BC}', gif: '\\u{1F5BC}', svg: '\\u{1F5BC}', webp: '\\u{1F5BC}',
|
|
734
|
+
mp4: '\\u{1F3AC}', mov: '\\u{1F3AC}', avi: '\\u{1F3AC}',
|
|
735
|
+
mp3: '\\u{1F3B5}', wav: '\\u{1F3B5}',
|
|
736
|
+
zip: '\\u{1F4E6}', rar: '\\u{1F4E6}', tar: '\\u{1F4E6}', gz: '\\u{1F4E6}',
|
|
737
|
+
js: '\\u{1F4DC}', ts: '\\u{1F4DC}', py: '\\u{1F4DC}', json: '\\u{1F4DC}', md: '\\u{1F4DC}',
|
|
738
|
+
};
|
|
739
|
+
return icons[ext] || '\\u{1F4CE}';
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
async function uploadFiles(files) {
|
|
743
|
+
if (!editingKey || files.length === 0) return;
|
|
744
|
+
|
|
745
|
+
const formData = new FormData();
|
|
746
|
+
for (const file of files) {
|
|
747
|
+
formData.append('files', file, file.name);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
try {
|
|
751
|
+
const res = await fetch('/api/tickets/' + editingKey + '/attachments', {
|
|
752
|
+
method: 'POST',
|
|
753
|
+
body: formData
|
|
754
|
+
});
|
|
755
|
+
const json = await res.json();
|
|
756
|
+
if (json.success) {
|
|
757
|
+
showNotification('success', 'Files uploaded', json.data.join(', '));
|
|
758
|
+
loadAttachments(editingKey);
|
|
759
|
+
} else {
|
|
760
|
+
showNotification('error', 'Upload failed', json.error);
|
|
761
|
+
}
|
|
762
|
+
} catch (e) {
|
|
763
|
+
showNotification('error', 'Upload error', e.message);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
document.getElementById('file-input').value = '';
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
async function deleteAttachment(filename) {
|
|
770
|
+
if (!editingKey) return;
|
|
771
|
+
if (!confirm('Delete ' + filename + '?')) return;
|
|
772
|
+
|
|
773
|
+
try {
|
|
774
|
+
const res = await fetch('/api/tickets/' + editingKey + '/attachments/' + encodeURIComponent(filename), {
|
|
775
|
+
method: 'DELETE'
|
|
776
|
+
});
|
|
777
|
+
const json = await res.json();
|
|
778
|
+
if (json.success) {
|
|
779
|
+
showNotification('info', 'File deleted', filename);
|
|
780
|
+
loadAttachments(editingKey);
|
|
781
|
+
} else {
|
|
782
|
+
showNotification('error', 'Delete failed', json.error);
|
|
783
|
+
}
|
|
784
|
+
} catch (e) {
|
|
785
|
+
showNotification('error', 'Delete error', e.message);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// ========================================
|
|
790
|
+
// DRAG & DROP
|
|
791
|
+
// ========================================
|
|
792
|
+
function onDragStart(e, key, columnSlug) {
|
|
793
|
+
draggedTicket = key;
|
|
794
|
+
draggedFromColumn = columnSlug;
|
|
795
|
+
e.target.classList.add('dragging');
|
|
796
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
797
|
+
e.dataTransfer.setData('text/plain', key);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
function onDragEnd(e) {
|
|
801
|
+
e.target.classList.remove('dragging');
|
|
802
|
+
document.querySelectorAll('.column').forEach(c => c.classList.remove('drag-over'));
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function onDragOver(e) {
|
|
806
|
+
e.preventDefault();
|
|
807
|
+
e.dataTransfer.dropEffect = 'move';
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function onDragEnter(e) {
|
|
811
|
+
e.preventDefault();
|
|
812
|
+
const column = e.target.closest('.column');
|
|
813
|
+
if (column) column.classList.add('drag-over');
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function onDragLeave(e) {
|
|
817
|
+
const column = e.target.closest('.column');
|
|
818
|
+
if (column && !column.contains(e.relatedTarget)) {
|
|
819
|
+
column.classList.remove('drag-over');
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
async function onDrop(e, targetColumnSlug, targetColumnName) {
|
|
824
|
+
e.preventDefault();
|
|
825
|
+
document.querySelectorAll('.column').forEach(c => c.classList.remove('drag-over'));
|
|
826
|
+
|
|
827
|
+
if (!draggedTicket) return;
|
|
828
|
+
if (draggedFromColumn === targetColumnSlug) {
|
|
829
|
+
draggedTicket = null;
|
|
830
|
+
draggedFromColumn = null;
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
const key = draggedTicket;
|
|
835
|
+
draggedTicket = null;
|
|
836
|
+
draggedFromColumn = null;
|
|
837
|
+
|
|
838
|
+
try {
|
|
839
|
+
await fetch('/api/tickets/' + key + '/move', {
|
|
840
|
+
method: 'POST',
|
|
841
|
+
headers: { 'Content-Type': 'application/json' },
|
|
842
|
+
body: JSON.stringify({ column: targetColumnName, force: true, lang: currentLang })
|
|
843
|
+
});
|
|
844
|
+
showNotification('info', key + ' ' + t('notify.ticketMoved'), t('notify.moveTo') + ' "' + targetColumnName + '"');
|
|
845
|
+
loadTicketsFromAPI();
|
|
846
|
+
} catch (err) {
|
|
847
|
+
showNotification('error', t('notify.error'), err.message);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// ========================================
|
|
852
|
+
// CONTEXT MENU
|
|
853
|
+
// ========================================
|
|
854
|
+
function showContextMenu(e, key) {
|
|
855
|
+
e.preventDefault();
|
|
856
|
+
contextMenuTicket = key;
|
|
857
|
+
const menu = document.getElementById('context-menu');
|
|
858
|
+
menu.style.left = e.clientX + 'px';
|
|
859
|
+
menu.style.top = e.clientY + 'px';
|
|
860
|
+
menu.classList.add('active');
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function hideContextMenu() {
|
|
864
|
+
document.getElementById('context-menu').classList.remove('active');
|
|
865
|
+
contextMenuTicket = null;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
function editFromContext() {
|
|
869
|
+
if (contextMenuTicket) openModal(contextMenuTicket);
|
|
870
|
+
hideContextMenu();
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
async function archiveFromContext() {
|
|
874
|
+
if (!contextMenuTicket) return;
|
|
875
|
+
const key = contextMenuTicket;
|
|
876
|
+
hideContextMenu();
|
|
877
|
+
|
|
878
|
+
if (confirm(t('confirm.archive') + ' ' + key + '?')) {
|
|
879
|
+
try {
|
|
880
|
+
const lastColumn = COLUMNS[COLUMNS.length - 1];
|
|
881
|
+
await fetch('/api/tickets/' + key + '/move', {
|
|
882
|
+
method: 'POST',
|
|
883
|
+
headers: { 'Content-Type': 'application/json' },
|
|
884
|
+
body: JSON.stringify({ column: lastColumn.name, force: true, lang: currentLang })
|
|
885
|
+
});
|
|
886
|
+
showNotification('info', t('notify.ticketArchived'), key);
|
|
887
|
+
loadTicketsFromAPI();
|
|
888
|
+
} catch (err) {
|
|
889
|
+
showNotification('error', t('notify.error'), err.message);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
document.addEventListener('click', hideContextMenu);
|
|
895
|
+
|
|
896
|
+
// ========================================
|
|
897
|
+
// ACTION.md MODAL
|
|
898
|
+
// ========================================
|
|
899
|
+
function openActionModal(slug) {
|
|
900
|
+
currentActionSlug = slug;
|
|
901
|
+
const col = COLUMNS.find(c => c.slug === slug);
|
|
902
|
+
document.getElementById('action-modal-title').textContent = col?.name || slug;
|
|
903
|
+
document.getElementById('action-modal').classList.add('active');
|
|
904
|
+
document.body.style.overflow = 'hidden';
|
|
905
|
+
reloadActionContent();
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function closeActionModal() {
|
|
909
|
+
document.getElementById('action-modal').classList.remove('active');
|
|
910
|
+
document.body.style.overflow = '';
|
|
911
|
+
currentActionSlug = null;
|
|
912
|
+
originalActionContent = '';
|
|
913
|
+
setActionEditMode(false);
|
|
914
|
+
document.getElementById('action-content').value = '';
|
|
915
|
+
document.getElementById('action-empty').style.display = 'none';
|
|
916
|
+
document.getElementById('action-meta').textContent = '';
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
function setActionEditMode(isEdit) {
|
|
920
|
+
const textarea = document.getElementById('action-content');
|
|
921
|
+
const saveBtn = document.getElementById('action-save-btn');
|
|
922
|
+
const editBtn = document.getElementById('action-edit-btn');
|
|
923
|
+
textarea.readOnly = !isEdit;
|
|
924
|
+
saveBtn.disabled = !isEdit;
|
|
925
|
+
editBtn.style.display = isEdit ? 'none' : 'inline-block';
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
async function reloadActionContent() {
|
|
929
|
+
if (!currentActionSlug) return;
|
|
930
|
+
setActionEditMode(false);
|
|
931
|
+
const textarea = document.getElementById('action-content');
|
|
932
|
+
const empty = document.getElementById('action-empty');
|
|
933
|
+
const meta = document.getElementById('action-meta');
|
|
934
|
+
|
|
935
|
+
empty.style.display = 'none';
|
|
936
|
+
textarea.style.display = 'block';
|
|
937
|
+
textarea.value = '...';
|
|
938
|
+
|
|
939
|
+
try {
|
|
940
|
+
const res = await fetch('/api/columns/' + currentActionSlug + '/actions?lang=' + currentActionLang);
|
|
941
|
+
const data = res.ok ? await res.json() : {};
|
|
942
|
+
|
|
943
|
+
if (!res.ok || !data.success) {
|
|
944
|
+
if (res.status === 404) {
|
|
945
|
+
textarea.value = '';
|
|
946
|
+
textarea.style.display = 'none';
|
|
947
|
+
empty.textContent = t('action.noFile');
|
|
948
|
+
empty.style.display = 'block';
|
|
949
|
+
meta.textContent = '';
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
throw new Error(data.error || t('notify.loadingError'));
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
const actionData = data.data || {};
|
|
956
|
+
originalActionContent = actionData.content || '';
|
|
957
|
+
textarea.value = originalActionContent;
|
|
958
|
+
const updated = actionData.updated_at ? new Date(actionData.updated_at).toLocaleString(currentLang === 'fr' ? 'fr-FR' : 'en-US') : '';
|
|
959
|
+
meta.textContent = (actionData.path || '') + (updated ? ' - ' + t('action.modifiedOn') + ' ' + updated : '');
|
|
960
|
+
} catch (e) {
|
|
961
|
+
empty.textContent = t('notify.error') + ': ' + e.message;
|
|
962
|
+
empty.style.display = 'block';
|
|
963
|
+
textarea.style.display = 'none';
|
|
964
|
+
meta.textContent = '';
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
function enterActionEdit() {
|
|
969
|
+
if (!currentActionSlug) return;
|
|
970
|
+
const textarea = document.getElementById('action-content');
|
|
971
|
+
const empty = document.getElementById('action-empty');
|
|
972
|
+
if (empty.style.display === 'block' && !textarea.value) {
|
|
973
|
+
textarea.value = '# ' + document.getElementById('action-modal-title').textContent + '\\n\\n';
|
|
974
|
+
empty.style.display = 'none';
|
|
975
|
+
textarea.style.display = 'block';
|
|
976
|
+
}
|
|
977
|
+
setActionEditMode(true);
|
|
978
|
+
textarea.focus();
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
async function saveActionContent() {
|
|
982
|
+
if (!currentActionSlug) return;
|
|
983
|
+
const btn = document.getElementById('action-save-btn');
|
|
984
|
+
const textarea = document.getElementById('action-content');
|
|
985
|
+
btn.disabled = true;
|
|
986
|
+
btn.textContent = t('btn.saving');
|
|
987
|
+
|
|
988
|
+
try {
|
|
989
|
+
const res = await fetch('/api/columns/' + currentActionSlug + '/actions?lang=' + currentActionLang, {
|
|
990
|
+
method: 'POST',
|
|
991
|
+
headers: { 'Content-Type': 'application/json' },
|
|
992
|
+
body: JSON.stringify({ content: textarea.value })
|
|
993
|
+
});
|
|
994
|
+
const data = await res.json();
|
|
995
|
+
if (!res.ok || !data.success) throw new Error(data.error || t('notify.error'));
|
|
996
|
+
|
|
997
|
+
originalActionContent = textarea.value;
|
|
998
|
+
setActionEditMode(false);
|
|
999
|
+
showNotification('success', 'ACTION.' + currentActionLang + '.md ' + t('notify.actionUpdated'), currentActionSlug);
|
|
1000
|
+
} catch (e) {
|
|
1001
|
+
showNotification('error', t('notify.unableToSave'), e.message);
|
|
1002
|
+
} finally {
|
|
1003
|
+
btn.disabled = false;
|
|
1004
|
+
btn.textContent = t('btn.save');
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// ========================================
|
|
1009
|
+
// WEBSOCKET
|
|
1010
|
+
// ========================================
|
|
1011
|
+
let ws;
|
|
1012
|
+
|
|
1013
|
+
function connectWebSocket() {
|
|
1014
|
+
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1015
|
+
ws = new WebSocket(protocol + '//' + location.host + '/ws');
|
|
1016
|
+
|
|
1017
|
+
ws.onmessage = (event) => {
|
|
1018
|
+
try {
|
|
1019
|
+
const data = JSON.parse(event.data);
|
|
1020
|
+
switch (data.type) {
|
|
1021
|
+
case 'refresh':
|
|
1022
|
+
case 'ticket_updated':
|
|
1023
|
+
case 'ticket_created':
|
|
1024
|
+
case 'ticket_moved':
|
|
1025
|
+
loadTicketsFromAPI();
|
|
1026
|
+
break;
|
|
1027
|
+
case 'claude_start':
|
|
1028
|
+
onClaudeStart(data.ticket);
|
|
1029
|
+
break;
|
|
1030
|
+
case 'claude_stream':
|
|
1031
|
+
onClaudeStream(data.ticket);
|
|
1032
|
+
break;
|
|
1033
|
+
case 'claude_end':
|
|
1034
|
+
onClaudeEnd(data.ticket, data.success, data.duration);
|
|
1035
|
+
loadTicketsFromAPI();
|
|
1036
|
+
break;
|
|
1037
|
+
case 'claude_complete':
|
|
1038
|
+
if (data.success) {
|
|
1039
|
+
showNotification('claude', t('notify.claudeFinished') + ' ' + data.ticket, t('notify.processingSuccess'));
|
|
1040
|
+
} else {
|
|
1041
|
+
showNotification('error', t('notify.claudeFailed') + ' ' + data.ticket, t('notify.checkLogs'));
|
|
1042
|
+
}
|
|
1043
|
+
break;
|
|
1044
|
+
}
|
|
1045
|
+
} catch {}
|
|
1046
|
+
};
|
|
1047
|
+
|
|
1048
|
+
ws.onclose = () => setTimeout(connectWebSocket, 2000);
|
|
1049
|
+
ws.onerror = () => ws.close();
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// ========================================
|
|
1053
|
+
// CLAUDE LOG (in modal)
|
|
1054
|
+
// ========================================
|
|
1055
|
+
let logPollingInterval = null;
|
|
1056
|
+
let claudeProcessingTicket = null;
|
|
1057
|
+
|
|
1058
|
+
function startLogPolling(key) {
|
|
1059
|
+
stopLogPolling();
|
|
1060
|
+
logPollingInterval = setInterval(() => fetchLog(key), 1000);
|
|
1061
|
+
fetchLog(key);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
function stopLogPolling() {
|
|
1065
|
+
if (logPollingInterval) {
|
|
1066
|
+
clearInterval(logPollingInterval);
|
|
1067
|
+
logPollingInterval = null;
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
function formatCodeBlock(content, filename) {
|
|
1072
|
+
const lines = content.split(/\\\\n|\\n/);
|
|
1073
|
+
let lang = 'plaintext';
|
|
1074
|
+
if (filename) {
|
|
1075
|
+
const ext = filename.split('.').pop().toLowerCase();
|
|
1076
|
+
const langMap = {
|
|
1077
|
+
'js': 'javascript', 'ts': 'typescript', 'vue': 'html', 'jsx': 'javascript',
|
|
1078
|
+
'tsx': 'typescript', 'py': 'python', 'rb': 'ruby', 'java': 'java',
|
|
1079
|
+
'go': 'go', 'rs': 'rust', 'cpp': 'cpp', 'c': 'c', 'h': 'c',
|
|
1080
|
+
'css': 'css', 'scss': 'scss', 'html': 'html', 'json': 'json',
|
|
1081
|
+
'md': 'markdown', 'sh': 'bash', 'yml': 'yaml', 'yaml': 'yaml'
|
|
1082
|
+
};
|
|
1083
|
+
lang = langMap[ext] || 'plaintext';
|
|
1084
|
+
}
|
|
1085
|
+
let codeLines = [];
|
|
1086
|
+
for (const line of lines) {
|
|
1087
|
+
const match = line.match(/^\\s*\\d+[→|](.*)$/);
|
|
1088
|
+
if (match) {
|
|
1089
|
+
codeLines.push(match[1]);
|
|
1090
|
+
} else if (line.trim()) {
|
|
1091
|
+
codeLines.push(line);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
const codeContent = codeLines.join('\\n');
|
|
1095
|
+
const lineCount = codeLines.length;
|
|
1096
|
+
const preview = filename || (lineCount + ' lignes');
|
|
1097
|
+
|
|
1098
|
+
let html = '<details class="log-code-block">';
|
|
1099
|
+
html += '<summary class="log-code-header"><span class="code-lang">' + lang + '</span> ' + escapeHtml(preview) + '</summary>';
|
|
1100
|
+
html += '<pre><code class="language-' + lang + '">' + escapeHtml(codeContent) + '</code></pre>';
|
|
1101
|
+
html += '</details>';
|
|
1102
|
+
return html;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
function formatLogContent(rawContent) {
|
|
1106
|
+
if (!rawContent) return '';
|
|
1107
|
+
let cleanedContent = rawContent.replace(/<system-reminder>[\\s\\S]*?<\\/system-reminder>/gi, '');
|
|
1108
|
+
const lines = cleanedContent.split('\\n');
|
|
1109
|
+
const entries = [];
|
|
1110
|
+
let textBuffer = [];
|
|
1111
|
+
|
|
1112
|
+
function flushTextBuffer() {
|
|
1113
|
+
if (textBuffer.length > 0) {
|
|
1114
|
+
const text = textBuffer.join('\\n');
|
|
1115
|
+
entries.push('<div class="log-message-card"><div class="log-message-header assistant-header">Assistant</div><div class="log-message-body">' + renderMarkdown(text) + '</div></div>');
|
|
1116
|
+
textBuffer = [];
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
for (const line of lines) {
|
|
1121
|
+
if (!line.trim()) continue;
|
|
1122
|
+
|
|
1123
|
+
const timestampMatch = line.match(/^\\[(\\d{4}-\\d{2}-\\d{2}T[^\\]]+)\\]\\s*(.*)$/);
|
|
1124
|
+
if (timestampMatch) {
|
|
1125
|
+
flushTextBuffer();
|
|
1126
|
+
const date = new Date(timestampMatch[1]);
|
|
1127
|
+
const timeStr = date.toLocaleTimeString();
|
|
1128
|
+
entries.push('<div class="log-entry timestamp">' + timeStr + ' - ' + escapeHtml(timestampMatch[2]) + '</div>');
|
|
1129
|
+
continue;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
if (line.startsWith('[RAW] ')) {
|
|
1133
|
+
const raw = line.slice(6);
|
|
1134
|
+
if (!raw.startsWith('{')) continue;
|
|
1135
|
+
|
|
1136
|
+
const codeMatch = raw.match(/"content":"(\\s*\\d+[→|][^"]*)/);
|
|
1137
|
+
if (codeMatch) {
|
|
1138
|
+
const contentMatch = raw.match(/"content":"([^"]+)/);
|
|
1139
|
+
if (contentMatch) {
|
|
1140
|
+
const decoded = contentMatch[1].replace(/\\\\n/g, '\\n').replace(/\\\\"/g, '"');
|
|
1141
|
+
entries.push('<div class="log-message-card"><div class="log-message-header result-header">Tool Result</div><div class="log-message-body">' + formatCodeBlock(decoded) + '</div></div>');
|
|
1142
|
+
continue;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
const textMatch = raw.match(/"type":"text","text":"([^"]+)"/);
|
|
1147
|
+
if (textMatch) {
|
|
1148
|
+
const decoded = textMatch[1].replace(/\\\\n/g, '\\n').replace(/\\\\"/g, '"');
|
|
1149
|
+
entries.push('<div class="log-message-card"><div class="log-message-header assistant-header">Assistant</div><div class="log-message-body">' + renderMarkdown(decoded) + '</div></div>');
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
const toolMatch = raw.match(/"type":"tool_use"[^}]*"name":"([^"]+)"/);
|
|
1154
|
+
if (toolMatch) {
|
|
1155
|
+
entries.push('<div class="log-entry tool-call"><span class="log-tool-badge">' + escapeHtml(toolMatch[1]) + '</span></div>');
|
|
1156
|
+
continue;
|
|
1157
|
+
}
|
|
1158
|
+
continue;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
if (line.startsWith('[SYSTEM]')) {
|
|
1162
|
+
entries.push('<div class="log-entry system"><span class="log-label">System</span><div class="log-content">' + escapeHtml(line.slice(9)) + '</div></div>');
|
|
1163
|
+
continue;
|
|
1164
|
+
}
|
|
1165
|
+
if (line.startsWith('[ASSISTANT]')) {
|
|
1166
|
+
entries.push('<div class="log-message-card"><div class="log-message-header assistant-header">Assistant</div><div class="log-message-body">' + renderMarkdown(line.slice(12)) + '</div></div>');
|
|
1167
|
+
continue;
|
|
1168
|
+
}
|
|
1169
|
+
if (line.startsWith('[TOOL]')) {
|
|
1170
|
+
entries.push('<div class="log-entry tool-call"><span class="log-tool-badge">' + escapeHtml(line.slice(7)) + '</span></div>');
|
|
1171
|
+
continue;
|
|
1172
|
+
}
|
|
1173
|
+
if (line.startsWith('[RESULT]')) {
|
|
1174
|
+
const content = line.slice(9);
|
|
1175
|
+
if (/^\\s*\\d+[→|]/.test(content)) {
|
|
1176
|
+
entries.push('<div class="log-message-card"><div class="log-message-header result-header">Result</div><div class="log-message-body">' + formatCodeBlock(content) + '</div></div>');
|
|
1177
|
+
} else {
|
|
1178
|
+
entries.push('<div class="log-message-card"><div class="log-message-header result-header">Result</div><div class="log-message-body">' + escapeHtml(content.slice(0, 1000)) + (content.length > 1000 ? '...' : '') + '</div></div>');
|
|
1179
|
+
}
|
|
1180
|
+
continue;
|
|
1181
|
+
}
|
|
1182
|
+
if (line.startsWith('[ERROR]')) {
|
|
1183
|
+
entries.push('<div class="log-entry error"><span class="log-label">Error</span><div class="log-content">' + escapeHtml(line.slice(8)) + '</div></div>');
|
|
1184
|
+
continue;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
entries.push('<div class="log-entry system">' + escapeHtml(line) + '</div>');
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
return entries.join('');
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
function resetClaudeLog() {
|
|
1194
|
+
stopLogPolling();
|
|
1195
|
+
document.getElementById('claude-log-section').style.display = 'none';
|
|
1196
|
+
document.getElementById('claude-log').innerHTML = '';
|
|
1197
|
+
document.getElementById('claude-log-status').className = 'claude-log-status';
|
|
1198
|
+
document.getElementById('claude-log-status').textContent = t('status.waiting');
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
async function fetchLog(key) {
|
|
1202
|
+
try {
|
|
1203
|
+
const res = await fetch('/api/tickets/' + key + '/log');
|
|
1204
|
+
const json = await res.json();
|
|
1205
|
+
if (json.success && json.data) {
|
|
1206
|
+
const section = document.getElementById('claude-log-section');
|
|
1207
|
+
const log = document.getElementById('claude-log');
|
|
1208
|
+
|
|
1209
|
+
if (json.data.exists || json.data.content) {
|
|
1210
|
+
section.style.display = 'block';
|
|
1211
|
+
log.innerHTML = formatLogContent(json.data.content || '');
|
|
1212
|
+
log.scrollTop = log.scrollHeight;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
} catch (e) {
|
|
1216
|
+
console.error('Log fetch error:', e);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
function onClaudeStart(ticket) {
|
|
1221
|
+
claudeProcessingTicket = ticket;
|
|
1222
|
+
claudeProcessingTickets.add(ticket);
|
|
1223
|
+
render();
|
|
1224
|
+
|
|
1225
|
+
const status = document.getElementById('claude-log-status');
|
|
1226
|
+
if (status) {
|
|
1227
|
+
status.className = 'claude-log-status processing';
|
|
1228
|
+
status.textContent = '\\u{1F916} ' + t('status.processing');
|
|
1229
|
+
}
|
|
1230
|
+
if (editingKey === ticket) {
|
|
1231
|
+
document.getElementById('claude-log-section').style.display = 'block';
|
|
1232
|
+
startLogPolling(ticket);
|
|
1233
|
+
}
|
|
1234
|
+
showNotification('claude', t('notify.claudeStarted'), ticket);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
function onClaudeStream(ticket) {
|
|
1238
|
+
if (editingKey === ticket) {
|
|
1239
|
+
fetchLog(ticket);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
function onClaudeEnd(ticket, success, duration) {
|
|
1244
|
+
claudeProcessingTicket = null;
|
|
1245
|
+
claudeProcessingTickets.delete(ticket);
|
|
1246
|
+
render();
|
|
1247
|
+
|
|
1248
|
+
const status = document.getElementById('claude-log-status');
|
|
1249
|
+
if (status && editingKey === ticket) {
|
|
1250
|
+
if (success) {
|
|
1251
|
+
status.className = 'claude-log-status success';
|
|
1252
|
+
status.textContent = '\\u2705 ' + t('status.completed') + ' (' + (duration / 1000).toFixed(1) + 's)';
|
|
1253
|
+
} else {
|
|
1254
|
+
status.className = 'claude-log-status error';
|
|
1255
|
+
status.textContent = '\\u274C ' + t('status.failed');
|
|
1256
|
+
}
|
|
1257
|
+
fetchLog(ticket);
|
|
1258
|
+
stopLogPolling();
|
|
1259
|
+
}
|
|
1260
|
+
if (success) {
|
|
1261
|
+
showNotification('success', t('notify.claudeFinished'), ticket);
|
|
1262
|
+
} else {
|
|
1263
|
+
showNotification('error', t('notify.claudeFailed'), ticket);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
// ========================================
|
|
1268
|
+
// API
|
|
1269
|
+
// ========================================
|
|
1270
|
+
async function loadTicketsFromAPI() {
|
|
1271
|
+
try {
|
|
1272
|
+
const res = await fetch('/api/tickets');
|
|
1273
|
+
const json = await res.json();
|
|
1274
|
+
if (json.success && json.data) {
|
|
1275
|
+
TICKETS.length = 0;
|
|
1276
|
+
(json.data.tickets || []).forEach(tk => TICKETS.push(tk));
|
|
1277
|
+
COLUMNS.length = 0;
|
|
1278
|
+
(json.data.columns || []).forEach(c => COLUMNS.push(c));
|
|
1279
|
+
render();
|
|
1280
|
+
}
|
|
1281
|
+
} catch (e) {
|
|
1282
|
+
console.error(t('notify.loadingError') + ':', e);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
async function loadProcessingStatus() {
|
|
1287
|
+
try {
|
|
1288
|
+
const res = await fetch('/api/status');
|
|
1289
|
+
const json = await res.json();
|
|
1290
|
+
if (json.success && json.data && json.data.processingTickets) {
|
|
1291
|
+
claudeProcessingTickets = new Set(json.data.processingTickets);
|
|
1292
|
+
render();
|
|
1293
|
+
}
|
|
1294
|
+
} catch (e) {
|
|
1295
|
+
console.error('Failed to load processing status:', e);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
function onTicketClick(key) {
|
|
1300
|
+
window.location.href = '/ticket/' + key;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
// ========================================
|
|
1304
|
+
// KEYBOARD
|
|
1305
|
+
// ========================================
|
|
1306
|
+
document.addEventListener('keydown', e => {
|
|
1307
|
+
if (e.key === 'Escape') {
|
|
1308
|
+
const actionModal = document.getElementById('action-modal');
|
|
1309
|
+
if (actionModal?.classList.contains('active')) {
|
|
1310
|
+
closeActionModal();
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
closeModal();
|
|
1314
|
+
}
|
|
1315
|
+
});
|
|
1316
|
+
|
|
1317
|
+
// ========================================
|
|
1318
|
+
// INIT
|
|
1319
|
+
// ========================================
|
|
1320
|
+
render();
|
|
1321
|
+
loadProcessingStatus();
|
|
1322
|
+
connectWebSocket();
|
|
1323
|
+
`;
|
|
1324
|
+
}
|
|
1325
|
+
//# sourceMappingURL=index.js.map
|