feat(129): order user notes module
CRUD notatek autorskich operatora per zamowienie z badge [N] na liscie
zamowien. Reuse istniejacej tabeli `order_notes` przez nowy
`note_type='user'` z `user_id` (FK->users SET NULL) i `author_name`
(snapshot). Sekcja `#notes` w "Wiadomosci i zalaczniki" w
`/orders/{id}` z inline edit form + delete przez
`OrderProAlerts.confirm`. Autoryzacja DB-level
(`WHERE user_id = :user_id`, rowCount=0 ⇒ 403) — bez admin override
(brak systemu rol w aplikacji).
- Migracja `20260514_000116_*.sql` (ADD COLUMN user_id + author_name +
FK + indeks `idx_order_notes_type_order`); idempotentne z DDL
no-op fallback.
- `OrderNotesService` (CRUD + walidacja body ≤ 2000 znakow); subquery
`user_notes_count` w paginate; badge HTML w `toTableRow()`.
- 3 routy POST /orders/{id}/notes(/update|/delete).
- SCSS module `_order-notes.scss` + vanilla JS `order-notes.js`
(inline edit toggle + delete confirm; idempotent guard).
- 9 kluczy i18n PL; PROJECT.md + ROADMAP.md + tech_changelog.md +
db_schema.md zaktualizowane.
Follow-up: `php bin/migrate.php` + manualny smoke test (autor vs inny
user + badge na /orders/list).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
89
public/assets/js/modules/order-notes.js
Normal file
89
public/assets/js/modules/order-notes.js
Normal file
@@ -0,0 +1,89 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
if (window.__orderNotesInit) {
|
||||
return;
|
||||
}
|
||||
window.__orderNotesInit = true;
|
||||
|
||||
function bindEdit(button) {
|
||||
if (button.dataset.bound === '1') return;
|
||||
button.dataset.bound = '1';
|
||||
|
||||
button.addEventListener('click', function () {
|
||||
var noteEl = button.closest('[data-note-id]');
|
||||
if (!noteEl) return;
|
||||
var body = noteEl.querySelector('.js-order-note-body');
|
||||
var form = noteEl.querySelector('.js-order-note-edit-form');
|
||||
if (!body || !form) return;
|
||||
body.style.display = 'none';
|
||||
form.style.display = '';
|
||||
var textarea = form.querySelector('textarea[name="body"]');
|
||||
if (textarea) {
|
||||
textarea.focus();
|
||||
var len = textarea.value.length;
|
||||
try { textarea.setSelectionRange(len, len); } catch (e) { /* ignore */ }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function bindCancel(button) {
|
||||
if (button.dataset.bound === '1') return;
|
||||
button.dataset.bound = '1';
|
||||
|
||||
button.addEventListener('click', function () {
|
||||
var noteEl = button.closest('[data-note-id]');
|
||||
if (!noteEl) return;
|
||||
var body = noteEl.querySelector('.js-order-note-body');
|
||||
var form = noteEl.querySelector('.js-order-note-edit-form');
|
||||
if (!body || !form) return;
|
||||
form.style.display = 'none';
|
||||
body.style.display = '';
|
||||
});
|
||||
}
|
||||
|
||||
function bindDelete(form) {
|
||||
if (form.dataset.bound === '1') return;
|
||||
form.dataset.bound = '1';
|
||||
|
||||
form.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
if (window.OrderProAlerts && typeof window.OrderProAlerts.confirm === 'function') {
|
||||
window.OrderProAlerts.confirm({
|
||||
title: 'Usunac notatke?',
|
||||
message: 'Tej operacji nie mozna cofnac.',
|
||||
danger: true,
|
||||
confirmLabel: 'Usun',
|
||||
onConfirm: function () {
|
||||
form.dataset.bound = '2';
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
form.dataset.bound = '2';
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
var editButtons = document.querySelectorAll('.js-order-note-edit');
|
||||
for (var i = 0; i < editButtons.length; i++) {
|
||||
bindEdit(editButtons[i]);
|
||||
}
|
||||
var cancelButtons = document.querySelectorAll('.js-order-note-edit-cancel');
|
||||
for (var j = 0; j < cancelButtons.length; j++) {
|
||||
bindCancel(cancelButtons[j]);
|
||||
}
|
||||
var deleteForms = document.querySelectorAll('.js-order-note-delete');
|
||||
for (var k = 0; k < deleteForms.length; k++) {
|
||||
bindDelete(deleteForms[k]);
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user