@per-diem-calculator/vanilla 1.0.1 → 1.0.2

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.
Files changed (102) hide show
  1. package/dist/index.js +11034 -0
  2. package/dist/index.umd.cjs +1384 -0
  3. package/package.json +12 -2
  4. package/.prettierrc +0 -17
  5. package/eslint.config.js +0 -29
  6. package/index.html +0 -11
  7. package/src/css/_styles.css +0 -8
  8. package/src/css/colors.css +0 -45
  9. package/src/css/fonts.css +0 -9
  10. package/src/css/rows/_heights.css +0 -6
  11. package/src/css/rows/_index.css +0 -15
  12. package/src/css/rows/add.css +0 -18
  13. package/src/css/rows/animate-btns.css +0 -18
  14. package/src/css/rows/animate-row-close.css +0 -18
  15. package/src/css/rows/animate-row-open.css +0 -14
  16. package/src/css/rows/animate-row-other.css +0 -5
  17. package/src/css/rows/btn-add-row.css +0 -41
  18. package/src/css/rows/btn-delete.css +0 -22
  19. package/src/css/rows/btn-expenses-calculate.css +0 -22
  20. package/src/css/rows/btn-expenses-category.css +0 -22
  21. package/src/css/rows/delete.css +0 -10
  22. package/src/css/rows/details.css +0 -22
  23. package/src/css/rows/expense.css +0 -18
  24. package/src/css/rows/location.css +0 -34
  25. package/src/css/rows/summary.css +0 -22
  26. package/src/css/tom-select/defaults.css +0 -530
  27. package/src/css/tom-select/overrides.css +0 -55
  28. package/src/css/tw-shadow-props.css +0 -50
  29. package/src/index.ts +0 -1
  30. package/src/ts/components/Button/Button.ts +0 -50
  31. package/src/ts/components/Button/template.html +0 -34
  32. package/src/ts/components/ExpenseRow/ExpenseRow.ts +0 -397
  33. package/src/ts/components/ExpenseRow/template.html +0 -260
  34. package/src/ts/components/Label/Label.ts +0 -45
  35. package/src/ts/components/Label/template.html +0 -1
  36. package/src/ts/components/LocationCategory/LocationCategory.ts +0 -226
  37. package/src/ts/components/LocationCategory/template.html +0 -520
  38. package/src/ts/components/LocationDate/LocationDate.ts +0 -366
  39. package/src/ts/components/LocationDate/template.html +0 -27
  40. package/src/ts/components/LocationSelect/LocationSelect.ts +0 -299
  41. package/src/ts/components/LocationSelect/template.html +0 -45
  42. package/src/ts/components/index.ts +0 -6
  43. package/src/ts/controller.ts +0 -193
  44. package/src/ts/model.ts +0 -163
  45. package/src/ts/types/config.ts +0 -22
  46. package/src/ts/types/dates.ts +0 -82
  47. package/src/ts/types/expenses.ts +0 -73
  48. package/src/ts/types/locations.ts +0 -25
  49. package/src/ts/utils/config/configDefault.ts +0 -13
  50. package/src/ts/utils/config/index.ts +0 -12
  51. package/src/ts/utils/config/numbers.ts +0 -24
  52. package/src/ts/utils/config/sanitizeConfig.ts +0 -39
  53. package/src/ts/utils/dates/INPUT_DATE_MINMAX.ts +0 -5
  54. package/src/ts/utils/dates/YEAR_REGEX.ts +0 -4
  55. package/src/ts/utils/dates/getDateSlice.ts +0 -54
  56. package/src/ts/utils/dates/getValidAPIYear.ts +0 -17
  57. package/src/ts/utils/dates/index.ts +0 -19
  58. package/src/ts/utils/dates/isDateRaw.ts +0 -90
  59. package/src/ts/utils/dates/isShortMonth.ts +0 -24
  60. package/src/ts/utils/dates/isYYYY.ts +0 -10
  61. package/src/ts/utils/dates/offsetDateString.ts +0 -17
  62. package/src/ts/utils/expenses/INTL_MIE_RATES.ts +0 -2125
  63. package/src/ts/utils/expenses/createExpenseObjs.ts +0 -35
  64. package/src/ts/utils/expenses/getLodgingRateDomestic.ts +0 -73
  65. package/src/ts/utils/expenses/getLodgingRateIntl.ts +0 -119
  66. package/src/ts/utils/expenses/getMieRates.ts +0 -84
  67. package/src/ts/utils/expenses/index.ts +0 -5
  68. package/src/ts/utils/expenses/parseIntlLodgingRates.ts +0 -124
  69. package/src/ts/utils/expenses/returnValidStateExpense.ts +0 -46
  70. package/src/ts/utils/fetch/fetchJsonGSA.ts +0 -29
  71. package/src/ts/utils/fetch/fetchXmlDOD.ts +0 -38
  72. package/src/ts/utils/fetch/index.ts +0 -3
  73. package/src/ts/utils/fetch/memoize.ts +0 -46
  74. package/src/ts/utils/fetch/parseXml.ts +0 -19
  75. package/src/ts/utils/locations/getCitiesDomestic.ts +0 -48
  76. package/src/ts/utils/locations/getCitiesIntl.ts +0 -63
  77. package/src/ts/utils/locations/getCountriesDomestic.ts +0 -237
  78. package/src/ts/utils/locations/getCountriesIntl.ts +0 -34
  79. package/src/ts/utils/locations/index.ts +0 -6
  80. package/src/ts/utils/locations/keepUniqueLocations.ts +0 -12
  81. package/src/ts/utils/locations/locationKeys.ts +0 -10
  82. package/src/ts/utils/locations/returnValidStateLocation.ts +0 -13
  83. package/src/ts/utils/locations/sortLocations.ts +0 -19
  84. package/src/ts/utils/misc/USD.ts +0 -4
  85. package/src/ts/utils/misc/debounce.ts +0 -22
  86. package/src/ts/utils/misc/handlePointerDown.ts +0 -3
  87. package/src/ts/utils/misc/handlePointerUp.ts +0 -22
  88. package/src/ts/utils/misc/inPrimitiveType.ts +0 -4
  89. package/src/ts/utils/misc/index.ts +0 -6
  90. package/src/ts/utils/misc/wait.ts +0 -4
  91. package/src/ts/utils/styles/applyStyles.ts +0 -19
  92. package/src/ts/utils/styles/highlightInput.ts +0 -15
  93. package/src/ts/utils/styles/index.ts +0 -3
  94. package/src/ts/utils/styles/removeStyles.ts +0 -14
  95. package/src/ts/views/Expense/Expense.ts +0 -465
  96. package/src/ts/views/Expense/template.html +0 -176
  97. package/src/ts/views/Location/Location.ts +0 -763
  98. package/src/ts/views/Location/template-row.html +0 -146
  99. package/src/ts/views/Location/template.html +0 -130
  100. package/src/ts/views/index.ts +0 -2
  101. package/tsconfig.json +0 -27
  102. package/vite.config.ts +0 -12
@@ -1,465 +0,0 @@
1
- // Types
2
- import type { DateRaw } from '../../types/dates';
3
- import {
4
- StateExpenseItemValid,
5
- StateExpenseItemUpdate,
6
- } from '../../types/expenses';
7
- import type { ConfigSectionText } from '../../types/config';
8
-
9
- // Utils
10
- import {
11
- USD,
12
- handlePointerDown,
13
- handlePointerUp,
14
- debounce,
15
- wait,
16
- } from '../../utils/misc';
17
- import { applyStyles, removeStyles } from '../../utils/styles';
18
- import { getDD, getMM, getYY, isDateRawType } from '../../utils/dates';
19
-
20
- // HTML/CSS
21
- import templateHTML from './template.html?raw';
22
-
23
- // Custom Elements
24
- import { PdcExpenseRow, PdcButton } from '../../components';
25
- customElements.define('pdc-expense-row', PdcExpenseRow);
26
-
27
- // Template for this Custom Element
28
- const template = document.createElement('template');
29
-
30
- // Custom Element
31
- export class PdcExpenseView extends HTMLElement {
32
- /* Initial setup
33
- */
34
- #styled = false;
35
- #observer: MutationObserver | null = null;
36
- #mieSubtotal = 0;
37
- #lodgingSubtotal = 0;
38
-
39
- constructor() {
40
- super();
41
- this.attachShadow({ mode: 'open' });
42
- }
43
-
44
- render(styled: boolean, config: ConfigSectionText) {
45
- this.#shadowRoot.innerHTML = '';
46
- window.removeEventListener('resize', this.#debouncedResizeHandler());
47
- this.#styled = styled;
48
- if (this.#styled) {
49
- template.innerHTML = templateHTML;
50
- applyStyles(this.#shadowRoot);
51
- } else template.innerHTML = removeStyles(templateHTML);
52
- this.#shadowRoot.appendChild(template.content.cloneNode(true));
53
- this.#applyConfig(config);
54
- this.#shadowRoot
55
- .querySelector<PdcButton>('pdc-button')
56
- ?.enableTabIndex(true);
57
- this.#addEventListeners();
58
- }
59
-
60
- renderLoadingSpinner(enabled: boolean) {
61
- if (!this.#styled) return;
62
- const spinner = this.#shadowRoot.querySelector(
63
- '[data-pdc="loading-spinner"]',
64
- );
65
- if (enabled) spinner?.classList.add('active');
66
- else spinner?.classList.remove('active');
67
- }
68
-
69
- #applyConfig = (config: ConfigSectionText) => {
70
- const heading = this.#shadowRoot.querySelector('#heading');
71
- const headingPrint = this.#shadowRoot.querySelector('#heading-print');
72
- const body = this.#shadowRoot.querySelector('#body');
73
- const bodyPrint = this.#shadowRoot.querySelector('#body-print');
74
-
75
- if (heading && config.heading) {
76
- heading.innerHTML = '';
77
- heading.insertAdjacentHTML('beforeend', config.heading);
78
- } else heading?.remove();
79
-
80
- if (headingPrint && config.headingPrint) {
81
- headingPrint.innerHTML = '';
82
- headingPrint.insertAdjacentHTML('beforeend', config.headingPrint);
83
- } else headingPrint?.remove();
84
-
85
- if (body && config.body) {
86
- body.innerHTML = '';
87
- body.insertAdjacentHTML('beforeend', config.body);
88
- } else body?.remove();
89
-
90
- if (bodyPrint && config.bodyPrint) {
91
- bodyPrint.innerHTML = '';
92
- bodyPrint.insertAdjacentHTML('beforeend', config.bodyPrint);
93
- } else bodyPrint?.remove();
94
- };
95
-
96
- /* Events
97
- */
98
- #addEventListeners() {
99
- // Mouse, touch events
100
- let pointerStartX = 0;
101
- let pointerStartY = 0;
102
- this.#viewContainer.addEventListener('pointerdown', e => {
103
- if (!(e instanceof PointerEvent)) return;
104
-
105
- const result = handlePointerDown(e);
106
- pointerStartX = result.pointerStartX;
107
- pointerStartY = result.pointerStartY;
108
- });
109
- this.#viewContainer.addEventListener('pointerup', e => {
110
- if (e instanceof PointerEvent) {
111
- const result = handlePointerUp(
112
- e,
113
- this.#handleClicks.bind(this),
114
- pointerStartX,
115
- pointerStartY,
116
- );
117
- pointerStartX = result.pointerStartX;
118
- pointerStartY = result.pointerStartY;
119
- }
120
- });
121
-
122
- // Keyboard events
123
- this.#viewContainer.addEventListener('keydown', e => {
124
- if (!(e.key === 'Enter' || e.key === ' ')) return;
125
- this.#handleClicks(e);
126
- });
127
-
128
- // Resize events
129
- window.addEventListener('resize', this.#debouncedResizeHandler());
130
- }
131
-
132
- #debouncedResizeHandler() {
133
- const resizeHandler = () => {
134
- this.#rows.forEach(row => row.windowResize());
135
- };
136
- return debounce(resizeHandler);
137
- }
138
-
139
- #handleClicks(e: Event) {
140
- const target = e.target;
141
- if (!(target instanceof Element || target instanceof SVGElement))
142
- return;
143
- const btn = target.closest('button');
144
- const btnPdcEl = target.closest<PdcButton>('pdc-button');
145
- if (!(btn || btnPdcEl)) return;
146
- switch (true) {
147
- case btn?.getAttribute('id') === 'toggle-expand':
148
- this.#expandAllRows(btn);
149
- return;
150
- case btnPdcEl?.getAttribute('id') === 'save-expenses':
151
- this.#viewContainer.setAttribute('table', 'true');
152
- return;
153
- default:
154
- return;
155
- }
156
- }
157
-
158
- /* Get view elements
159
- */
160
- get #rowsContainer() {
161
- const rowsContainer =
162
- this.#shadowRoot.querySelector<HTMLDivElement>('#rows');
163
- if (!rowsContainer)
164
- throw new Error(
165
- 'Failed to render row container/rates table in Expense view.',
166
- );
167
- return rowsContainer;
168
- }
169
-
170
- get #rows() {
171
- const rows =
172
- this.#shadowRoot.querySelectorAll<PdcExpenseRow>('pdc-expense-row');
173
- if (!rows)
174
- throw new Error('Failed to render row elements for expense View.');
175
- return rows;
176
- }
177
-
178
- get #viewContainer() {
179
- const container =
180
- this.#shadowRoot.querySelector<HTMLElement>('#expense-container');
181
- if (!container)
182
- throw new Error(
183
- 'Failed to render container element for expense View.',
184
- );
185
- return container;
186
- }
187
-
188
- get #expensesTable() {
189
- const expensesTable = this.#shadowRoot.querySelector('#expenses-table');
190
- if (!expensesTable)
191
- throw new Error(
192
- 'Failed to render expenses table for expense View.',
193
- );
194
- return expensesTable;
195
- }
196
-
197
- get #shadowRoot() {
198
- if (!this.shadowRoot)
199
- throw new Error(`Failed to render ShadowRoot for expense View.`);
200
- return this.shadowRoot;
201
- }
202
-
203
- /* Visual methods
204
- */
205
- #expandAllRows(btn: HTMLButtonElement) {
206
- if (!this.#styled) return;
207
- btn.classList.toggle('active');
208
- const toggle = btn.classList.contains('active') ? 'open' : 'close';
209
- this.#rows.forEach(row => row.rowToggle(toggle));
210
- }
211
-
212
- renderEmtpy() {
213
- this.#shadowRoot.innerHTML = '';
214
- }
215
-
216
- /* Update methods
217
- */
218
- async #updateTotals() {
219
- this.#mieSubtotal = 0;
220
- this.#lodgingSubtotal = 0;
221
- this.#rows.forEach(row => {
222
- this.#mieSubtotal += row.amount.mie;
223
- this.#lodgingSubtotal += row.amount.lodging;
224
- });
225
- }
226
-
227
- #updateTotalsText() {
228
- const mieSubtotalEl = this.#shadowRoot.querySelector('#mie-subtotal');
229
- const lodgingSubtotalEl =
230
- this.#shadowRoot.querySelector('#lodging-subtotal');
231
- const totalEl = this.#shadowRoot.querySelector('#total');
232
- if (!(mieSubtotalEl && lodgingSubtotalEl && totalEl))
233
- throw new Error(
234
- 'Failed to render subtotal/total elements in Expense view.',
235
- );
236
- mieSubtotalEl.textContent = `${USD.format(this.#mieSubtotal)}`;
237
- lodgingSubtotalEl.textContent = `${USD.format(this.#lodgingSubtotal)}`;
238
- totalEl.textContent = `${USD.format(this.#mieSubtotal + this.#lodgingSubtotal)}`;
239
- }
240
-
241
- updateRowMie(
242
- date: DateRaw,
243
- newMieTotal: number,
244
- totalMie: number,
245
- totalLodging: number,
246
- ) {
247
- this.#shadowRoot
248
- .querySelector<PdcExpenseRow>(`[date="${date}"]`)
249
- ?.updateMieAmount(newMieTotal);
250
- this.#mieSubtotal = totalMie;
251
- this.#lodgingSubtotal = totalLodging;
252
- this.#updateTotalsText();
253
- }
254
-
255
- async addRows(
256
- expenses: StateExpenseItemValid[],
257
- expensesCategory: 'mie' | 'lodging' | 'both',
258
- ) {
259
- this.#rowsContainer.innerHTML = '';
260
- expenses.forEach(expense => {
261
- const row = new PdcExpenseRow(
262
- expense,
263
- this.#styled,
264
- expensesCategory,
265
- );
266
- this.#rowsContainer.appendChild(row);
267
- row.styleRow();
268
- });
269
- const position = this.getBoundingClientRect().top + window.pageYOffset;
270
- window.scrollTo({ top: position, behavior: 'smooth' });
271
- await this.#updateTotals();
272
- this.#updateTotalsText();
273
- this.#createSourceList();
274
- this.#createRatesTable();
275
- }
276
-
277
- /* Create prinout
278
- */
279
- #createSourceList() {
280
- const sources = new Set<string>();
281
- this.#rows?.forEach(row => {
282
- sources.add(row.rateSource);
283
- });
284
- const sourcesEl = this.#shadowRoot.querySelector('#sources');
285
- if (!sourcesEl)
286
- throw new Error(
287
- 'Failed to render sources element in Expense view.',
288
- );
289
- sources.forEach(source => {
290
- sourcesEl.insertAdjacentHTML(
291
- 'beforeend',
292
- `<a href="${source}" target="_blank">${source}</a>`,
293
- );
294
- });
295
- }
296
-
297
- #createRatesTable() {
298
- const ratesTable = this.#shadowRoot.querySelector('#rates-table');
299
- if (!ratesTable)
300
- throw new Error('Failed to render rates table in Expense view.');
301
-
302
- const rateSet = new Set<string>();
303
- this.#rows.forEach((row, i, arr) => {
304
- if (i === 0 || row.rateString !== arr[i - 1].rateString)
305
- rateSet.add(row.rateStringForTable);
306
- });
307
-
308
- let markup = '';
309
- rateSet.forEach(item => {
310
- markup += this.#createRateTableMarkup(item);
311
- });
312
-
313
- ratesTable.innerHTML = markup;
314
- }
315
-
316
- #createRateTableMarkup(item: string) {
317
- const rateObject = JSON.parse(item);
318
- const { rates } = rateObject;
319
- return /*HTML*/ `
320
- <tr class="border-b-2">
321
- <td class="border-r-2 p-3">${rateObject.monthYear}</td>
322
- <td class="border-r-2 p-3">${rateObject.city}, ${rateObject.country}</td>
323
- <td>${rates.maxLodging.toFixed(2)}</td>
324
- <td>${rates.maxMie.toFixed(2)}</td>
325
- <td>${rates.maxMieFirstLast.toFixed(2)}</td>
326
- <td class="border-r-2">${rates.maxIncidental.toFixed(2)}</td>
327
- <td>${rates.deductionBreakfast.toFixed(2)}</td>
328
- <td>${rates.deductionLunch.toFixed(2)}</td>
329
- <td>${rates.deductionDinner.toFixed(2)}</td>
330
- </tr>
331
- `;
332
- }
333
-
334
- async createExpenseTable(expenses: StateExpenseItemValid[]) {
335
- let mieTotal = 0;
336
- let lodgingTotal = 0;
337
- let total = 0;
338
- let markup = '';
339
-
340
- expenses.forEach((expense, i, arr) => {
341
- mieTotal += expense.mieAmount;
342
- lodgingTotal += expense.lodgingAmount;
343
- total += expense.totalAmount;
344
- const locationString =
345
- `<span class="font-semibold">${expense.city}, ${expense.country}</span>` +
346
- this.#createDeductionsString(expense, i, arr.length);
347
-
348
- markup += this.#createExpenseTableRow(expense, locationString);
349
- });
350
-
351
- markup += /* HTML */ `
352
- <tr class="border-t-3 *:text-lg *:font-semibold">
353
- <td colspan="2" class="text-left">Total</td>
354
- <td class="text-right">${USD.format(mieTotal)}</td>
355
- <td class="text-right">${USD.format(lodgingTotal)}</td>
356
- <td class="text-right">${USD.format(total)}</td>
357
- </tr>
358
- `;
359
- this.#expensesTable.innerHTML = markup;
360
- await wait(0);
361
- window.print();
362
- }
363
-
364
- #createDeductionsString(
365
- expense: StateExpenseItemValid,
366
- i: number,
367
- arrLength: number,
368
- ) {
369
- const { breakfastProvided, lunchProvided, dinnerProvided } =
370
- expense.deductions;
371
- const deductionsArr: string[] = [];
372
- if (i === 0) deductionsArr.push('First Day');
373
- if (i === arrLength - 1) deductionsArr.push('Last Day');
374
- if (breakfastProvided) deductionsArr.push('Breakfast');
375
- if (lunchProvided) deductionsArr.push('Lunch');
376
- if (dinnerProvided) deductionsArr.push('Dinner');
377
- return deductionsArr.length === 0 ?
378
- ''
379
- : deductionsArr.reduce((result, deduction) => {
380
- return result === '' ?
381
- `<br>${deduction}`
382
- : `${result}, ${deduction}`;
383
- }, '');
384
- }
385
-
386
- #createExpenseTableRow(expense: StateExpenseItemValid, location: string) {
387
- return /*HTML*/ `
388
- <tr>
389
- <td class="text-center">${getMM(expense.date)}/${getDD(expense.date)}/${getYY(expense.date)}</td> <!-- // 2024-10-01 to 10/01/24 -->
390
- <td class="text-left">${location}</td>
391
- <td class="text-right">${USD.format(expense.mieAmount)}</td>
392
- <td class="text-right">${USD.format(expense.lodgingAmount)}</td>
393
- <td class="text-right font-semibold">${USD.format(expense.totalAmount)}</td>
394
- </tr>
395
- `;
396
- }
397
-
398
- emptyExpenseTable() {
399
- this.#expensesTable.innerHTML = '';
400
- }
401
-
402
- /* Controller / View interaction
403
- */
404
- controllerHandler(
405
- controlUpdateFunction: (row: StateExpenseItemUpdate) => void,
406
- controlTableFunction: () => void,
407
- ) {
408
- const callback = (mutations: MutationRecord[]) => {
409
- mutations.forEach(mutation => {
410
- const changedAttr = mutation.attributeName;
411
- if (!(changedAttr && mutation.target instanceof Element))
412
- return;
413
- const target = mutation.target;
414
- const newValue = target.getAttribute(changedAttr);
415
- if (changedAttr === 'table') {
416
- if (newValue) {
417
- this.#viewContainer.removeAttribute('table');
418
- controlTableFunction();
419
- }
420
- return;
421
- }
422
- const result = this.#returnRowUpdate(target);
423
- controlUpdateFunction(result);
424
- return;
425
- });
426
- };
427
- if (this.#observer === null)
428
- this.#observer = new MutationObserver(callback);
429
- this.#observer.disconnect();
430
- this.#observer.observe(this.#viewContainer, {
431
- subtree: true,
432
- attributes: true,
433
- attributeFilter: [
434
- 'lodging',
435
- 'breakfastprovided',
436
- 'lunchprovided',
437
- 'dinnerprovided',
438
- 'table',
439
- ],
440
- });
441
- }
442
-
443
- #returnRowUpdate(target: Element): StateExpenseItemUpdate {
444
- const row = target.closest<Element>('pdc-expense-row');
445
- const date = row?.getAttribute('date');
446
- const lodging = row?.getAttribute('lodging');
447
- const breakfastProvided =
448
- row?.getAttribute('breakfastprovided') === 'yes';
449
- const lunchProvided = row?.getAttribute('lunchprovided') === 'yes';
450
- const dinnerProvided = row?.getAttribute('dinnerprovided') === 'yes';
451
-
452
- if (!(date && isDateRawType(date) && lodging))
453
- throw new Error(
454
- 'Failed to get date and lodging values to update row in expense View.',
455
- );
456
-
457
- return {
458
- date,
459
- lodgingAmount: +lodging,
460
- breakfastProvided,
461
- lunchProvided,
462
- dinnerProvided,
463
- };
464
- }
465
- }
@@ -1,176 +0,0 @@
1
- <div
2
- id="expense-container"
3
- class="group relative mx-auto max-w-xl overflow-hidden p-3
4
- [transition:height_0.7s_ease] print:hidden"
5
- >
6
- <!-- Indicator that results are still loading -->
7
- <div
8
- data-pdc="loading-spinner"
9
- class="absolute top-0 left-0 z-0 hidden h-full w-full justify-center
10
- rounded-lg border-2 border-neutral-200 bg-white p-6 opacity-0
11
- transition-opacity duration-500 ease-in [.active]:z-50
12
- [.active]:flex [.active]:opacity-100"
13
- >
14
- <div
15
- class="border-primary-50 border-t-primary-600 h-10 w-10 animate-spin
16
- rounded-full border-4 border-t-4"
17
- ></div>
18
- </div>
19
-
20
- <!-- Heading, toggle rows button, rows -->
21
- <div
22
- class="relative z-10 overflow-hidden rounded-lg ring-2 ring-neutral-300
23
- [transition:height_0.7s_eas]"
24
- >
25
- <!-- Heading, toggle -->
26
- <div
27
- class="flex justify-between border-b-2 border-b-neutral-200 bg-white
28
- p-4 text-neutral-900"
29
- >
30
- <div>
31
- <h3 id="heading" class="text-xl font-semibold"></h3>
32
- <p id="body" class="mt-2"></p>
33
- </div>
34
- <button
35
- type="button"
36
- title="Toggle all rows"
37
- id="toggle-expand"
38
- class="hover:text-primary-900 focus-visible:border-b-primary-800
39
- relative ml-3 border-b-4 border-b-transparent px-2
40
- text-neutral-600 transition-colors duration-500 *:size-5
41
- *:overflow-visible *:fill-current **:origin-center
42
- **:transition-all hover:cursor-pointer focus:outline-none
43
- [&_.down]:[transform:translateY(15%)]
44
- [&_.up]:[transform:translateY(-15%)]
45
- [&.active_.down]:[transform:scaleY(-1)_translateY(-70%)]
46
- [&.active_.up]:[transform:scaleY(-1)_translateY(70%)]"
47
- >
48
- <svg
49
- xmlns="http://www.w3.org/2000/svg"
50
- data-pdc-delete="Toggle Expenses"
51
- class="fill-current"
52
- viewBox="0 0 16 16"
53
- >
54
- <path
55
- class="down"
56
- fill-rule="evenodd"
57
- d="M3.646 14.854a.5.5 0 0 0 .708 0L8 11.207l3.646 3.647a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 0 0 0 .708"
58
- />
59
- <path
60
- fill-rule="evenodd"
61
- d="M1 8a.5.5 0 0 1 .5-.5h13a.5.5 0 0 1 0 1h-13A.5.5 0 0 1 1 8"
62
- />
63
- <path
64
- class="up"
65
- fill-rule="evenodd"
66
- d="M3.646 1.146a.5.5 0 0 1 .708 0L8 4.793l3.646-3.647a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 0-.708"
67
- />
68
- </svg>
69
- </button>
70
- </div>
71
-
72
- <!-- Rows -->
73
- <div
74
- id="rows"
75
- class="overflow-hidden [transition:height_0.7s_ease]"
76
- ></div>
77
- </div>
78
-
79
- <!-- Totals -->
80
- <div id="total-amounts" class="my-8 px-3 text-lg">
81
- <div class="flex">
82
- <p class="grow">M&IE</p>
83
- <p id="mie-subtotal" class="text-right">$0.00</p>
84
- </div>
85
- <div class="flex">
86
- <p class="grow">Lodging</p>
87
- <p id="lodging-subtotal" class="text-right">$0.00</p>
88
- </div>
89
- <div class="flex font-semibold">
90
- <p class="grow">Total</p>
91
- <p id="total" class="text-right">$0.00</p>
92
- </div>
93
- </div>
94
-
95
- <!-- Button print expenses -->
96
- <div class="w-full text-center">
97
- <pdc-button
98
- styled="true"
99
- id="save-expenses"
100
- text="Save Expenses"
101
- title="Save expenses"
102
- ></pdc-button>
103
- </div>
104
- </div>
105
-
106
- <!-- Print section-->
107
- <div class="hidden w-full print:block">
108
- <!-- Print heading, body-->
109
- <h3 id="heading-print" class="mt-2.5 text-xl font-semibold"></h3>
110
- <p id="body-print" class="text-sm"></p>
111
-
112
- <!-- Print rates table, sources -->
113
- <div class="mt-8 w-full">
114
- <h3 class="mb-2.5 text-xl font-semibold">Rates</h3>
115
- <table
116
- class="w-full table-auto **:text-center **:align-middle **:text-xs"
117
- >
118
- <thead>
119
- <tr class="border-t-2 border-b-2 *:px-2 *:font-semibold">
120
- <th class="w-[12.5%] border-r-2" rowspan="2">
121
- Eff.<br />Date
122
- </th>
123
- <th class="w-[22%] border-r-2" rowspan="2">Location</th>
124
- <th class="border-r-2 py-1.5" colspan="4">Maximums</th>
125
- <th colspan="3">Deductions</th>
126
- </tr>
127
- <tr class="*:font-base border-b-3 *:px-2">
128
- <th>Lodging</th>
129
- <th class="py-1.5">
130
- M&IE<br /><span class="font-normal">(Full)</span>
131
- </th>
132
- <th>
133
- M&IE<br /><span class="font-normal">(First/Last)</span>
134
- </th>
135
- <th class="border-r-2">Incid.</th>
136
- <th>Break.</th>
137
- <th>Lunch</th>
138
- <th>Dinner</th>
139
- </tr>
140
- </thead>
141
- <tbody id="rates-table"></tbody>
142
- </table>
143
- <!-- Sources -->
144
- <div
145
- class="**:pb-2.5 **:text-xs [&>a]:ml-5 [&>a]:list-item [&>a]:pl-2.5"
146
- id="sources"
147
- >
148
- <p class="pt-2.5">Source:</p>
149
- </div>
150
- </div>
151
-
152
- <!-- Print expenses table -->
153
- <div class="mt-8 w-full">
154
- <h3 class="mb-2.5 text-xl font-semibold">Expenses</h3>
155
-
156
- <table class="w-full table-fixed **:align-middle **:text-xs">
157
- <thead>
158
- <tr class="border-t-2 border-b-3 *:p-3 *:font-semibold">
159
- <th class="w-[12.5%] text-center" rowspan="2">Date</th>
160
- <th class="w-[40%] text-left" rowspan="2">
161
- Location
162
- <br />
163
- <span class="font-normal">Deductions</span>
164
- </th>
165
- <th class="text-right">M&IE</th>
166
- <th class="text-right">Lodging</th>
167
- <th class="text-right">Total</th>
168
- </tr>
169
- </thead>
170
- <tbody
171
- class="**:align-middle [&_td]:p-3 [&_tr]:border-b-2"
172
- id="expenses-table"
173
- ></tbody>
174
- </table>
175
- </div>
176
- </div>