diff --git a/package.json b/package.json index 43be007..f4c24b6 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "author": "", "license": "ISC", "dependencies": { - "rxjs": "^7.8.1" + "dropzone-vue3": "^1.0.2", + "rxjs": "^7.8.1", + "v-file-drop": "^0.2.1" } } diff --git a/private/minis-backend/build.gradle.kts b/private/minis-backend/build.gradle.kts index b7aae24..1cacd21 100644 --- a/private/minis-backend/build.gradle.kts +++ b/private/minis-backend/build.gradle.kts @@ -44,6 +44,9 @@ dependencies { implementation("io.github.cdimascio:dotenv-kotlin:6.4.1") implementation("at.favre.lib:bcrypt:0.10.2") + implementation("org.apache.poi:poi:5.2.3") // Check for the latest version + implementation("org.apache.poi:poi-ooxml:5.2.3") // Check for the latest version + implementation("org.apache.xmlbeans:xmlbeans:5.0.2") // Check for the latest version testImplementation("io.ktor:ktor-server-test-host") testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") diff --git a/private/minis-backend/src/main/kotlin/de/walamana/plugins/HTTP.kt b/private/minis-backend/src/main/kotlin/de/walamana/plugins/HTTP.kt index 3f27924..454c047 100644 --- a/private/minis-backend/src/main/kotlin/de/walamana/plugins/HTTP.kt +++ b/private/minis-backend/src/main/kotlin/de/walamana/plugins/HTTP.kt @@ -10,9 +10,13 @@ fun Application.configureHTTP() { allowMethod(HttpMethod.Put) allowMethod(HttpMethod.Delete) allowMethod(HttpMethod.Patch) + allowMethod(HttpMethod.Post) allowMethod(HttpMethod.Options) allowHeader(HttpHeaders.Authorization) allowHeader(HttpHeaders.AccessControlAllowOrigin) + allowHeader(HttpHeaders.ContentType) + allowHeader(HttpHeaders.CacheControl) + allowHeader("x-requested-with") allowNonSimpleContentTypes = true // allowHeader("MyCustomHeader") anyHost() // @TODO: Don't do this in production if possible. Try to limit it. diff --git a/private/minis-backend/src/main/kotlin/de/walamana/service/ZelebrationsplanParser.kt b/private/minis-backend/src/main/kotlin/de/walamana/service/ZelebrationsplanParser.kt new file mode 100644 index 0000000..5a50959 --- /dev/null +++ b/private/minis-backend/src/main/kotlin/de/walamana/service/ZelebrationsplanParser.kt @@ -0,0 +1,83 @@ +package de.walamana.service + +import de.walamana.models.Gottesdienst +import org.apache.poi.ss.usermodel.Row +import org.apache.poi.xssf.usermodel.XSSFWorkbook +import java.io.File +import java.io.InputStream +import java.lang.Exception +import java.time.* +import java.time.format.DateTimeFormatter +import java.util.Date + + +object ZelebrationsplanParser { + + private val weekdays = listOf("Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag") + + fun parse(ins: InputStream, planId: Int): HashMap> { + val workbook = XSSFWorkbook(ins) + val sheet = workbook.getSheetAt(0) + + var currentDate: String? = null + val gottesdienste = hashMapOf>() + + for (row in sheet) { + val r = rowToArray(row) + val weekday = weekdays.firstOrNull { r[0]?.startsWith(it) == true } + if(weekday != null) { + currentDate = r[0]!! + }else if(r[0]?.isBlank() == true) { + println("Empty line") + }else if(r.all { it != null }){ + try { + val time = formatTime(currentDate!!, r[1]!!) + val pfarrei = r[0]!! + val attendance = time - Duration.ofMinutes(30) + + gottesdienste[pfarrei] = gottesdienste.getOrElse(pfarrei) { arrayListOf() }.apply { + add(Gottesdienst( + id = 0, + name = r[2]!!, + date = time.toDate(), + attendance = attendance.toDate(), + planId = planId + )) + } + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + return gottesdienste + } + + private fun formatTime(date: String, time: String): LocalDateTime { + try{ + return LocalDateTime.parse("$date, $time", DateTimeFormatter.ofPattern("EEEE, d. MMMM yyyy, H.mm 'Uhr'")) + } catch (_: Exception) {} + + try{ + return LocalDateTime.parse("$date, $time", DateTimeFormatter.ofPattern("EEEE, d. MMMM yyyy, H:mm 'Uhr'")) + } catch (_: Exception) {} + + try{ + return LocalDateTime.parse("$date, $time", DateTimeFormatter.ofPattern("EEEE, d. MMMM yyyy, 'a. 'H.mm 'Uhr'")) + } catch (_: Exception) {} + + return LocalDateTime.parse("$date, $time", DateTimeFormatter.ofPattern("EEEE, d. MMMM yyyy, 'a. 'H:mm 'Uhr'")) + } + + private fun rowToArray(row: Row): Array { + return arrayOf(row.getCell(0)?.stringCellValue, row.getCell(1)?.stringCellValue, row.getCell(2)?.stringCellValue, row.getCell(3)?.stringCellValue) + } + + private fun LocalDateTime.toDate() = Date.from(this.atZone(ZoneId.systemDefault()).toInstant()) +} + +fun main() { + File("Zelebrationsplan Mai 2025.xlsx").inputStream().use { + ZelebrationsplanParser.parse(it, 0) + } +} \ No newline at end of file diff --git a/private/minis-backend/src/main/kotlin/de/walamana/views/GottesdiensteView.kt b/private/minis-backend/src/main/kotlin/de/walamana/views/GottesdiensteView.kt index 9331976..9cd2b09 100644 --- a/private/minis-backend/src/main/kotlin/de/walamana/views/GottesdiensteView.kt +++ b/private/minis-backend/src/main/kotlin/de/walamana/views/GottesdiensteView.kt @@ -1,9 +1,10 @@ package de.walamana.views import de.walamana.models.Gottesdienst -import de.walamana.models.Gottesdienste import de.walamana.models.GottesdiensteDao +import de.walamana.service.ZelebrationsplanParser import io.ktor.http.* +import io.ktor.http.content.* import io.ktor.server.application.* import io.ktor.server.request.* import io.ktor.server.response.* @@ -43,5 +44,17 @@ fun Route.configureGottesdiensteRoutes() { GottesdiensteDao.deleteGottesdienst(id) call.respond(HttpStatusCode.OK) } + + post("/parseZelebrationsplan") { + val id = call.parameters.getOrFail("id").toInt() + val multipartData = call.receiveMultipart() + multipartData.forEachPart { part -> + if(part is PartData.FileItem) { + val data = ZelebrationsplanParser.parse(part.streamProvider(), planId = id) + call.respond(data) + } + part.dispose() + } + } } } \ No newline at end of file diff --git a/public/src/components/PlanActionBar.vue b/public/src/components/PlanActionBar.vue index a0a922d..d9b66b4 100644 --- a/public/src/components/PlanActionBar.vue +++ b/public/src/components/PlanActionBar.vue @@ -1,5 +1,5 @@ + + + + \ No newline at end of file diff --git a/public/src/views/PlanView.vue b/public/src/views/PlanView.vue index 74186a0..92a8fb5 100644 --- a/public/src/views/PlanView.vue +++ b/public/src/views/PlanView.vue @@ -16,6 +16,7 @@ import CreateMinistrantDialog from "@/components/dialog/CreateMinistrantDialog.v import {useRoute, useRouter} from "vue-router"; import debounce from "underscore/modules/debounce.js" import SavingIndicator from "@/components/SavingIndicator.vue"; +import ImportZelebrationsplanDialog from "@/components/dialog/ImportZelebrationsplanDialog.vue"; const MAX_WIDTH_MOBILE = 600; @@ -279,6 +280,18 @@ async function createMinistrant(ministrantId?: number) { }) } +async function importZelebrationsplan() { + Dialogs.createDialog(ImportZelebrationsplanDialog, { + onPositive() {}, + onNegative() {}, + onDismiss() {} + }, { + planId: planId.value, + async onImport(godis: Gottesdienst[]) { + plan.gottesdienste.push(...godis) + } + }) +} @@ -329,6 +342,7 @@ async function createMinistrant(ministrantId?: number) { @save="saveChanges()" @add-godi="createGottesdienst()" @add-mini="createMinistrant()" + @import-zelebrationsplan="importZelebrationsplan()" v-if="editPlanAdmin" />