anuragshas commited on
Commit
4c1d2cd
·
1 Parent(s): 14ee8e9

fix: ui improvements

Browse files
Files changed (3) hide show
  1. index.html +117 -89
  2. script.js +170 -124
  3. style.css +31 -22
index.html CHANGED
@@ -17,8 +17,12 @@
17
  id="quotation-form"
18
  class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4"
19
  >
20
- <fieldset class="border border-gray-300 p-4 mb-4 rounded-lg">
21
- <legend class="text-lg font-semibold mb-2 text-gray-700">
 
 
 
 
22
  Your Company Details
23
  </legend>
24
  <input
@@ -58,8 +62,12 @@
58
  class="w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
59
  />
60
  </fieldset>
61
- <fieldset class="border border-gray-300 p-4 mb-4 rounded-lg">
62
- <legend class="text-lg font-semibold mb-2 text-gray-700">
 
 
 
 
63
  Customer Company Details
64
  </legend>
65
  <input
@@ -99,8 +107,12 @@
99
  class="w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
100
  />
101
  </fieldset>
102
- <fieldset class="border border-gray-300 p-4 mb-4 rounded-lg">
103
- <legend class="text-lg font-semibold mb-2 text-gray-700">
 
 
 
 
104
  Quotation Details
105
  </legend>
106
  <input
@@ -119,25 +131,31 @@
119
  class="w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
120
  />
121
  </fieldset>
122
- <fieldset class="border border-gray-300 p-4 mb-4 rounded-lg">
123
- <legend class="text-lg font-semibold mb-2 text-gray-700">
 
 
 
 
124
  Items
125
  </legend>
126
  <div
127
- class="bg-blue-50 border border-blue-200 rounded p-3 mb-4 text-sm text-blue-800"
128
  >
129
  <strong>💡 Tips:</strong>
130
- <ul class="list-disc list-inside mt-1 space-y-1">
131
  <li>
132
  Use
133
- <kbd class="bg-gray-200 px-1 rounded"
 
134
  >Enter</kbd
135
  >
136
  to move to the next field or add a new row
137
  </li>
138
  <li>
139
  Use
140
- <kbd class="bg-gray-200 px-1 rounded"
 
141
  >Ctrl+I</kbd
142
  >
143
  to quickly add a new item
@@ -152,73 +170,38 @@
152
  </li>
153
  </ul>
154
  </div>
155
- <div class="overflow-x-auto">
156
- <table
157
- id="items-table"
158
- class="w-full mb-2 border-collapse"
 
 
 
 
 
159
  >
160
- <thead class="hidden lg:table-header-group">
161
- <tr class="bg-gray-200">
162
- <th
163
- class="p-2 border border-gray-300 text-left text-sm font-semibold text-gray-700 w-16"
164
- >
165
- S.No
166
- </th>
167
- <th
168
- class="p-2 border border-gray-300 text-left text-sm font-semibold text-gray-700 w-2/5"
169
- >
170
- Description *
171
- </th>
172
- <th
173
- class="p-2 border border-gray-300 text-left text-sm font-semibold text-gray-700 w-24"
174
- >
175
- HSN Code
176
- </th>
177
- <th
178
- class="p-2 border border-gray-300 text-left text-sm font-semibold text-gray-700 w-20"
179
- >
180
- Qty *
181
- </th>
182
- <th
183
- class="p-2 border border-gray-300 text-left text-sm font-semibold text-gray-700 w-32"
184
- >
185
- Unit Price *
186
- </th>
187
- <th
188
- class="p-2 border border-gray-300 text-left text-sm font-semibold text-gray-700 w-28"
189
- >
190
- Discount (%)
191
- </th>
192
- <th
193
- class="p-2 border border-gray-300 text-left text-sm font-semibold text-gray-700 w-32"
194
- >
195
- Amount
196
- </th>
197
- <th
198
- class="p-2 border border-gray-300 text-left text-sm font-semibold text-gray-700 w-24"
199
- >
200
- Action
201
- </th>
202
- </tr>
203
- </thead>
204
- <tbody
205
- class="divide-y divide-gray-200 lg:table-row-group"
206
- ></tbody>
207
- </table>
208
  </div>
209
- <button
210
- type="button"
211
- id="add-item"
212
- class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded flex items-center gap-2 mt-2"
213
- >
214
- <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
215
- <path fill-rule="evenodd" d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z" clip-rule="evenodd" />
216
- </svg>
217
- Add Another Item
218
- </button>
219
  </fieldset>
220
- <fieldset class="border border-gray-300 p-4 mb-4 rounded-lg">
221
- <legend class="text-lg font-semibold mb-2 text-gray-700">
 
 
 
 
222
  Additional Charges
223
  </legend>
224
  <label class="block mb-2"
@@ -240,8 +223,12 @@
240
  class="w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
241
  /></label>
242
  </fieldset>
243
- <fieldset class="border border-gray-300 p-4 mb-4 rounded-lg">
244
- <legend class="text-lg font-semibold mb-2 text-gray-700">
 
 
 
 
245
  Bank Details
246
  </legend>
247
  <input
@@ -318,11 +305,22 @@
318
  <div
319
  id="storage-info-panel"
320
  class="absolute z-10 bg-white shadow-lg rounded-lg px-6 pt-4 pb-6 mb-4 hidden w-full max-w-md"
321
- style="top: 50px; right: 0;"
322
  >
323
  <h3 class="text-lg font-bold mb-3 flex items-center gap-2">
324
- <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
325
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.57-.588-3.756z" />
 
 
 
 
 
 
 
 
 
 
 
326
  </svg>
327
  Storage Information
328
  </h3>
@@ -358,9 +356,20 @@
358
  </p>
359
  </div>
360
  <div class="bg-gray-100 p-3 rounded-lg mt-3">
361
- <h4 class="font-semibold text-gray-800 flex items-center gap-1">
362
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
363
- <path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 3.001-1.742 3.001H4.42c-1.53 0-2.493-1.667-1.743-3.001l5.58-9.92zM10 13a1 1 0 110-2 1 1 0 010 2zm-1-8a1 1 0 00-1 1v3a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
 
 
 
 
 
 
 
 
 
 
 
364
  </svg>
365
  Security Notice
366
  </h4>
@@ -382,12 +391,31 @@
382
  </div>
383
  </div>
384
  </div>
385
- <div id="quotation-output" class="hidden fixed inset-0 bg-gray-900 bg-opacity-75 z-50 overflow-y-auto">
 
 
 
386
  <div class="flex items-center justify-center min-h-screen">
387
- <div class="bg-white p-8 rounded-lg shadow-2xl max-w-4xl w-full mx-auto my-8 relative">
388
- <button id="close-quotation" class="absolute top-4 right-4 text-gray-500 hover:text-gray-800">
389
- <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
390
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
  </svg>
392
  </button>
393
  <div id="quotation-content"></div>
 
17
  id="quotation-form"
18
  class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4"
19
  >
20
+ <fieldset
21
+ class="border border-gray-300 p-4 mb-4 rounded-lg"
22
+ >
23
+ <legend
24
+ class="text-lg font-semibold mb-2 text-gray-700"
25
+ >
26
  Your Company Details
27
  </legend>
28
  <input
 
62
  class="w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
63
  />
64
  </fieldset>
65
+ <fieldset
66
+ class="border border-gray-300 p-4 mb-4 rounded-lg"
67
+ >
68
+ <legend
69
+ class="text-lg font-semibold mb-2 text-gray-700"
70
+ >
71
  Customer Company Details
72
  </legend>
73
  <input
 
107
  class="w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
108
  />
109
  </fieldset>
110
+ <fieldset
111
+ class="border border-gray-300 p-4 mb-4 rounded-lg"
112
+ >
113
+ <legend
114
+ class="text-lg font-semibold mb-2 text-gray-700"
115
+ >
116
  Quotation Details
117
  </legend>
118
  <input
 
131
  class="w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
132
  />
133
  </fieldset>
134
+ <fieldset
135
+ class="border border-gray-300 p-4 mb-4 rounded-lg"
136
+ >
137
+ <legend
138
+ class="text-lg font-semibold mb-2 text-gray-700"
139
+ >
140
  Items
141
  </legend>
142
  <div
143
+ class="bg-blue-50 border border-blue-200 rounded p-2 mb-3 text-xs text-blue-800"
144
  >
145
  <strong>💡 Tips:</strong>
146
+ <ul class="list-disc list-inside mt-1 space-y-0.5">
147
  <li>
148
  Use
149
+ <kbd
150
+ class="bg-gray-200 px-1 rounded text-xs"
151
  >Enter</kbd
152
  >
153
  to move to the next field or add a new row
154
  </li>
155
  <li>
156
  Use
157
+ <kbd
158
+ class="bg-gray-200 px-1 rounded text-xs"
159
  >Ctrl+I</kbd
160
  >
161
  to quickly add a new item
 
170
  </li>
171
  </ul>
172
  </div>
173
+ <!-- Items Container -->
174
+ <div id="items-container" class="space-y-4">
175
+ <!-- Items will be dynamically added here -->
176
+ </div>
177
+ <div class="flex justify-center mt-4">
178
+ <button
179
+ type="button"
180
+ id="add-item"
181
+ class="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-md flex items-center gap-2 shadow-sm"
182
  >
183
+ <svg
184
+ xmlns="http://www.w3.org/2000/svg"
185
+ class="h-4 w-4"
186
+ viewBox="0 0 20 20"
187
+ fill="currentColor"
188
+ >
189
+ <path
190
+ fill-rule="evenodd"
191
+ d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z"
192
+ clip-rule="evenodd"
193
+ />
194
+ </svg>
195
+ Add Another Item
196
+ </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  </div>
 
 
 
 
 
 
 
 
 
 
198
  </fieldset>
199
+ <fieldset
200
+ class="border border-gray-300 p-4 mb-4 rounded-lg"
201
+ >
202
+ <legend
203
+ class="text-lg font-semibold mb-2 text-gray-700"
204
+ >
205
  Additional Charges
206
  </legend>
207
  <label class="block mb-2"
 
223
  class="w-full p-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
224
  /></label>
225
  </fieldset>
226
+ <fieldset
227
+ class="border border-gray-300 p-4 mb-4 rounded-lg"
228
+ >
229
+ <legend
230
+ class="text-lg font-semibold mb-2 text-gray-700"
231
+ >
232
  Bank Details
233
  </legend>
234
  <input
 
305
  <div
306
  id="storage-info-panel"
307
  class="absolute z-10 bg-white shadow-lg rounded-lg px-6 pt-4 pb-6 mb-4 hidden w-full max-w-md"
308
+ style="top: 50px; right: 0"
309
  >
310
  <h3 class="text-lg font-bold mb-3 flex items-center gap-2">
311
+ <svg
312
+ xmlns="http://www.w3.org/2000/svg"
313
+ class="h-6 w-6 text-gray-600"
314
+ fill="none"
315
+ viewBox="0 0 24 24"
316
+ stroke="currentColor"
317
+ >
318
+ <path
319
+ stroke-linecap="round"
320
+ stroke-linejoin="round"
321
+ stroke-width="2"
322
+ d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.57-.588-3.756z"
323
+ />
324
  </svg>
325
  Storage Information
326
  </h3>
 
356
  </p>
357
  </div>
358
  <div class="bg-gray-100 p-3 rounded-lg mt-3">
359
+ <h4
360
+ class="font-semibold text-gray-800 flex items-center gap-1"
361
+ >
362
+ <svg
363
+ xmlns="http://www.w3.org/2000/svg"
364
+ class="h-4 w-4"
365
+ viewBox="0 0 20 20"
366
+ fill="currentColor"
367
+ >
368
+ <path
369
+ fill-rule="evenodd"
370
+ d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 3.001-1.742 3.001H4.42c-1.53 0-2.493-1.667-1.743-3.001l5.58-9.92zM10 13a1 1 0 110-2 1 1 0 010 2zm-1-8a1 1 0 00-1 1v3a1 1 0 102 0V6a1 1 0 00-1-1z"
371
+ clip-rule="evenodd"
372
+ />
373
  </svg>
374
  Security Notice
375
  </h4>
 
391
  </div>
392
  </div>
393
  </div>
394
+ <div
395
+ id="quotation-output"
396
+ class="hidden fixed inset-0 bg-gray-900 bg-opacity-75 z-50 overflow-y-auto"
397
+ >
398
  <div class="flex items-center justify-center min-h-screen">
399
+ <div
400
+ class="bg-white p-8 rounded-lg shadow-2xl max-w-4xl w-full mx-auto my-8 relative"
401
+ >
402
+ <button
403
+ id="close-quotation"
404
+ class="absolute top-4 right-4 text-gray-500 hover:text-gray-800"
405
+ >
406
+ <svg
407
+ xmlns="http://www.w3.org/2000/svg"
408
+ class="h-6 w-6"
409
+ fill="none"
410
+ viewBox="0 0 24 24"
411
+ stroke="currentColor"
412
+ >
413
+ <path
414
+ stroke-linecap="round"
415
+ stroke-linejoin="round"
416
+ stroke-width="2"
417
+ d="M6 18L18 6M6 6l12 12"
418
+ />
419
  </svg>
420
  </button>
421
  <div id="quotation-content"></div>
script.js CHANGED
@@ -58,7 +58,7 @@ function numberToWords(num) {
58
  if (typeof document !== "undefined") {
59
  document.addEventListener("DOMContentLoaded", function () {
60
  const addItemBtn = document.getElementById("add-item");
61
- const itemsTableBody = document.querySelector("#items-table tbody");
62
  const form = document.getElementById("quotation-form");
63
  const output = document.getElementById("quotation-output");
64
  const previewContent = document.getElementById("preview-content");
@@ -93,27 +93,29 @@ if (typeof document !== "undefined") {
93
  branch: data.get("bank-branch"),
94
  };
95
  const items = [];
96
- itemsTableBody.querySelectorAll("tr").forEach((row) => {
97
- const desc = row.querySelector(".item-desc").value.trim();
98
  if (desc) {
99
- // Only include rows with description
100
  items.push({
101
  description: desc,
102
- hsn: row.querySelector(".item-hsn").value,
103
  qty:
104
- parseFloat(row.querySelector(".item-qty").value) ||
105
  0,
106
  price:
107
  parseFloat(
108
- row.querySelector(".item-price").value,
109
  ) || 0,
110
  discount:
111
  parseFloat(
112
- row.querySelector(".item-discount").value,
113
  ) || 0,
114
  amount:
115
  parseFloat(
116
- row.querySelector(".item-amount").textContent,
 
 
117
  ) || 0,
118
  });
119
  }
@@ -263,76 +265,78 @@ if (typeof document !== "undefined") {
263
  }
264
 
265
  function updatePreview() {
 
266
  const html = generateQuotationHTML(form);
267
  previewContent.innerHTML = html;
268
  }
269
 
270
  function updateSerialNumbers() {
271
- itemsTableBody.querySelectorAll("tr").forEach((row, i) => {
272
- row.querySelector(".item-slno").textContent = i + 1;
273
  });
274
  }
275
 
276
  function validateItemInput(input) {
277
  const value = input.value;
278
- const type = input.type;
279
 
280
- input.classList.remove(
281
- "border-red-500",
282
- "bg-red-100",
283
- "border-green-500",
284
- );
285
 
286
- if (type === "number" && value !== "") {
287
- const num = parseFloat(value);
288
- if (isNaN(num) || num < 0) {
289
- input.classList.add("border-red-500", "bg-red-100");
290
- return false;
291
- } else {
292
- input.classList.add("border-green-500");
 
 
 
 
 
 
 
 
 
 
 
 
293
  }
294
- } else if (
295
- type === "text" &&
296
- input.hasAttribute("required") &&
297
- value.trim() === ""
298
- ) {
299
- input.classList.add("border-red-500", "bg-red-100");
300
- return false;
301
- }
302
-
303
- return true;
304
  }
305
 
306
  function handleKeyNavigation(event) {
307
  if (event.key === "Tab" || event.key === "Enter") {
308
- const currentRow = event.target.closest("tr");
309
- const inputs = Array.from(currentRow.querySelectorAll("input"));
 
 
310
  const currentIndex = inputs.indexOf(event.target);
311
 
312
  if (event.key === "Enter") {
313
  event.preventDefault();
314
 
315
- // If we're at the last input in the row and it's the last row, add a new row
316
  if (currentIndex === inputs.length - 1) {
317
- const allRows = Array.from(
318
- itemsTableBody.querySelectorAll("tr"),
319
  );
320
- const currentRowIndex = allRows.indexOf(currentRow);
321
 
322
- if (currentRowIndex === allRows.length - 1) {
323
- // Last row - add new row and focus first input
324
  addItemRow();
325
  setTimeout(() => {
326
- const newRow = itemsTableBody.lastElementChild;
327
- newRow.querySelector(".item-desc").focus();
328
  }, 50);
329
  } else {
330
- // Not last row - focus next row's first input
331
- const nextRow = allRows[currentRowIndex + 1];
332
- nextRow.querySelector(".item-desc").focus();
333
  }
334
  } else {
335
- // Move to next input in same row
336
  inputs[currentIndex + 1].focus();
337
  }
338
  }
@@ -340,54 +344,76 @@ if (typeof document !== "undefined") {
340
  }
341
 
342
  function addItemRow() {
343
- const row = document.createElement("tr");
344
- row.className =
345
- "block lg:table-row border-b-2 lg:border-b-0 lg:border-gray-200";
346
- row.innerHTML = `
347
- <td class="item-slno p-2 border border-gray-300 text-center font-semibold text-gray-600 bg-gray-50 w-16 hidden lg:table-cell" data-label="S.No"></td>
348
- <td class="p-2 border border-gray-300 block lg:table-cell" data-label="Description">
349
- <div class="tooltip">
350
- <input type="text" class="item-desc w-full p-2 border border-gray-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Enter item description" required>
351
- <span class="tooltiptext">Describe the item or service being quoted</span>
352
- </div>
353
- </td>
354
- <td class="p-2 border border-gray-300 block lg:table-cell" data-label="HSN Code">
355
- <div class="tooltip">
356
- <input type="text" class="item-hsn w-full p-2 border border-gray-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="HSN Code">
357
- <span class="tooltiptext">Harmonized System of Nomenclature code for tax purposes</span>
358
- </div>
359
- </td>
360
- <td class="p-2 border border-gray-300 block lg:table-cell" data-label="Qty">
361
- <div class="tooltip">
362
- <input type="number" class="item-qty w-full p-2 border border-gray-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" value="1" min="0" step="0.01" required>
363
- <span class="tooltiptext">Quantity of items</span>
364
- </div>
365
- </td>
366
- <td class="p-2 border border-gray-300 block lg:table-cell" data-label="Unit Price">
367
- <div class="tooltip">
368
- <input type="number" class="item-price w-full p-2 border border-gray-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" value="0" min="0" step="0.01" required>
369
- <span class="tooltiptext">Price per unit before discount</span>
370
- </div>
371
- </td>
372
- <td class="p-2 border border-gray-300 block lg:table-cell" data-label="Discount (%)">
373
- <div class="tooltip">
374
- <input type="number" class="item-discount w-full p-2 border border-gray-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" value="0" min="0" max="100" step="0.01" placeholder="0">
375
- <span class="tooltiptext">Discount percentage (0-100)</span>
376
- </div>
377
- </td>
378
- <td class="item-amount p-2 border border-gray-300 text-right font-semibold text-green-700 bg-green-50 font-mono block lg:table-cell" data-label="Amount">0.00</td>
379
- <td class="p-2 border border-gray-300 block lg:table-cell" data-label="Action">
380
- <button type="button" class="remove-item bg-red-500 hover:bg-red-600 text-white text-xs font-bold py-1 px-2 rounded flex items-center gap-1" title="Remove this item">
381
- <span>×</span> Remove
382
- </button>
383
- </td>
384
- `;
385
-
386
- itemsTableBody.appendChild(row);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
387
  updateSerialNumbers();
388
 
389
  // Add event listeners to inputs
390
- const inputs = row.querySelectorAll("input");
391
  inputs.forEach((input) => {
392
  input.addEventListener("input", (event) => {
393
  validateItemInput(event.target);
@@ -403,15 +429,15 @@ if (typeof document !== "undefined") {
403
  });
404
 
405
  // Add remove button listener
406
- row.querySelector(".remove-item").addEventListener(
407
  "click",
408
  (_event) => {
409
- if (itemsTableBody.children.length > 1) {
410
- row.remove();
411
  updateSerialNumbers();
412
  updatePreview();
413
  } else {
414
- // If it's the last row, just clear it instead of removing
415
  inputs.forEach((input) => {
416
  if (input.type === "number") {
417
  input.value = input.classList.contains(
@@ -423,53 +449,50 @@ if (typeof document !== "undefined") {
423
  input.value = "";
424
  }
425
  input.classList.remove(
426
- "input-error",
427
- "input-success",
428
  );
429
  });
430
- row.querySelector(".item-amount").textContent = "0.00";
 
431
  updatePreview();
432
  }
433
  },
434
  );
435
 
436
  // Auto-calculate initial amount
437
- updateItemAmount({ target: row.querySelector(".item-qty") });
438
  }
439
 
440
  function updateItemAmount(event) {
441
- const row = event.target.closest("tr");
442
- if (!row) return;
443
 
444
- const qty = parseFloat(row.querySelector(".item-qty").value) || 0;
445
  const price =
446
- parseFloat(row.querySelector(".item-price").value) || 0;
447
  const discountRate =
448
- parseFloat(row.querySelector(".item-discount").value) || 0;
449
 
450
  const discountAmount = (qty * price * discountRate) / 100;
451
  const amount = qty * price - discountAmount;
452
 
453
- row.querySelector(".item-amount").textContent = amount.toFixed(2);
 
454
 
455
- // Add visual feedback for calculated amount
456
- const amountCell = row.querySelector(".item-amount");
457
- amountCell.classList.toggle("bg-green-50", amount > 0);
458
- amountCell.classList.toggle("text-green-700", amount > 0);
459
- amountCell.classList.toggle("bg-red-50", amount <= 0);
460
- amountCell.classList.toggle("text-red-700", amount <= 0);
461
  }
462
 
463
- // Enhanced Add Item button
464
  addItemBtn.addEventListener("click", () => {
465
  addItemRow();
466
  updatePreview();
467
 
468
- // Focus the new row's first input
469
- const newRow = itemsTableBody.lastElementChild;
470
- newRow.scrollIntoView({ behavior: "smooth", block: "center" });
471
  setTimeout(() => {
472
- newRow.querySelector(".item-desc").focus();
473
  }, 100);
474
  });
475
 
@@ -488,9 +511,9 @@ if (typeof document !== "undefined") {
488
 
489
  // Validate that we have at least one item with description
490
  const hasValidItems = Array.from(
491
- itemsTableBody.querySelectorAll("tr"),
492
- ).some((row) => {
493
- return row.querySelector(".item-desc").value.trim() !== "";
494
  });
495
 
496
  if (!hasValidItems) {
@@ -500,14 +523,37 @@ if (typeof document !== "undefined") {
500
  return;
501
  }
502
 
503
- const quotationContent = document.getElementById("quotation-content");
 
504
  const html = generateQuotationHTML(form);
505
  quotationContent.innerHTML = html;
506
  output.classList.remove("hidden");
507
  });
508
 
509
- document.getElementById("close-quotation").addEventListener("click", () => {
510
- output.classList.add("hidden");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
511
  });
512
 
513
  // Initial preview
 
58
  if (typeof document !== "undefined") {
59
  document.addEventListener("DOMContentLoaded", function () {
60
  const addItemBtn = document.getElementById("add-item");
61
+ const itemsContainer = document.getElementById("items-container");
62
  const form = document.getElementById("quotation-form");
63
  const output = document.getElementById("quotation-output");
64
  const previewContent = document.getElementById("preview-content");
 
93
  branch: data.get("bank-branch"),
94
  };
95
  const items = [];
96
+ itemsContainer.querySelectorAll(".item-card").forEach((card) => {
97
+ const desc = card.querySelector(".item-desc").value.trim();
98
  if (desc) {
99
+ // Only include cards with description
100
  items.push({
101
  description: desc,
102
+ hsn: card.querySelector(".item-hsn").value,
103
  qty:
104
+ parseFloat(card.querySelector(".item-qty").value) ||
105
  0,
106
  price:
107
  parseFloat(
108
+ card.querySelector(".item-price").value,
109
  ) || 0,
110
  discount:
111
  parseFloat(
112
+ card.querySelector(".item-discount").value,
113
  ) || 0,
114
  amount:
115
  parseFloat(
116
+ card
117
+ .querySelector(".item-amount")
118
+ .textContent.replace("₹", ""),
119
  ) || 0,
120
  });
121
  }
 
265
  }
266
 
267
  function updatePreview() {
268
+ const form = document.getElementById("quotation-form");
269
  const html = generateQuotationHTML(form);
270
  previewContent.innerHTML = html;
271
  }
272
 
273
  function updateSerialNumbers() {
274
+ itemsContainer.querySelectorAll(".item-card").forEach((card, i) => {
275
+ card.querySelector(".item-slno").textContent = i + 1;
276
  });
277
  }
278
 
279
  function validateItemInput(input) {
280
  const value = input.value;
 
281
 
282
+ // Remove existing classes
283
+ input.classList.remove("border-red-500", "border-green-500");
 
 
 
284
 
285
+ // Remove loading effect
286
+ setTimeout(() => {
287
+ if (input.classList.contains("item-desc")) {
288
+ if (value.trim().length < 3) {
289
+ input.classList.add("border-red-500");
290
+ // Removed tooltip error display
291
+ } else {
292
+ input.classList.add("border-green-500");
293
+ // Removed tooltip error display
294
+ }
295
+ } else if (input.type === "number") {
296
+ const numValue = parseFloat(value);
297
+ if (isNaN(numValue) || numValue < 0) {
298
+ input.classList.add("border-red-500");
299
+ // Removed tooltip error display
300
+ } else {
301
+ input.classList.add("border-green-500");
302
+ // Removed tooltip error display
303
+ }
304
  }
305
+ }, 50);
 
 
 
 
 
 
 
 
 
306
  }
307
 
308
  function handleKeyNavigation(event) {
309
  if (event.key === "Tab" || event.key === "Enter") {
310
+ const currentCard = event.target.closest(".item-card");
311
+ const inputs = Array.from(
312
+ currentCard.querySelectorAll("input, textarea"),
313
+ );
314
  const currentIndex = inputs.indexOf(event.target);
315
 
316
  if (event.key === "Enter") {
317
  event.preventDefault();
318
 
319
+ // If we're at the last input in the card and it's the last card, add a new card
320
  if (currentIndex === inputs.length - 1) {
321
+ const allCards = Array.from(
322
+ itemsContainer.querySelectorAll(".item-card"),
323
  );
324
+ const currentCardIndex = allCards.indexOf(currentCard);
325
 
326
+ if (currentCardIndex === allCards.length - 1) {
327
+ // Last card - add new card and focus first input
328
  addItemRow();
329
  setTimeout(() => {
330
+ const newCard = itemsContainer.lastElementChild;
331
+ newCard.querySelector(".item-desc").focus();
332
  }, 50);
333
  } else {
334
+ // Not last card - focus next card's first input
335
+ const nextCard = allCards[currentCardIndex + 1];
336
+ nextCard.querySelector(".item-desc").focus();
337
  }
338
  } else {
339
+ // Move to next input in same card
340
  inputs[currentIndex + 1].focus();
341
  }
342
  }
 
344
  }
345
 
346
  function addItemRow() {
347
+ const card = document.createElement("div");
348
+ card.className =
349
+ "item-card bg-white border border-gray-200 rounded-lg p-4 shadow-sm";
350
+ card.innerHTML = `
351
+ <!-- Unified Card Layout for All Screen Sizes -->
352
+ <div class="space-y-4">
353
+ <div class="flex items-center justify-between border-b border-gray-100 pb-3">
354
+ <div class="flex items-center gap-2">
355
+ <span class="item-slno bg-blue-100 text-blue-800 text-sm font-semibold px-2 py-1 rounded-full min-w-[1.5rem] text-center"></span>
356
+ <span class="text-base font-medium text-gray-800">Item Details</span>
357
+ </div>
358
+ <button type="button" class="remove-item bg-red-500 hover:bg-red-600 text-white text-sm font-medium py-1.5 px-3 rounded-md flex items-center gap-1" title="Remove this item">
359
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
360
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1-1H8a1 1 0 00-1 1v3M4 7h16" />
361
+ </svg>
362
+ Remove
363
+ </button>
364
+ </div>
365
+
366
+ <div class="space-y-3">
367
+ <div>
368
+ <label class="block text-sm font-medium text-gray-700 mb-1">Description *</label>
369
+ <textarea class="item-desc w-full p-3 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none" rows="2" placeholder="Enter item description..." required></textarea>
370
+ </div>
371
+
372
+ <div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
373
+ <div>
374
+ <label class="block text-sm font-medium text-gray-700 mb-1">HSN Code</label>
375
+ <input type="text" class="item-hsn w-full p-3 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" placeholder="HSN Code">
376
+ </div>
377
+
378
+ <div>
379
+ <label class="block text-sm font-medium text-gray-700 mb-1">Quantity *</label>
380
+ <input type="number" class="item-qty w-full p-3 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" value="1" min="0" step="0.01" required>
381
+ </div>
382
+ </div>
383
+
384
+ <div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
385
+ <div>
386
+ <label class="block text-sm font-medium text-gray-700 mb-1">Unit Price *</label>
387
+ <div class="relative">
388
+ <span class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500 text-sm">₹</span>
389
+ <input type="number" class="item-price w-full pl-7 pr-3 py-3 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" value="0" min="0" step="0.01" required>
390
+ </div>
391
+ </div>
392
+
393
+ <div>
394
+ <label class="block text-sm font-medium text-gray-700 mb-1">Discount (%)</label>
395
+ <div class="relative">
396
+ <input type="number" class="item-discount w-full pr-7 pl-3 py-3 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" value="0" min="0" max="100" step="0.01" placeholder="0">
397
+ <span class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-500 text-sm">%</span>
398
+ </div>
399
+ </div>
400
+ </div>
401
+
402
+ <div class="bg-green-50 border border-green-200 rounded-md p-3">
403
+ <div class="flex items-center justify-between">
404
+ <span class="text-sm font-medium text-green-800">Total Amount</span>
405
+ <span class="item-amount text-lg font-bold text-green-700 font-mono">₹0.00</span>
406
+ </div>
407
+ </div>
408
+ </div>
409
+ </div>
410
+ `;
411
+
412
+ itemsContainer.appendChild(card);
413
  updateSerialNumbers();
414
 
415
  // Add event listeners to inputs
416
+ const inputs = card.querySelectorAll("input, textarea");
417
  inputs.forEach((input) => {
418
  input.addEventListener("input", (event) => {
419
  validateItemInput(event.target);
 
429
  });
430
 
431
  // Add remove button listener
432
+ card.querySelector(".remove-item").addEventListener(
433
  "click",
434
  (_event) => {
435
+ if (itemsContainer.children.length > 1) {
436
+ card.remove();
437
  updateSerialNumbers();
438
  updatePreview();
439
  } else {
440
+ // If it's the last card, just clear it instead of removing
441
  inputs.forEach((input) => {
442
  if (input.type === "number") {
443
  input.value = input.classList.contains(
 
449
  input.value = "";
450
  }
451
  input.classList.remove(
452
+ "border-red-500",
453
+ "border-green-500",
454
  );
455
  });
456
+ card.querySelector(".item-amount").textContent =
457
+ "₹0.00";
458
  updatePreview();
459
  }
460
  },
461
  );
462
 
463
  // Auto-calculate initial amount
464
+ updateItemAmount({ target: card.querySelector(".item-qty") });
465
  }
466
 
467
  function updateItemAmount(event) {
468
+ const card = event.target.closest(".item-card");
469
+ if (!card) return;
470
 
471
+ const qty = parseFloat(card.querySelector(".item-qty").value) || 0;
472
  const price =
473
+ parseFloat(card.querySelector(".item-price").value) || 0;
474
  const discountRate =
475
+ parseFloat(card.querySelector(".item-discount").value) || 0;
476
 
477
  const discountAmount = (qty * price * discountRate) / 100;
478
  const amount = qty * price - discountAmount;
479
 
480
+ card.querySelector(".item-amount").textContent =
481
+ `₹${amount.toFixed(2)}`;
482
 
483
+ // Remove complex visual feedback
 
 
 
 
 
484
  }
485
 
486
+ // Add Item button
487
  addItemBtn.addEventListener("click", () => {
488
  addItemRow();
489
  updatePreview();
490
 
491
+ // Focus the new card's first input
492
+ const newCard = itemsContainer.lastElementChild;
493
+ newCard.scrollIntoView({ behavior: "smooth", block: "center" });
494
  setTimeout(() => {
495
+ newCard.querySelector(".item-desc").focus();
496
  }, 100);
497
  });
498
 
 
511
 
512
  // Validate that we have at least one item with description
513
  const hasValidItems = Array.from(
514
+ itemsContainer.querySelectorAll(".item-card"),
515
+ ).some((card) => {
516
+ return card.querySelector(".item-desc").value.trim() !== "";
517
  });
518
 
519
  if (!hasValidItems) {
 
523
  return;
524
  }
525
 
526
+ const quotationContent =
527
+ document.getElementById("quotation-content");
528
  const html = generateQuotationHTML(form);
529
  quotationContent.innerHTML = html;
530
  output.classList.remove("hidden");
531
  });
532
 
533
+ document
534
+ .getElementById("close-quotation")
535
+ .addEventListener("click", () => {
536
+ output.classList.add("hidden");
537
+ });
538
+
539
+ // Enhanced keyboard shortcuts
540
+ document.addEventListener("keydown", (event) => {
541
+ if (event.ctrlKey || event.metaKey) {
542
+ if (event.key === "i") {
543
+ event.preventDefault();
544
+ addItemBtn.click();
545
+ }
546
+ }
547
+ });
548
+
549
+ // Auto-save functionality
550
+ let saveTimeout;
551
+ form.addEventListener("input", () => {
552
+ clearTimeout(saveTimeout);
553
+ saveTimeout = setTimeout(() => {
554
+ // Auto-save form data (existing functionality)
555
+ updatePreview();
556
+ }, 500);
557
  });
558
 
559
  // Initial preview
style.css CHANGED
@@ -1,34 +1,43 @@
1
  @import "tailwindcss";
2
 
3
- /* Tooltips */
4
- .tooltip {
 
 
5
  position: relative;
6
- display: inline-block;
7
  }
8
 
9
- .tooltip .tooltiptext {
10
- visibility: hidden;
11
- width: 200px;
12
- background-color: #374151;
13
- color: white;
14
- text-align: center;
15
- border-radius: 6px;
16
- padding: 5px;
17
- position: absolute;
18
- z-index: 1;
19
- bottom: 125%;
20
- left: 50%;
21
- margin-left: -100px;
22
- opacity: 0;
23
- transition: opacity 0.3s;
24
- font-size: 0.75rem;
25
  }
26
 
27
- .tooltip:hover .tooltiptext {
28
- visibility: visible;
29
- opacity: 1;
 
 
 
 
 
 
 
30
  }
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  .quotation-print {
33
  max-width: 800px;
34
  margin: auto;
 
1
  @import "tailwindcss";
2
 
3
+ /* Card Layout Optimizations for All Screen Sizes */
4
+ .item-card {
5
+ border: 1px solid #e5e7eb;
6
+ margin-bottom: 1rem;
7
  position: relative;
8
+ overflow: hidden;
9
  }
10
 
11
+ .item-card input,
12
+ .item-card textarea {
13
+ font-size: 16px; /* Prevent zoom on iOS */
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  }
15
 
16
+ /* Enhanced spacing for larger screens */
17
+ @media (width >= 768px) {
18
+ .item-card {
19
+ margin-bottom: 1rem;
20
+ }
21
+
22
+ .item-card input,
23
+ .item-card textarea {
24
+ font-size: 16px;
25
+ }
26
  }
27
 
28
+ @media (width >= 768px) {
29
+ .item-card .grid-cols-1 {
30
+ grid-template-columns: repeat(2, 1fr);
31
+ }
32
+ }
33
+
34
+ /* Enhanced amount display */
35
+ .item-amount {
36
+ text-shadow: 0 1px 2px rgb(0 0 0 / 10%);
37
+ }
38
+
39
+ /* Removed scroll enhancement and focus ring */
40
+
41
  .quotation-print {
42
  max-width: 800px;
43
  margin: auto;