@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.
- package/dist/index.js +11034 -0
- package/dist/index.umd.cjs +1384 -0
- package/package.json +12 -2
- package/.prettierrc +0 -17
- package/eslint.config.js +0 -29
- package/index.html +0 -11
- package/src/css/_styles.css +0 -8
- package/src/css/colors.css +0 -45
- package/src/css/fonts.css +0 -9
- package/src/css/rows/_heights.css +0 -6
- package/src/css/rows/_index.css +0 -15
- package/src/css/rows/add.css +0 -18
- package/src/css/rows/animate-btns.css +0 -18
- package/src/css/rows/animate-row-close.css +0 -18
- package/src/css/rows/animate-row-open.css +0 -14
- package/src/css/rows/animate-row-other.css +0 -5
- package/src/css/rows/btn-add-row.css +0 -41
- package/src/css/rows/btn-delete.css +0 -22
- package/src/css/rows/btn-expenses-calculate.css +0 -22
- package/src/css/rows/btn-expenses-category.css +0 -22
- package/src/css/rows/delete.css +0 -10
- package/src/css/rows/details.css +0 -22
- package/src/css/rows/expense.css +0 -18
- package/src/css/rows/location.css +0 -34
- package/src/css/rows/summary.css +0 -22
- package/src/css/tom-select/defaults.css +0 -530
- package/src/css/tom-select/overrides.css +0 -55
- package/src/css/tw-shadow-props.css +0 -50
- package/src/index.ts +0 -1
- package/src/ts/components/Button/Button.ts +0 -50
- package/src/ts/components/Button/template.html +0 -34
- package/src/ts/components/ExpenseRow/ExpenseRow.ts +0 -397
- package/src/ts/components/ExpenseRow/template.html +0 -260
- package/src/ts/components/Label/Label.ts +0 -45
- package/src/ts/components/Label/template.html +0 -1
- package/src/ts/components/LocationCategory/LocationCategory.ts +0 -226
- package/src/ts/components/LocationCategory/template.html +0 -520
- package/src/ts/components/LocationDate/LocationDate.ts +0 -366
- package/src/ts/components/LocationDate/template.html +0 -27
- package/src/ts/components/LocationSelect/LocationSelect.ts +0 -299
- package/src/ts/components/LocationSelect/template.html +0 -45
- package/src/ts/components/index.ts +0 -6
- package/src/ts/controller.ts +0 -193
- package/src/ts/model.ts +0 -163
- package/src/ts/types/config.ts +0 -22
- package/src/ts/types/dates.ts +0 -82
- package/src/ts/types/expenses.ts +0 -73
- package/src/ts/types/locations.ts +0 -25
- package/src/ts/utils/config/configDefault.ts +0 -13
- package/src/ts/utils/config/index.ts +0 -12
- package/src/ts/utils/config/numbers.ts +0 -24
- package/src/ts/utils/config/sanitizeConfig.ts +0 -39
- package/src/ts/utils/dates/INPUT_DATE_MINMAX.ts +0 -5
- package/src/ts/utils/dates/YEAR_REGEX.ts +0 -4
- package/src/ts/utils/dates/getDateSlice.ts +0 -54
- package/src/ts/utils/dates/getValidAPIYear.ts +0 -17
- package/src/ts/utils/dates/index.ts +0 -19
- package/src/ts/utils/dates/isDateRaw.ts +0 -90
- package/src/ts/utils/dates/isShortMonth.ts +0 -24
- package/src/ts/utils/dates/isYYYY.ts +0 -10
- package/src/ts/utils/dates/offsetDateString.ts +0 -17
- package/src/ts/utils/expenses/INTL_MIE_RATES.ts +0 -2125
- package/src/ts/utils/expenses/createExpenseObjs.ts +0 -35
- package/src/ts/utils/expenses/getLodgingRateDomestic.ts +0 -73
- package/src/ts/utils/expenses/getLodgingRateIntl.ts +0 -119
- package/src/ts/utils/expenses/getMieRates.ts +0 -84
- package/src/ts/utils/expenses/index.ts +0 -5
- package/src/ts/utils/expenses/parseIntlLodgingRates.ts +0 -124
- package/src/ts/utils/expenses/returnValidStateExpense.ts +0 -46
- package/src/ts/utils/fetch/fetchJsonGSA.ts +0 -29
- package/src/ts/utils/fetch/fetchXmlDOD.ts +0 -38
- package/src/ts/utils/fetch/index.ts +0 -3
- package/src/ts/utils/fetch/memoize.ts +0 -46
- package/src/ts/utils/fetch/parseXml.ts +0 -19
- package/src/ts/utils/locations/getCitiesDomestic.ts +0 -48
- package/src/ts/utils/locations/getCitiesIntl.ts +0 -63
- package/src/ts/utils/locations/getCountriesDomestic.ts +0 -237
- package/src/ts/utils/locations/getCountriesIntl.ts +0 -34
- package/src/ts/utils/locations/index.ts +0 -6
- package/src/ts/utils/locations/keepUniqueLocations.ts +0 -12
- package/src/ts/utils/locations/locationKeys.ts +0 -10
- package/src/ts/utils/locations/returnValidStateLocation.ts +0 -13
- package/src/ts/utils/locations/sortLocations.ts +0 -19
- package/src/ts/utils/misc/USD.ts +0 -4
- package/src/ts/utils/misc/debounce.ts +0 -22
- package/src/ts/utils/misc/handlePointerDown.ts +0 -3
- package/src/ts/utils/misc/handlePointerUp.ts +0 -22
- package/src/ts/utils/misc/inPrimitiveType.ts +0 -4
- package/src/ts/utils/misc/index.ts +0 -6
- package/src/ts/utils/misc/wait.ts +0 -4
- package/src/ts/utils/styles/applyStyles.ts +0 -19
- package/src/ts/utils/styles/highlightInput.ts +0 -15
- package/src/ts/utils/styles/index.ts +0 -3
- package/src/ts/utils/styles/removeStyles.ts +0 -14
- package/src/ts/views/Expense/Expense.ts +0 -465
- package/src/ts/views/Expense/template.html +0 -176
- package/src/ts/views/Location/Location.ts +0 -763
- package/src/ts/views/Location/template-row.html +0 -146
- package/src/ts/views/Location/template.html +0 -130
- package/src/ts/views/index.ts +0 -2
- package/tsconfig.json +0 -27
- 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>
|