33< head >
44 < meta charset ="UTF-8 ">
55 < meta name ="viewport " content ="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no ">
6- < title > فَوْتَرَة كامل - نظام الفوترة الجزائري (حفظ الصور )</ title >
6+ < title > فَوْتَرَة كامل - نظام الفوترة الجزائري (صور واضحة )</ title >
77 < script src ="https://cdn.tailwindcss.com "> </ script >
88 < link rel ="preconnect " href ="https://fonts.googleapis.com ">
99 < link rel ="preconnect " href ="https://fonts.gstatic.com " crossorigin >
104104 }
105105 .dark .product-grid-card { background : # 1e293b ; border-color : # 334155 ; }
106106 .product-grid-card : hover { transform : translateY (-4px ); border-color : # 0ea5e9 ; box-shadow : 0 8px 25px rgba (14 , 165 , 233 , 0.15 ); }
107- .product-grid-card img { width : 60px ; height : 60px ; object-fit : cover; border-radius : 10px ; border : 1px solid # e2e8f0 ; background : white; }
107+ /* حجم الصورة في شبكة POS - 64x64 */
108+ .product-grid-card img {
109+ width : 64px ;
110+ height : 64px ;
111+ object-fit : cover;
112+ border-radius : 12px ;
113+ border : 1px solid # e2e8f0 ;
114+ background : white;
115+ }
108116 .dark .product-grid-card img { border-color : # 475569 ; background : # 0f172a ; }
109117 .product-grid-card .product-name {
110118 font-weight : 800 ;
113121 line-height : 1.3 ;
114122 color : # 000000 ;
115123 }
116- .dark .product-grid-card .product-name {
117- color : # f1f5f9 ;
118- }
124+ .dark .product-grid-card .product-name { color : # f1f5f9 ; }
119125 .product-grid-card .product-price {
120126 color : # 0ea5e9 ;
121127 font-weight : 800 ;
369375 margin-right : 8px ;
370376 }
371377 .dark .edit-badge { background : # d97706 ; }
378+
379+ /* تنسيق الصورة في جدول المنتجات بحجم أكبر */
380+ .product-table-img {
381+ width : 80px ;
382+ height : 80px ;
383+ object-fit : cover;
384+ border-radius : 12px ;
385+ border : 1px solid # e2e8f0 ;
386+ background : white;
387+ }
388+ .dark .product-table-img {
389+ border-color : # 475569 ;
390+ background : # 0f172a ;
391+ }
392+ .product-table-img-placeholder {
393+ width : 80px ;
394+ height : 80px ;
395+ border-radius : 12px ;
396+ background : # f1f5f9 ;
397+ display : flex;
398+ align-items : center;
399+ justify-content : center;
400+ color : # 94a3b8 ;
401+ font-size : 28px ;
402+ border : 1px solid # e2e8f0 ;
403+ }
404+ .dark .product-table-img-placeholder {
405+ background : # 1e293b ;
406+ border-color : # 334155 ;
407+ color : # 64748b ;
408+ }
372409 </ style >
373410</ head >
374411< body class ="bg-[#f7f8fb] dark:bg-slate-950 text-slate-800 dark:text-slate-100 transition-colors ">
384421 </ div >
385422 < div >
386423 < h1 class ="font-extrabold text-lg leading-none "> فَوْتَرَة</ h1 >
387- < p class ="text-[11px] text-slate-500 dark:text-slate-400 "> الإصدار الجزائري v4.5 </ p >
424+ < p class ="text-[11px] text-slate-500 dark:text-slate-400 "> الإصدار الجزائري v4.6 </ p >
388425 </ div >
389426 </ div >
390427 < nav class ="p-3 space-y-1.5 overflow-y-auto ">
@@ -555,15 +592,15 @@ <h2 class="text-xl font-extrabold">إدارة الفواتير</h2>
555592 </ div >
556593 </ section >
557594
558- <!-- PRODUCTS -->
595+ <!-- PRODUCTS (مع صور أكبر) -->
559596 < section id ="page-products " class ="page hidden space-y-4 animate-in ">
560597 < div class ="flex items-center justify-between ">
561598 < h2 class ="text-xl font-extrabold "> المنتجات والمخزون</ h2 >
562599 < button id ="btn-add-product " class ="h-10 px-4 rounded-xl bg-sky-600 text-white text-sm font-bold "> < i class ="fa-solid fa-plus ml-1 "> </ i > منتج جديد</ button >
563600 </ div >
564601 < div class ="bg-white dark:bg-slate-900 rounded-3xl border border-slate-200 dark:border-slate-800 overflow-hidden ">
565602 < div class ="overflow-x-auto table-wrap ">
566- < table class ="w-full text-sm min-w-[800px ] ">
603+ < table class ="w-full text-sm min-w-[850px ] ">
567604 < thead class ="bg-slate-50 dark:bg-slate-800/50 "> < tr class ="text-right text-slate-500 ">
568605 < th class ="p-3 "> الصورة</ th > < th class ="p-3 "> المنتج</ th > < th class ="p-3 "> المرجع</ th > < th class ="p-3 "> سعر الشراء</ th > < th class ="p-3 "> سعر البيع HT</ th > < th class ="p-3 "> TVA %</ th > < th class ="p-3 "> المخزون</ th > < th class ="p-3 "> إجراءات</ th >
569606 </ tr > </ thead >
@@ -1237,16 +1274,36 @@ <h3 id="modal-title" class="font-extrabold text-lg">إضافة</h3>
12371274 }
12381275
12391276 // ============================================================
1240- // PRODUCTS (مع تحسين حفظ الصورة )
1277+ // PRODUCTS (مع صور أكبر )
12411278 // ============================================================
12421279 let pendingAddToInvoice = false ;
12431280
1281+ // دالة معالجة الصورة
1282+ function handleImageFile ( file ) {
1283+ return new Promise ( ( resolve ) => {
1284+ const reader = new FileReader ( ) ;
1285+ reader . onload = function ( e ) {
1286+ const dataUrl = e . target . result ;
1287+ if ( dataUrl . length > 2 * 1024 * 1024 ) {
1288+ showToast ( '⚠️ الصورة كبيرة جداً، قد تواجه مشكلة في الأداء' , 'warning' , 4000 ) ;
1289+ }
1290+ resolve ( dataUrl ) ;
1291+ } ;
1292+ reader . onerror = function ( ) {
1293+ showToast ( 'فشل قراءة الصورة' , 'error' ) ;
1294+ resolve ( '' ) ;
1295+ } ;
1296+ reader . readAsDataURL ( file ) ;
1297+ } ) ;
1298+ }
1299+
12441300 function renderProducts ( ) {
12451301 $ ( '#products-table' ) . innerHTML = state . products . map ( p => `
12461302 <tr class="border-b dark:border-slate-800 hover:bg-slate-50 dark:hover:bg-slate-800/50">
12471303 <td class="p-3">
1248- ${ p . image ? `<img src="${ p . image } " class="w-12 h-12 rounded-lg object-cover border border-slate-200 dark:border-slate-700" alt="${ p . name } ">` :
1249- `<div class="w-12 h-12 rounded-lg bg-slate-200 dark:bg-slate-700 flex items-center justify-center text-slate-400"><i class="fa-solid fa-image"></i></div>` }
1304+ ${ p . image && p . image . startsWith ( 'data:image' ) ?
1305+ `<img src="${ p . image } " class="product-table-img" alt="${ p . name } ">` :
1306+ `<div class="product-table-img-placeholder"><i class="fa-solid fa-image"></i></div>` }
12501307 </td>
12511308 <td class="p-3 font-bold">${ p . name } </td>
12521309 <td class="p-3 font-mono text-xs">${ p . ref } </td>
@@ -1270,8 +1327,9 @@ <h3 id="modal-title" class="font-extrabold text-lg">إضافة</h3>
12701327 $ ( '#product-grid' ) . innerHTML = list . map ( p => `
12711328 <div class="product-grid-card" onclick="addItemById(${ p . id } )">
12721329 <button class="add-btn" onclick="event.stopPropagation(); addItemById(${ p . id } )" aria-label="إضافة المنتج">+</button>
1273- ${ p . image ? `<img src="${ p . image } " alt="${ p . name } ">` :
1274- `<div class="w-[60px] h-[60px] rounded-xl bg-slate-200 dark:bg-slate-700 flex items-center justify-center text-slate-400"><i class="fa-solid fa-image text-2xl"></i></div>` }
1330+ ${ p . image && p . image . startsWith ( 'data:image' ) ?
1331+ `<img src="${ p . image } " alt="${ p . name } ">` :
1332+ `<div class="w-16 h-16 rounded-xl bg-slate-200 dark:bg-slate-700 flex items-center justify-center text-slate-400"><i class="fa-solid fa-image text-2xl"></i></div>` }
12751333 <div class="product-name">${ p . name } </div>
12761334 <div class="product-price">${ money ( p . price ) } </div>
12771335 <div class="text-xs text-slate-500">${ p . ref } </div>
@@ -1281,32 +1339,6 @@ <h3 id="modal-title" class="font-extrabold text-lg">إضافة</h3>
12811339
12821340 $ ( '#btn-add-product' ) . onclick = ( ) => openProduct ( ) ;
12831341
1284- // دالة لضغط الصورة إذا كانت كبيرة
1285- function compressImage ( dataUrl , maxWidth = 200 , maxHeight = 200 , quality = 0.7 ) {
1286- return new Promise ( ( resolve ) => {
1287- const img = new Image ( ) ;
1288- img . onload = function ( ) {
1289- let width = img . width ;
1290- let height = img . height ;
1291- if ( width > maxWidth ) {
1292- height = ( maxWidth / width ) * height ;
1293- width = maxWidth ;
1294- }
1295- if ( height > maxHeight ) {
1296- width = ( maxHeight / height ) * width ;
1297- height = maxHeight ;
1298- }
1299- const canvas = document . createElement ( 'canvas' ) ;
1300- canvas . width = width ;
1301- canvas . height = height ;
1302- const ctx = canvas . getContext ( '2d' ) ;
1303- ctx . drawImage ( img , 0 , 0 , width , height ) ;
1304- resolve ( canvas . toDataURL ( 'image/jpeg' , quality ) ) ;
1305- } ;
1306- img . src = dataUrl ;
1307- } ) ;
1308- }
1309-
13101342 function openProduct ( p = null , ref = '' , addToInvoice = false ) {
13111343 pendingAddToInvoice = addToInvoice ;
13121344 $ ( '#modal-title' ) . textContent = p ? 'تعديل منتج' : ( ref ? 'إضافة منتج جديد من المسح' : 'منتج جديد' ) ;
@@ -1340,29 +1372,16 @@ <h3 id="modal-title" class="font-extrabold text-lg">إضافة</h3>
13401372 fileInput . addEventListener ( 'change' , async function ( e ) {
13411373 const file = e . target . files [ 0 ] ;
13421374 if ( file ) {
1343- // التحقق من حجم الصورة
1344- if ( file . size > 1024 * 1024 ) { // أكبر من 1 ميجابايت
1345- showToast ( 'الصورة كبيرة جداً، سيتم ضغطها تلقائياً' , 'warning' , 3000 ) ;
1346- }
1347- const reader = new FileReader ( ) ;
1348- reader . onload = async function ( ev ) {
1349- let dataUrl = ev . target . result ;
1350- // ضغط الصورة إذا كانت كبيرة
1351- if ( dataUrl . length > 500 * 1024 ) { // أكبر من 500 كيلوبايت
1352- try {
1353- dataUrl = await compressImage ( dataUrl , 300 , 300 , 0.7 ) ;
1354- } catch ( err ) {
1355- console . warn ( 'فشل ضغط الصورة' , err ) ;
1356- }
1357- }
1375+ try {
1376+ const dataUrl = await handleImageFile ( file ) ;
13581377 previewImg . src = dataUrl ;
13591378 preview . classList . remove ( 'hidden' ) ;
1360- // تخزين البيانات مؤقتاً في input hidden
13611379 fileInput . dataset . imageData = dataUrl ;
1362- } ;
1363- reader . readAsDataURL ( file ) ;
1380+ } catch ( err ) {
1381+ console . error ( 'خطأ في تحميل الصورة:' , err ) ;
1382+ showToast ( 'فشل تحميل الصورة' , 'error' ) ;
1383+ }
13641384 } else {
1365- // إذا لم يتم اختيار ملف، نستخدم الصورة القديمة
13661385 if ( imageSrc ) {
13671386 previewImg . src = imageSrc ;
13681387 preview . classList . remove ( 'hidden' ) ;
@@ -1376,19 +1395,23 @@ <h3 id="modal-title" class="font-extrabold text-lg">إضافة</h3>
13761395 }
13771396
13781397 function saveProduct ( id ) {
1379- const name = $ ( '#p-name' ) . value . trim ( ) ;
1380- if ( ! name ) return showToast ( 'ادخل الاسم' , 'error' ) ;
1381- const fileInput = document . getElementById ( 'p-image' ) ;
1382- let imageData = '' ;
1383- // إذا كان هناك صورة جديدة تم تحميلها، نأخذها من dataset
1384- if ( fileInput && fileInput . dataset . imageData ) {
1385- imageData = fileInput . dataset . imageData ;
1386- } else {
1387- // وإلا نستخدم الصورة القديمة من المنتج إن وجدت
1388- const existing = state . products . find ( p => p . id === id ) ;
1389- imageData = existing ? existing . image || '' : '' ;
1398+ try {
1399+ const name = $ ( '#p-name' ) . value . trim ( ) ;
1400+ if ( ! name ) return showToast ( 'ادخل الاسم' , 'error' ) ;
1401+
1402+ const fileInput = document . getElementById ( 'p-image' ) ;
1403+ let imageData = '' ;
1404+ if ( fileInput && fileInput . dataset && fileInput . dataset . imageData ) {
1405+ imageData = fileInput . dataset . imageData ;
1406+ } else {
1407+ const existing = state . products . find ( p => p . id === id ) ;
1408+ imageData = existing ? existing . image || '' : '' ;
1409+ }
1410+ actuallySaveProduct ( id , name , imageData ) ;
1411+ } catch ( err ) {
1412+ console . error ( 'خطأ في حفظ المنتج:' , err ) ;
1413+ showToast ( 'حدث خطأ أثناء حفظ المنتج' , 'error' ) ;
13901414 }
1391- actuallySaveProduct ( id , name , imageData ) ;
13921415 }
13931416
13941417 function actuallySaveProduct ( id , name , imageData ) {
0 commit comments