@@ -1,116 +1,217 @@
document . addEventListener ( 'DOMContentLoaded' , function ( ) {
const modal = document . getElementById ( 'massAddModal' ) ;
const productList = document . getElementById ( 'mass-add-list' ) ;
const sortBar = document . getElementById ( 'sort-bar' ) ;
const productCountDisplay = document . getElementById ( 'product-count' ) ;
const modalBody = modal ? . querySelector ( '.modal-body' ) ;
// Funkcja normalizacji (usuwa diakrytyki i zamienia na lowercase)
function normalize ( str ) {
return str . normalize ( "NFD" ) . replace ( /[\u0300-\u036f]/g , "" ) . toLowerCase ( ) ;
return str ? . trim ( ) . toLowerCase ( ) || '' ;
}
modal . addEventListener ( 'show.bs.modal' , async function ( ) {
let addedProducts = new Set ( ) ;
let sortMode = 'popularity' ;
let limit = 100 ;
let offset = 0 ;
let loading = false ;
let reachedEnd = false ;
let allProducts = [ ] ;
let addedProducts = new Set ( ) ;
function renderSortBar ( ) {
if ( ! sortBar ) return ;
sortBar . innerHTML = `
Sortuj: <a href="#" id="sort-popularity" ${ sortMode === "popularity" ? 'style="font-weight:bold"' : '' } >Popularność</a> |
<a href="#" id="sort-alphabetical" ${ sortMode === "alphabetical" ? 'style="font-weight:bold"' : '' } >Alfabetycznie</a>
` ;
document . getElementById ( 'sort-popularity' ) . onclick = ( e ) => {
e . preventDefault ( ) ;
if ( sortMode !== 'popularity' ) {
sortMode = 'popularity' ;
resetAndFetchProducts ( ) ;
}
} ;
document . getElementById ( 'sort-alphabetical' ) . onclick = ( e ) => {
e . preventDefault ( ) ;
if ( sortMode !== 'alphabetical' ) {
sortMode = 'alphabetical' ;
resetAndFetchProducts ( ) ;
}
} ;
}
function resetAndFetchProducts ( ) {
offset = 0 ;
reachedEnd = false ;
allProducts = [ ] ;
productList . innerHTML = '' ;
fetchProducts ( true ) ;
renderSortBar ( ) ;
if ( productCountDisplay ) productCountDisplay . textContent = '' ;
}
async function fetchProducts ( reset = false ) {
if ( loading || reachedEnd ) return ;
loading = true ;
if ( ! reset ) {
const loadingLi = document . createElement ( 'li' ) ;
loadingLi . className = 'list-group-item bg-dark text-light loading' ;
loadingLi . textContent = 'Ładowanie...' ;
productList . appendChild ( loadingLi ) ;
}
try {
const res = await fetch ( ` /all_products?sort= ${ sortMode } &limit= ${ limit } &offset= ${ offset } ` ) ;
const data = await res . json ( ) ;
const products = data . products || [ ] ;
if ( products . length < limit ) reachedEnd = true ;
allProducts = reset ? products : allProducts . concat ( products ) ;
const loadingEl = productList . querySelector ( '.loading' ) ;
if ( loadingEl ) loadingEl . remove ( ) ;
if ( reset && products . length === 0 ) {
const emptyLi = document . createElement ( 'li' ) ;
emptyLi . className = 'list-group-item text-muted bg-dark' ;
emptyLi . textContent = 'Brak produktów do wyświetlenia.' ;
productList . appendChild ( emptyLi ) ;
} else {
renderProducts ( products ) ;
}
offset += limit ;
if ( productCountDisplay ) {
productCountDisplay . textContent = ` Wyświetlono ${ allProducts . length } produktów. ` ;
}
} catch ( err ) {
const loadingEl = productList . querySelector ( '.loading' ) ;
if ( loadingEl ) loadingEl . remove ( ) ;
const errorLi = document . createElement ( 'li' ) ;
errorLi . className = 'list-group-item text-danger bg-dark' ;
errorLi . textContent = 'Błąd ładowania danych' ;
productList . appendChild ( errorLi ) ;
}
loading = false ;
}
function getAlreadyAddedProducts ( ) {
const set = new Set ( ) ;
document . querySelectorAll ( '#items li' ) . forEach ( li => {
if ( li . dataset . name ) {
addedProducts . add ( normalize ( li . dataset . name ) ) ;
set . add ( normalize ( li . dataset . name ) ) ;
}
} ) ;
return set ;
}
p roductList . innerHTML = '<li class="list-group-item bg-dark text-light">Ładowanie...</li>' ;
try {
const res = await fetch ( '/all_products' ) ;
const data = await res . json ( ) ;
const allproducts = data . allproducts ;
productList . innerHTML = '' ;
allproducts . forEach ( name => {
const li = document . createElement ( 'li' ) ;
li . className = 'list-group-item d-flex justify-content-between align-items-center bg-dark text-light' ;
function renderP roducts ( products ) {
addedProducts = getAlreadyAddedProducts ( ) ;
if ( addedProducts . has ( normalize ( name ) ) ) {
const nameSpan = document . createElement ( 'span' ) ;
nameSpan . textContent = name ;
li . appendChild ( nameSpan ) ;
const existingNames = new Set ( ) ;
document . querySelectorAll ( '#mass-add-list li' ) . forEach ( li => {
const name = li . querySelector ( 'span' ) ? . textContent ;
if ( name ) existingNames . add ( normalize ( name ) ) ;
} ) ;
li . classList . add ( 'opacity-50' ) ;
const badg e = document . createElement ( 'span' ) ;
badge . className = 'badge bg-success ms-auto' ;
badge . textContent = 'Dodano' ;
li . appendChild ( badg e ) ;
} else {
const nameSpan = document . createElement ( 'span' ) ;
nameSpan . textContent = name ;
nameSpan . style . flex = '1 1 auto' ;
li . appendChild ( nameSpan ) ;
products . forEach ( product => {
const nam e = typeof product === "object" ? product . name : product ;
const normName = normalize ( name ) ;
if ( existingNames . has ( normName ) ) return ;
existingNames . add ( normNam e) ;
const qtyWrapper = document . createElement ( 'div ' ) ;
qtyWrapper . className = 'd-flex align-items-center ms-2 quantity-controls ' ;
const li = document . createElement ( 'li ' ) ;
li . className = 'list-group-item d-flex justify-content-between align-items-center bg-dark text-light ' ;
const minusBtn = document . createElement ( 'button' ) ;
minusBtn . type = 'button' ;
minusBtn . className = 'btn btn-outline-light btn-sm px-2' ;
minusBtn . textContent = '− ' ;
minusBtn . onclick = ( ) => {
qty . value = Math . max ( 1 , parseInt ( qty . value ) - 1 ) ;
} ;
if ( addedProducts . has ( normName ) ) {
const nameSpan = document . createElement ( 'span' ) ;
nameSpan . textContent = name ;
li . appendChild ( nameSpan ) ;
li . classList . add ( 'opacity-50' ) ;
const badge = document . createElement ( 'span' ) ;
badge . className = 'badge bg-success ms-auto' ;
badge . textContent = 'Dodano' ;
li . appendChild ( badge ) ;
} else {
const nameSpan = document . createElement ( 'span' ) ;
nameSpan . textContent = name ;
nameSpan . style . flex = '1 1 auto' ;
li . appendChild ( nameSpan ) ;
const qty = document . createElement ( 'input ' ) ;
qty. type = 'number ' ;
qty . min = 1 ;
qty . value = 1 ;
qty . className = 'form-control text-center p-1 rounded' ;
qty . style . width = '50px' ;
qty . style . margin = '0 2px' ;
qty . title = 'Ilość' ;
const qtyWrapper = document . createElement ( 'div ' ) ;
qtyWrapper . className = 'd-flex align-items-center ms-2 quantity-controls ' ;
const pl usBtn = document . createElement ( 'button' ) ;
pl usBtn . type = 'button' ;
pl usBtn . className = 'btn btn-outline-light btn-sm px-2' ;
pl usBtn . textContent = '+ ' ;
plusBtn . onclick = ( ) => {
qty . value = parseInt ( qty . value ) + 1 ;
} ;
const min usBtn = document . createElement ( 'button' ) ;
min usBtn. type = 'button' ;
min usBtn. className = 'btn btn-outline-light btn-sm px-2' ;
min usBtn. textContent = '− ' ;
qtyWrapper . appendChild ( minusBtn ) ;
qtyWrapper . appendChild ( qty ) ;
qtyWrapper . appendChild ( plusBtn ) ;
const qty = document . createElement ( 'input' ) ;
qty. type = 'number' ;
qty. min = 1 ;
qty . value = 1 ;
qty . className = 'form-control text-center p-1 rounded' ;
qty . style . width = '50px' ;
qty . style . margin = '0 2px' ;
qty . title = 'Ilość' ;
const b tn = document . createElement ( 'button' ) ;
b tn . classNam e = 'btn btn-sm btn-primary ms-4 ' ;
b tn . textContent = '+ ';
const plusB tn = document . createElement ( 'button' ) ;
plusB tn. typ e = 'button ' ;
plusB tn. className = 'btn btn-outline-light btn-sm px-2 ';
plusBtn . textContent = '+' ;
b tn . onclick = ( ) => {
const quantity = parseInt ( qty . value ) || 1 ;
socket . emit ( 'add_item' , { list _id : LIST _ID , name : name , quantity : quantity } ) ;
} ;
minusB tn. onclick = ( ) => {
qty . value = Math . max ( 1 , parseInt ( qty . value ) - 1 ) ;
} ;
plusBtn . onclick = ( ) => {
qty . value = parseInt ( qty . value ) + 1 ;
} ;
li . appendChild ( qtyWrapper ) ;
li . appendChild ( btn ) ;
}
productList . appendChild ( li ) ;
} ) ;
qtyWrapper . append ( minusBtn , qty , plusBtn ) ;
} catch ( err ) {
productList . innerHTML = '<li class="list-group-item text-danger bg-dark">Błąd ładowania danych</li> ';
}
const btn = document . createElement ( 'button' ) ;
btn . className = 'btn btn-sm btn-primary ms-4 ' ;
btn . textContent = '+' ;
btn . onclick = ( ) => {
const quantity = parseInt ( qty . value ) || 1 ;
socket . emit ( 'add_item' , { list _id : LIST _ID , name : name , quantity : quantity } ) ;
} ;
li . append ( qtyWrapper , btn ) ;
}
productList . appendChild ( li ) ;
} ) ;
}
if ( modalBody ) {
modalBody . addEventListener ( 'scroll' , function ( ) {
if ( ! loading && ! reachedEnd && ( modalBody . scrollTop + modalBody . clientHeight > modalBody . scrollHeight - 80 ) ) {
fetchProducts ( false ) ;
}
} ) ;
}
modal . addEventListener ( 'show.bs.modal' , function ( ) {
resetAndFetchProducts ( ) ;
} ) ;
renderSortBar ( ) ;
socket . on ( 'item_added' , data => {
document . querySelectorAll ( '#mass-add-list li' ) . forEach ( li => {
const itemName = li . firstChild ? . textContent . trim ( ) ;
if ( normalize ( itemName ) === normalize ( data . name ) && ! li . classList . contains ( 'opacity-50' ) ) {
li . classList . add ( 'opacity-50' ) ;
// Usuń poprzednie przyciski
li . querySelectorAll ( 'button' ) . forEach ( btn => btn . remove ( ) ) ;
const quantityControls = li . querySelector ( '.quantity-controls' ) ;
if ( quantityControls ) quantityControls . remove ( ) ;
// Badge "Dodano"
const badge = document . createElement ( 'span' ) ;
badge . className = 'badge bg-success' ;
badge . textContent = 'Dodano' ;
// Grupowanie przycisku + licznika
const btnGroup = document . createElement ( 'div' ) ;
btnGroup . className = 'btn-group btn-group-sm me-2' ;
btnGroup . role = 'group' ;
@@ -124,17 +225,13 @@ document.addEventListener('DOMContentLoaded', function () {
let secondsLeft = 15 ;
timerBtn . textContent = ` ${ secondsLeft } s ` ;
btnGroup . appendChild ( undoBtn ) ;
btnGroup . appendChild ( timerBtn ) ;
btnGroup . append ( undoBtn , timerBtn );
// Kontener na prawą stronę
const rightWrapper = document . createElement ( 'div' ) ;
rightWrapper . className = 'd-flex align-items-center gap-2 ms-auto' ;
rightWrapper . appendChild ( btnGroup ) ;
rightWrapper . appendChild ( badge ) ;
rightWrapper . append ( btnGroup , badge );
li . appendChild ( rightWrapper ) ;
// Odliczanie
const intervalId = setInterval ( ( ) => {
secondsLeft -- ;
if ( secondsLeft > 0 ) {
@@ -145,14 +242,12 @@ document.addEventListener('DOMContentLoaded', function () {
}
} , 1000 ) ;
// Obsługa cofnięcia
undoBtn . onclick = ( ) => {
clearInterval ( intervalId ) ;
btnGroup . remove ( ) ;
badge . remove ( ) ;
li . classList . remove ( 'opacity-50' ) ;
// Przywróć kontrolki ilości
const qtyWrapper = document . createElement ( 'div' ) ;
qtyWrapper . className = 'd-flex align-items-center ms-2 quantity-controls' ;
@@ -185,7 +280,6 @@ document.addEventListener('DOMContentLoaded', function () {
qtyWrapper . append ( minusBtn , qty , plusBtn ) ;
li . appendChild ( qtyWrapper ) ;
// Dodaj przycisk dodawania
const addBtn = document . createElement ( 'button' ) ;
addBtn . className = 'btn btn-sm btn-primary ms-4' ;
addBtn . textContent = '+' ;
@@ -199,13 +293,9 @@ document.addEventListener('DOMContentLoaded', function () {
} ;
li . appendChild ( addBtn ) ;
// Usuń z listy
socket . emit ( 'delete_item' , { item _id : data . id } ) ;
} ;
}
} ) ;
} ) ;
} ) ;