diff --git a/src/frontend/src/modules/SolarHomeSystem/AddSolarHomeSystemModal.vue b/src/frontend/src/modules/SolarHomeSystem/AddSolarHomeSystemModal.vue index 6326e0bbf..35e0d9abe 100644 --- a/src/frontend/src/modules/SolarHomeSystem/AddSolarHomeSystemModal.vue +++ b/src/frontend/src/modules/SolarHomeSystem/AddSolarHomeSystemModal.vue @@ -21,24 +21,43 @@
- - - - {{ errors.first("shs-add-form.serial_number") }} - - + + + + Paste comma-separated serial numbers (example: + 12345678,87654321). Click a chip to edit it. + + + + +
@@ -148,6 +167,10 @@ export default { manufacturerService: new ManufacturerService(), applianceService: new ApplianceService(), loading: false, + serialNumbers: [], + serialNumberError: null, + invalidSerialNumbers: [], + editingSerialChipIndex: null, } }, beforeMount() { @@ -155,13 +178,180 @@ export default { this.applianceService.getAppliances() }, methods: { + clearSerialInput() { + this.serialNumbers = [] + this.serialNumberError = null + this.invalidSerialNumbers = [] + this.editingSerialChipIndex = null + + const serialNumberChips = this.$refs.serialNumberChips + if (serialNumberChips) { + serialNumberChips.inputValue = "" + } + }, + commitPendingSerialInput() { + const serialNumberChips = this.$refs.serialNumberChips + if (!serialNumberChips || !serialNumberChips.inputValue) { + return + } + + if (serialNumberChips.inputValue.trim().length > 0) { + serialNumberChips.insertChip({ target: null }) + } + }, + parseSerialNumbers(value) { + return value + .split(/[\n,]/) + .map((serialNumber) => serialNumber.trim()) + .filter((serialNumber) => serialNumber.length > 0) + }, + appendSerialNumbers(serialNumbers, insertAtIndex = null) { + const serialNumbersToInsert = serialNumbers.filter( + (serialNumber) => !this.serialNumbers.includes(serialNumber), + ) + + if (serialNumbersToInsert.length === 0) { + this.serialNumberError = null + this.invalidSerialNumbers = [] + return + } + + if ( + Number.isInteger(insertAtIndex) && + insertAtIndex >= 0 && + insertAtIndex <= this.serialNumbers.length + ) { + this.serialNumbers.splice(insertAtIndex, 0, ...serialNumbersToInsert) + } else { + this.serialNumbers.push(...serialNumbersToInsert) + } + + this.serialNumberError = null + this.invalidSerialNumbers = [] + }, + isInvalidSerialNumber(serialNumber) { + return this.invalidSerialNumbers.includes(serialNumber) + }, + editSerialChip(serialNumber, chipIndex) { + if (typeof serialNumber !== "string") { + return + } + + this.commitPendingSerialInput() + this.serialNumberError = null + this.invalidSerialNumbers = [] + + const index = Number.isInteger(chipIndex) + ? chipIndex + : this.serialNumbers.indexOf(serialNumber) + + if (index === -1) { + return + } + + this.editingSerialChipIndex = index + this.serialNumbers.splice(index, 1) + + const serialNumberChips = this.$refs.serialNumberChips + if (!serialNumberChips) { + return + } + + serialNumberChips.inputValue = serialNumber + this.$nextTick(() => { + if (serialNumberChips.$refs.input && serialNumberChips.$refs.input.$el) { + serialNumberChips.$refs.input.$el.focus() + } + }) + }, + handleSerialPaste(event) { + const clipboardData = event.clipboardData || window.clipboardData + if (!clipboardData) { + return + } + + const pastedValue = clipboardData.getData("text") + if (typeof pastedValue !== "string" || pastedValue.trim().length === 0) { + return + } + + event.preventDefault() + const insertAtIndex = this.editingSerialChipIndex + this.editingSerialChipIndex = null + this.appendSerialNumbers(this.parseSerialNumbers(pastedValue), insertAtIndex) + }, + handleInsertedSerial(insertedSerialNumber) { + if (typeof insertedSerialNumber !== "string") { + return + } + + const insertAtIndex = this.editingSerialChipIndex + this.editingSerialChipIndex = null + + const parsedSerialNumbers = this.parseSerialNumbers(insertedSerialNumber) + if ( + parsedSerialNumbers.length === 1 && + parsedSerialNumbers[0] === insertedSerialNumber.trim() + ) { + if (!Number.isInteger(insertAtIndex)) { + this.serialNumberError = null + return + } + + const insertedChipIndex = this.serialNumbers.lastIndexOf( + insertedSerialNumber, + ) + if (insertedChipIndex !== -1) { + this.serialNumbers.splice(insertedChipIndex, 1) + } + + this.appendSerialNumbers(parsedSerialNumbers, insertAtIndex) + this.serialNumberError = null + return + } + + const insertedChipIndex = this.serialNumbers.lastIndexOf( + insertedSerialNumber, + ) + if (insertedChipIndex !== -1) { + this.serialNumbers.splice(insertedChipIndex, 1) + } + + this.appendSerialNumbers(parsedSerialNumbers, insertAtIndex) + }, + validateSerialNumbers() { + if (this.serialNumbers.length === 0) { + this.serialNumberError = "Please add at least one serial number." + this.invalidSerialNumbers = [] + return false + } + + this.invalidSerialNumbers = this.serialNumbers.filter( + (serialNumber) => serialNumber.length < 8 || serialNumber.length > 11, + ) + + if (this.invalidSerialNumbers.length > 0) { + this.serialNumberError = "Invalid serial number format." + return false + } + + this.serialNumberError = null + this.invalidSerialNumbers = [] + return true + }, async save() { + this.commitPendingSerialInput() const validator = await this.$validator.validateAll("shs-add-form") - if (validator) { + const hasValidSerialNumbers = this.validateSerialNumbers() + + if (validator && hasValidSerialNumbers) { this.loading = true try { const createdShs = - await this.solarHomeSystemService.createSolarHomeSystem() + await this.solarHomeSystemService.createSolarHomeSystems( + this.serialNumbers, + ) + this.clearSerialInput() this.alertNotify("success", this.$tc("phrases.newShs")) this.$emit("created", createdShs) } catch (e) { @@ -171,6 +361,7 @@ export default { } }, cancel() { + this.clearSerialInput() this.$emit("hideAddShs") }, }, @@ -188,3 +379,22 @@ export default { }, } + + diff --git a/src/frontend/src/modules/SolarHomeSystem/SolarHomeSystems.vue b/src/frontend/src/modules/SolarHomeSystem/SolarHomeSystems.vue index cd88834cb..cdbe2433d 100644 --- a/src/frontend/src/modules/SolarHomeSystem/SolarHomeSystems.vue +++ b/src/frontend/src/modules/SolarHomeSystem/SolarHomeSystems.vue @@ -103,10 +103,16 @@ export default { this.solarHomeSystemService.list.length, ) }, - updateList(shs) { + updateList(createdShs) { this.showAddSolarHomeSystem = false - const shsList = [...this.solarHomeSystemService.list] - shsList.unshift(shs) + const newlyCreatedSolarHomeSystems = Array.isArray(createdShs) + ? createdShs + : [createdShs] + + const shsList = [ + ...newlyCreatedSolarHomeSystems, + ...this.solarHomeSystemService.list, + ] this.solarHomeSystemService.updateList(shsList) EventBus.$emit( "widgetContentLoaded", diff --git a/src/frontend/src/services/SolarHomeSystemService.js b/src/frontend/src/services/SolarHomeSystemService.js index cedbe3d75..ee2f7c5fb 100644 --- a/src/frontend/src/services/SolarHomeSystemService.js +++ b/src/frontend/src/services/SolarHomeSystemService.js @@ -59,6 +59,19 @@ export class SolarHomeSystemService { return new ErrorHandler(errorMessage, "http") } } + + async createSolarHomeSystems(serialNumbers) { + const createdSolarHomeSystems = [] + + for (const serialNumber of serialNumbers) { + this.shs.serialNumber = serialNumber + const createdSolarHomeSystem = await this.createSolarHomeSystem() + createdSolarHomeSystems.push(createdSolarHomeSystem) + } + + return createdSolarHomeSystems + } + search(term) { this.paginator = new Paginator(`${this.repository.resource}/search`) EventBus.$emit("loadPage", this.paginator, { term: term })