From 8ab4e532fd15daa6be86adef33c326f87f3e8d8d Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Wed, 20 May 2026 14:36:05 +0200 Subject: [PATCH] Slim API deployment - composer install + full CRUD endpoints --- api/README.md | 44 ++ api/composer.json | 29 + api/public/index.php | 36 ++ api/reset_pass.php | 9 + api/src/Config/Container.php | 56 ++ .../Controllers/AppointmentsController.php | 81 +++ api/src/Controllers/AuthController.php | 80 +++ api/src/Controllers/BootstrapController.php | 67 +++ api/src/Controllers/InstructorsController.php | 55 ++ api/src/Controllers/LessonTypesController.php | 25 + api/src/Controllers/StudentsController.php | 75 +++ api/src/Controllers/TemplatesController.php | 27 + api/src/Middleware/JwtMiddleware.php | 62 ++ api/src/Models/Appointment.php | 28 + api/src/Models/Instructor.php | 28 + api/src/Models/LessonType.php | 27 + .../Models/LessonTypeTemplateVisibility.php | 14 + api/src/Models/LicenseClassTemplate.php | 31 + api/src/Models/LicenseTemplateRequirement.php | 17 + api/src/Models/Student.php | 28 + api/src/Models/Tenant.php | 37 ++ api/src/Models/User.php | 29 + api/src/Routes/api.php | 62 ++ api/test_db.php | 9 + api/test_login.php | 12 + .../docs/README.txt | 1 + .../api.fahrschultermin.de/public/index.php | 2 + .../Controllers/AppointmentsController.php | 187 ++++++ .../app/Http/Controllers/AuthController.php | 49 ++ .../Http/Controllers/BootstrapController.php | 76 +++ .../Controllers/InstructorsController.php | 33 ++ .../Controllers/ReferenceDataController.php | 90 +++ .../Http/Controllers/StudentsController.php | 78 +++ .../Http/Controllers/TenantsController.php | 40 ++ .../app/Http/Controllers/UsersController.php | 51 ++ .../Repositories/AppointmentRepository.php | 290 ++++++++++ .../app/Repositories/BaseRepository.php | 23 + .../app/Repositories/InstructorRepository.php | 82 +++ .../Repositories/ReferenceDataRepository.php | 515 +++++++++++++++++ .../app/Repositories/StudentRepository.php | 294 ++++++++++ .../app/Repositories/TenantRepository.php | 86 +++ .../app/Repositories/UserRepository.php | 112 ++++ .../app/Services/CalendarService.php | 93 +++ .../app/Services/HolidayService.php | 109 ++++ .../app/Services/RequirementService.php | 56 ++ .../app/Services/SunTimesService.php | 55 ++ .../fahrschultermin.de/app/Support/Auth.php | 42 ++ .../app/Support/Database.php | 37 ++ .../app/Support/Request.php | 40 ++ .../app/Support/Response.php | 22 + .../fahrschultermin.de/app/Support/Router.php | 63 ++ .../www/fahrschultermin.de/app/bootstrap.php | 44 ++ .../www/fahrschultermin.de/bin/migrate.php | 27 + .../www/fahrschultermin.de/bin/seed.php | 208 +++++++ .../www/fahrschultermin.de/config/app.php | 13 + .../database/migrations/001_initial.sql | 145 +++++ .../migrations/002_instructors_user_link.sql | 1 + .../003_backfill_instructor_user_links.sql | 11 + .../004_lesson_type_template_visibility.sql | 13 + .../migrations/005_tenant_location_sun.sql | 3 + .../migrations/006_tenant_federal_state.sql | 1 + .../007_student_prior_completed_units.sql | 1 + .../migrations/008_lesson_type_system_key.sql | 2 + .../009_requirement_phase_labels.sql | 2 + .../database/migrations/010_student_email.sql | 1 + .../migrations/010_students_contact_flags.sql | 2 + .../migrations/011_student_needs_glasses.sql | 1 + .../012_split_combination_templates.sql | 183 ++++++ .../013_combo_generic_key_cleanup.sql | 42 ++ ...tudent_key_aliases_to_lesson_type_keys.sql | 170 ++++++ .../015_instructor_pre_start_buffer.sql | 1 + .../www/fahrschultermin.de/public/.htaccess | 4 + .../public/assets/.vite/manifest.json | 11 + .../public/assets/assets/main-C54NZrM5.css | 1 + .../public/assets/assets/main-F76uNDS1.js | 44 ++ .../www/fahrschultermin.de/public/index.html | 27 + .../www/fahrschultermin.de/public/index.php | 100 ++++ .../storage/database/app.sqlite | Bin 0 -> 126976 bytes .../database/app.sqlite.bak-20260422203538 | 0 .../app.sqlite.bak-users-20260422204004 | Bin 0 -> 73728 bytes .../sess_0006a50819ddb4c83f8927e073726008 | 0 .../sess_00309f86663d537153fcb66685d86df7 | 0 .../sess_00409744a4c7e3bb92d2eb4076f6f9f1 | 0 .../sess_004433bebe95f3233eb3afeacee557d2 | 0 .../sess_01276da25030fe7610e51db01928f92e | 0 .../sess_013cba357db97c86d9affd6f490425d2 | 0 .../sess_01db5cc5dc2af4df786929887036129e | 0 .../sess_025b3db5cdc9307a49bbc4e7c8569322 | 0 .../sess_02ce241714378b298d820865b9f207fa | 0 .../sess_0302bb8051aea505d498b8118cf07cbc | 0 .../sess_033690960537713e4b7584067e851174 | 0 .../sess_0387d0c9c23e92d7c5da1f59219d140a | 0 .../sess_03ab912d95e7096bd5041a815feaaac6 | 0 .../sess_03bec560a62e6282f3c00ee39e0b5185 | 0 .../sess_03f80785c72e82de6dbb20e633f8e70b | 0 .../sess_04921a0b3787740b88c11ab3b2b4d4dc | 0 .../sess_04ba107751852addceb705f95cb01636 | 0 .../sess_04e7eeca7ab45efc4714e55d0e488aab | 0 .../sess_04ec6601cf72642f743fb0ad41d466b8 | 0 .../sess_0521642416caa2fbc62e3f8298109cf1 | 0 .../sess_052b45081be50758c250257b9a65d04a | 0 .../sess_05fa0356f494ca589061fb1f1cda9f38 | 0 .../sess_05fc405f00dd93a93a4e02d048ecfdef | 0 .../sess_05fe8c42f2d6ce2eb298f3773fe7aa46 | 0 .../sess_06322d63f2b61ed856911309f0491b02 | 0 .../sess_068031028f6af11d6206ba4e176e106f | 0 .../sess_06b2450149fb978337c3b300c5177c89 | 0 .../sess_06d7f7359a7c55810206e5722284344a | 0 .../sess_076f1f9b3f3607d85628e676aa3deeef | 0 .../sess_0781485879098048ec9ec26f838e230b | 0 .../sess_0854ff9e45ba1f5477fbbad5e54698f3 | 0 .../sess_087a67b3949d5d39a90f231048dbc168 | 0 .../sess_09013b1da99e4f816a92e860b645e65b | 1 + .../sess_0979ad1bce9bd29c7f78266fb0db9496 | 0 .../sess_09a34f83873ab7845a54ad0e1f123da2 | 0 .../sess_0a1062c7493039579c5b1ccf819017cf | 0 .../sess_0a3a77bb3b296f6ef7c2c58f223522dc | 0 .../sess_0a457d212ce74a302f1245eaadd68550 | 0 .../sess_0a8407b10f0705fb94abc083ef7629a1 | 0 .../sess_0a918e1bf4f81d888710bb6eabccf5b7 | 0 .../sess_0b02235f6c9d7a8fdd13403950def66a | 0 .../sess_0b4c3e7eeb7915687615efbcb7ad351a | 0 .../sess_0b689f1efcd64909309b788ce0aa77a8 | 0 .../sess_0b7a6dd656b413dfa91158a8f36533fe | 0 .../sess_0bee18c4dceb5b1916bfb6d16f940237 | 1 + .../sess_0c6b2d92ad51dc235c8a326a6f15bbde | 0 .../sess_0cc19484bb603a199b713e1506f53ba0 | 0 .../sess_0cf9ec95094969104196ac2547eb3cc5 | 0 .../sess_0d444063fde0f76a4a254962ecc24900 | 0 .../sess_0d674f078cb072a267fab787102b842c | 0 .../sess_0dca6c782e6359327b87294c5be46f45 | 0 .../sess_0df4eb5e781f33fdf8978650b309cd49 | 0 .../sess_0e04def9d162ce239e6947e6321ae8ee | 0 .../sess_0e42f0bcb89c3342f5fd4f7440391e29 | 0 .../sess_0e5c1480078e1d9b5f84eba3b40bb77e | 0 .../sess_0e60def1012c7eb8dcd4e9c711f82bd1 | 0 .../sess_0e98d5ac2834ebc5bf78d2d5f0c91fea | 0 .../sess_0e9b111cebcfc845371544ee02e6ba44 | 0 .../sess_0f07e83c587cd6ba9d4374bad4a0e050 | 1 + .../sess_0f1d85f511393a35984d6532feb1861d | 0 .../sess_0f20d94dbfd397db961146e58523b752 | 0 .../sess_0f2589aadc148fbb95ddae15307b2ea9 | 0 .../sess_0fb288edf359a988efacf668fabed004 | 0 .../sess_0ff28449277d7fe69998599d5c19d934 | 0 .../sess_10009006bdabf68eb0fab5eb37ca2fe3 | 0 .../sess_110b899ae4b4a0c037123345c4e49d27 | 0 .../sess_117db80a15a6ba699f9678b99a1d9de7 | 0 .../sess_1202777c449219fd2aac7b5ffb07de17 | 0 .../sess_1247d7fb0d48d3e756c4dffbc1270e2a | 0 .../sess_124be71b45653d1b3f57538d487fa105 | 0 .../sess_125bf65956128d9a6750548f818c3348 | 0 .../sess_128a4fda8650abb78800f45754ad48be | 0 .../sess_12a821bed8de2d9944c9f8796684d638 | 0 .../sess_14624d98ed0c363b2a095334c7a924c4 | 0 .../sess_14b57fe7649feaf9706aea2773911e8a | 0 .../sess_1506f280114920a5be86dc1437738d5d | 0 .../sess_15ff36ba1e9ccbb86875e7ffe82f935f | 0 .../sess_165fb060e3ebc3fc704b851ac1d01598 | 0 .../sess_16732beb713293cfc64b4a62d20e5cf0 | 0 .../sess_16ef8072fdc04f0357bc1654ccd9b88b | 0 .../sess_16f69df80eee2cd72267465123374bed | 0 .../sess_176074fcad8595788ae0cdc97b974c18 | 0 .../sess_176897c620d6f055c1dbcb598905cc3b | 0 .../sess_1779168ab91c36713aafe5bf02bcc109 | 0 .../sess_1790c58801f9b2487b8c093cc82be8e9 | 0 .../sess_17a0585e95ccd9c51a580839630dc92b | 0 .../sess_180e9fa06a637da36cb1f76f4d0ccb9a | 0 .../sess_18c8679e062e0a332aca916fe40c6859 | 0 .../sess_18e0ea982f318fbb1005dfcda8a8bdf1 | 0 .../sess_192aef27a1fce8903cf7d28379fa497c | 0 .../sess_192bccfcedba055ff11758985c9485f7 | 0 .../sess_194c7cd0b03e84efe00b5484ca2d8aac | 0 .../sess_1950a2ef1291ec9df468535c6b0fc191 | 0 .../sess_19770a921c71cfc42485c3ac4615611b | 0 .../sess_19c0d2d4a229fd60d2099992ea8690a0 | 1 + .../sess_19fa167361a1d43b543976961054333c | 0 .../sess_1a912668b674f48b5ffc7de9e824aa5e | 0 .../sess_1aee1ec32f6fc52fcde579475a6fdf90 | 0 .../sess_1af06631c6a9c42ffe0961f03c21c73b | 0 .../sess_1b62f79b6a161a44066e889fb5890864 | 0 .../sess_1bc34aea1212c788f139097c26f6a232 | 0 .../sess_1bd8426e10991ec9c1856c793196522a | 0 .../sess_1c19ba48c4d66fb010544f9777b0b4f7 | 0 .../sess_1c1eb9653903a3f2ca0cd68b2e2a31c8 | 0 .../sess_1c1f7b1efd31a1165e99abede59cbc0a | 0 .../sess_1c8fd85f2d4fa6c41130f08f87b3068d | 0 .../sess_1daa8c750fa07697f55057eba9f07208 | 0 .../sess_1dfacd171678bbfd3713e55ee6e1c0b0 | 0 .../sess_1e0fe014daf164ddf58fba633fb433ad | 0 .../sess_1e19408096523f324050696e40e78c2c | 0 .../sess_1e3b112ac09b1917473e8d5d1f050a13 | 0 .../sess_1e3fa8cca3f53f006f75140261d3a5d5 | 0 .../sess_1e4606b18533461a9fcd557a11b1339d | 0 .../sess_1e576ef7adf606c5bdcb7341f2dfc7ee | 0 .../sess_1e607b0b5e8b9b44045b78c038ebd342 | 0 .../sess_1e752925b000debefcf8e1b6f462bfc2 | 0 .../sess_1ec360e5d18fce3b53084f54c50523f6 | 0 .../sess_1f3d92dbc703b68c7e795e5f51c1dc19 | 0 .../sess_1f6c34e49bd50f27424f86a2c7f0b2a2 | 0 .../sess_1fb01a63321004f84edde1c2f01b9660 | 0 .../sess_1fb1ae525d180aa6cb59e558984c2072 | 0 .../sess_2041982bc34bf965174b00a51558b7e0 | 0 .../sess_2043674c95a6ab2d3f71332b76dda4ce | 0 .../sess_204d8a56472208a59a1aa70ab737fe89 | 0 .../sess_2123c53e3d0044bd7f2e5cef59a41837 | 0 .../sess_21b513631be5e9c0dfd1b2d517f84398 | 0 .../sess_21d59f9f57be2f40d95c6857ba79b486 | 0 .../sess_21e0c4efd285609f6d2a1f54f58971ff | 1 + .../sess_220bdce3ff2cfd02d7e5fd4851774fdb | 0 .../sess_2230676d13498deef61f7ed59dcdeca3 | 0 .../sess_2292fab487ebf06df95e0036beac57f1 | 0 .../sess_22a4e3c7055808fd4c5a2963e52ffa36 | 0 .../sess_22af559095a190687e0d544b8eee6644 | 0 .../sess_22d9645a0764cd276e1010062ce58708 | 0 .../sess_22f0a00861d4da216089fde3b14fe2ef | 0 .../sess_23a4409db0925d7a59ad29e42bc4884f | 0 .../sess_24390c9968cd8821cd7bc5f2aca39df9 | 0 .../sess_2456d2263cd6a8256041d4b3c89ff307 | 0 .../sess_2470d1af939f31518959c76496aa7a6d | 0 .../sess_248cfaa4e75cbdb490cfe8dce5aeaedc | 0 .../sess_24a867c9d2c15b255232a9d8e1e84f38 | 0 .../sess_24e2550cf6651e9d8a37e82da464ce5d | 0 .../sess_2503f4a5cbae9324cbf3b95b5b97df27 | 0 .../sess_254c55b9ab44a7e81392a361205f9608 | 0 .../sess_255b248494b7bc41838d5e03d8c63f2a | 0 .../sess_2576dfcf73df11914c84eea9030362ee | 0 .../sess_258ba55b7396886da889d6c57b622370 | 0 .../sess_258d74a6647426ebba4ce0f0a0c67c66 | 0 .../sess_25bc952c7944ac5478e505b7d07617b8 | 0 .../sess_25bfb2e99746cac127855aca7dd51a66 | 0 .../sess_2619421cbb71439a217328646badf18b | 0 .../sess_264744b8536d1f0a4942fd4884126ad1 | 0 .../sess_26475586b26e819cbe7ed6b7c91f4353 | 0 .../sess_26817db6139724550add44986d13955b | 0 .../sess_2694eee41fc1488b7bb8cade34db7095 | 0 .../sess_271efa4ff2d546b1e0a65b6f3ddaf929 | 0 .../sess_272592a07264fcde1b184f03d1b63a3d | 0 .../sess_279218f8e2c26131e4ca39dbe14eb7fe | 0 .../sess_27dd196fe7ad686655daf28dbc6ebd34 | 0 .../sess_28136e39aa3fb81f6ea47823e5cff14e | 0 .../sess_2878c22f67e22d014964d1bc29fc80d2 | 0 .../sess_2941e7e905f15d5da543295c53736cb2 | 0 .../sess_2960b8132f30396e838b0ebdbb96dcaf | 0 .../sess_29a45c491f6998a47aa0b0f66fd83f3a | 0 .../sess_29ccade580d9dad87ac1a35ff275d2d0 | 0 .../sess_2a16d5f4beece112f2e67a4bcb781a07 | 0 .../sess_2a64bf1990c00f2b2bc1836c1b96fd2f | 0 .../sess_2ae5a56ff11d55dbcf879524b71f88c9 | 0 .../sess_2b67b6a92be7fcd363c23a7e494057dc | 0 .../sess_2c5719342be78ab42c03ddf812474077 | 0 .../sess_2ce24f0bd9ec5909e465b42fa8d33670 | 0 .../sess_2d1c750f56256eb9ba589168d9b17b65 | 0 .../sess_2d1cba190840b33e6d54f9befd89ae52 | 1 + .../sess_2eb1f32411558996d87b5d8567dcd94a | 0 .../sess_2f4233260393ed2a31e00ac9e2969636 | 0 .../sess_2f52d9fd84045a93ce4bb3a5be6a31e0 | 0 .../sess_2fb5535ba46f2edcc64f6b7c581b9c7f | 0 .../sess_2fb9f36e4449aa120905dde208f60510 | 0 .../sess_2fe3e2083c13b197a53c094156873f5e | 0 .../sess_309ebde30dd24884b70bd8df07279749 | 0 .../sess_311959d4bc3d190ae2499291e9b42220 | 0 .../sess_31210c92118f237f856a58f569a84141 | 0 .../sess_3160a2a2b74ee599bee5f62ac40e1591 | 0 .../sess_31eaca9ab96ee1d5a814bf3202eb3c22 | 0 .../sess_3211e64538a844841fd3167dd09dbef7 | 0 .../sess_3212a8af9dd17c466ab8ea91052c3cc4 | 0 .../sess_32932fd7d38fd6c8279e60d44e48e922 | 0 .../sess_32c66a45bdf19b9f50de688ea047e696 | 0 .../sess_334e686c0876c391c42fb587b3bfa976 | 0 .../sess_338deba0ad100d079f68ea413a6e3048 | 0 .../sess_345384363d30f764d4244e131c21d36d | 0 .../sess_354431ef347b5ba5cfcc7273061da04e | 0 .../sess_35a3681412ab8e77fc498a7699733368 | 0 .../sess_35abdcd2565ba6cebc1d5323c51976ce | 0 .../sess_36323cbf08bff404511acc133ecddf9b | 0 .../sess_3689a40959fc96cd6f3c50abc1203ff7 | 0 .../sess_37282ed122e7b4eaaff47a3883309651 | 0 .../sess_379e5c9dcce57030269caa935f44b608 | 0 .../sess_382329b5050c26c18ecedab29e115c9e | 0 .../sess_386bd2888cf16751430e80917a8f85fd | 0 .../sess_387a266ab74eed24c6346816390f7769 | 0 .../sess_38f8f88f7abb27ac17d9375a5a4cba31 | 0 .../sess_3bcfeced08df9ceb2af9241e3e4204e5 | 0 .../sess_3c6086415f4fcb8af3e7af3207b96d8f | 0 .../sess_3c6c3f9430ffcd8fda0687736ec2d718 | 0 .../sess_3c7b2b014f20046f58b831fd0ad34c9f | 0 .../sess_3d6993df0f462c4ffff7556ec530e435 | 0 .../sess_3da2ba55a8c5d696a5814b42dc0b2e14 | 0 .../sess_3db0a0533c6ffd6078c55906988a594b | 0 .../sess_3dd3b5c1c41191d551256202b307b743 | 0 .../sess_3df20e7c93a01e37ead25a82abcfa7d8 | 0 .../sess_3e081a2d79c698f00a94dff6db2e962f | 0 .../sess_3e0baa6f9f93aef56b4c28cbe7f0892a | 0 .../sess_3e34570e03450813b46f65a5332d9c1e | 0 .../sess_3eba8aa5bdff2f16073b6b03a879f828 | 0 .../sess_3f4d92dfc22756dc3491753c3710d58d | 0 .../sess_3f5d80074117dcf2345eb24489088752 | 0 .../sess_3f7f9ebea56914012835d097cf04ca92 | 0 .../sess_3fae57c4628e0e6de8700f2aefb0b86a | 0 .../sess_3fde18821c79b139e6cb19e20b72a0d0 | 0 .../sess_407b46f6532a47e17ec5babe6c30bc4f | 0 .../sess_409dcbc73298f3e020b7c60a3432fde7 | 0 .../sess_40f2a96e744926f0865a52dcbac9a160 | 0 .../sess_412a5eb5d0597f918c0bb9bab03b2faf | 0 .../sess_413faf6f188d744d9522dd2fcf41495f | 0 .../sess_415f109c0c4fe4d2a1d4979ad5d9bb24 | 0 .../sess_4181fda8edd2d12b37f109fad33ac6a7 | 0 .../sess_420cdf45b5c2330160d506a70eadb33c | 0 .../sess_4247a0c7906fc90ae18a64adaf1aee2c | 0 .../sess_4254feb9a5b1a5b349f5dc3556d1a201 | 0 .../sess_4338f754256fd9a05ffce8620f13f07b | 0 .../sess_4368d3d721b713fe8f89cfbb6e1005dd | 0 .../sess_44b7c1306ee68cfa7a89fbc4e043209a | 0 .../sess_456a3e7bbad68012698a87d1072c1b38 | 0 .../sess_4587b7a5e1e5ef217166c56763ce423f | 0 .../sess_45a98e9ab472b14179dd14db5f14bbff | 0 .../sess_469e07211c9f0f2764b6d7a2c851ee46 | 0 .../sess_46e2408207b1c95658fb7b626ee6958e | 0 .../sess_46e8ebf399968a345980d5a95e8c06ff | 0 .../sess_47b05bb74c2af2828b06cbf5e3ff8477 | 0 .../sess_47bf2f6a7024254fa8971c7a85b0910d | 0 .../sess_4853a954dcf6e2ad4cfa31535490e110 | 0 .../sess_497ce6e38a9c5c1015e856f7589548ad | 0 .../sess_49a86631804a0d6da2aee719475f739a | 0 .../sess_49c66d8f15ac3c3053c8534b28fd2ef0 | 0 .../sess_4a45962e93cea2ed73e1665936bc73a3 | 0 .../sess_4a6501b0989d99b4dab93189ce30fad6 | 0 .../sess_4a8e405b702f60027e17800b0a9ef45b | 0 .../sess_4c9a1aaceaab78fddc55765178473d79 | 0 .../sess_4caafb58e3487c63db3e767199e558f2 | 0 .../sess_4ce44d9d6463335e836ae3de308d4b34 | 0 .../sess_4cfbbd26846fb01c6f9d339c557b03c8 | 0 .../sess_4d29d6096a285987a2b2e625068ff5ed | 0 .../sess_4d7c6193ca1cb655e8d82dfe9c6c7eb4 | 0 .../sess_4e38e1eb4f49de672fed6ed540681642 | 0 .../sess_4e71c4ae079c79d06b3b4199b37c8d6e | 0 .../sess_4e8ab1cc7be41320be57403a5764d80a | 0 .../sess_4f3604d0593e4bad5b3c1a2ee7fbcbe0 | 0 .../sess_4f758a3b77fb168967f90b28555033d0 | 0 .../sess_4f9fd9991f5f3d30dc0ac97c372b731c | 0 .../sess_4fb189b01ba2f06e00ef5f9b5a7f23da | 0 .../sess_4fbfeca9a5fb8855a221e690ef502b38 | 0 .../sess_4fc591afe53506325ffb61b8bb9c88fb | 0 .../sess_4fc8717e82c041e7287a910c435a73fd | 0 .../sess_509426117c6882aa1596380c7cf47a87 | 0 .../sess_50f11f6d3fd52e9b97404943e55c90cc | 0 .../sess_5104ac2b3efed41e0167e8ac230efad4 | 0 .../sess_510691ebd56ac6658a8f0d05def56656 | 0 .../sess_519f45ae9f86f930f3abdcb63300057f | 0 .../sess_51e0aff602241718c4b8416f452b580b | 0 .../sess_51e0b1db7e62b4fdcdd8306d0c23943d | 0 .../sess_5283908e9b80eb47d1f52f4d4400605d | 0 .../sess_5294cb940dc83bcbd4bf57b17a00ba3f | 0 .../sess_5341c2fcecad979f5311ceac1bd28718 | 0 .../sess_53926f2b0c401a2668cdb0ee55739e71 | 0 .../sess_54e9093941ef282786b14c14e94470eb | 0 .../sess_54f3fb06c0aca2a55c2dd5ec4d25512c | 0 .../sess_55398030f9196b2640c7bad95056c2a5 | 0 .../sess_55b079046d294c1c69f287fd4337d04b | 0 .../sess_55e94cdd1ed910c96f22fc4e1f73c0bd | 1 + .../sess_56355cbfc04402cf36d92f821bae5090 | 0 .../sess_564322bb4ff156306233922d7d4fb345 | 0 .../sess_574d1c7a48adf43155170876b326b4aa | 0 .../sess_57744595846324be63e78c3aa28a4f33 | 0 .../sess_5775e29dec1dea074f4fc9c32fa3a34b | 0 .../sess_57e47e62da11c1399a779afd1e78b75e | 0 .../sess_583371d8396ad02e05cf1ed99be48719 | 0 .../sess_587bf41af67b4725cf167a5652622640 | 0 .../sess_5971640cd75544c110ed69c8b2704b38 | 0 .../sess_59d0ba86b7085a23b070b559be815e6d | 0 .../sess_59d76f244bb8bfebfc4b923adbc15419 | 0 .../sess_59df6b8eff37a8f46f806ef70d53862d | 0 .../sess_5a4ae2edb0253f99e7e0d768060c5e83 | 0 .../sess_5afc92d91483488a5e2756611359c02a | 0 .../sess_5b9ebb48cacc40324a7627ed5cd03ea3 | 0 .../sess_5baa5d3d23563e43afa7446486dd72f8 | 0 .../sess_5c0025a4a3bc7edc09d828efe2b28cac | 0 .../sess_5c0f0b21134060b451e12ef30081f540 | 0 .../sess_5c2d18b62ab5f4e62bf93bf01d6a8abc | 0 .../sess_5c49211f43aa46d12999628ab1a4bd28 | 0 .../sess_5c572f6972ef0bbc0fdb158726375a0f | 0 .../sess_5d5cd460d1d5835451d781e05c3adf89 | 0 .../sess_5da65e8529c7df8023ed18faa35d5a9d | 0 .../sess_5e286521edc4303f85116330fe10e8a7 | 0 .../sess_5e7a74bf5069e74d295e89a325872261 | 0 .../sess_5eaa995db9fb15a416a357e533b6b131 | 0 .../sess_5ecbe991d413b362c14567f1f073f4cd | 0 .../sess_60668ea8e3890507478e420a56ddbd5f | 0 .../sess_6143815503ebf3f82c544d21c1b7ad5b | 0 .../sess_61b414feb765617dd3fb38f7eceffcfe | 0 .../sess_61d6228ae940365ff8bbd93a87303a02 | 0 .../sess_61e9d1453030accdf287a54e918ee927 | 0 .../sess_61f558ea9155fbeba01a42a2931d7b7f | 0 .../sess_621511f8d7c30ebc5aa01ce455b8ebaf | 0 .../sess_621ef44ae59c047ff712c18d050011e5 | 0 .../sess_6299dfcc8ce40cafed3a335a341a6336 | 0 .../sess_629f9dd62d1204aba62dfefe65fb0d74 | 0 .../sess_62b5d304a31805cce616357f0e59a9f2 | 0 .../sess_63a233987c509e08fae79917349f9bb3 | 0 .../sess_63e9364f0c944608a17504f9b91784b3 | 0 .../sess_63e9c9819a2de781d792f1d2a60851ba | 0 .../sess_6477fc14a025865e68f2dea333bebea8 | 0 .../sess_64ff70ad1229278f497281f0214ccef0 | 0 .../sess_65468453a39a8a4dfaa1082215ca9836 | 0 .../sess_65b339d0b1ab7066ca6b44ecd8facc40 | 0 .../sess_666d42c52b9ccca9698401d63202cbbf | 0 .../sess_66f307a1423bb488cc26a24cc5c4a42a | 0 .../sess_6719a214f44edb74418e6e890bf5ac08 | 0 .../sess_676be8f36a7e54aa1863917c871eb567 | 0 .../sess_6779e0e89629bc58388284b76bd76192 | 0 .../sess_67c0a94fd2af88c6fe4e5fcd34036c36 | 0 .../sess_67d11c2d90f54a43e96d16bf3c7c45ce | 0 .../sess_685d40735c321a9ffb35d557798cb1b5 | 0 .../sess_686f0c41b1eaaa6c33131bff43058b77 | 0 .../sess_68be2f0cedefb60f859f4c4d9c2a880f | 0 .../sess_6908fafa1345473eb21f5cb9dcb153e6 | 0 .../sess_692cbc36b4318bfe850fc4aede3832db | 0 .../sess_6972dfd4b41c386eeb36cb9140582224 | 0 .../sess_69c6be375919a05a7948264c4db4e9dc | 0 .../sess_69dea144c3340502072b895a59b41b24 | 1 + .../sess_6a4b12a9d81b4f56fdab7814f88b59bd | 0 .../sess_6adef316d6f11d1cfcb4765ee289aa95 | 0 .../sess_6b3dc5917d807ae794a1ed28fc9e99f5 | 0 .../sess_6b45e848464ad9143282b3909897ee8b | 0 .../sess_6b8aea5fa4705e2c8928d1ec20387164 | 0 .../sess_6bf7d9cc4cb88d9fe96dafc8ed1288a9 | 0 .../sess_6c0ab8e461aebf50d0ed2a344176ff8d | 0 .../sess_6c601c0358a1d9724a5252cd756bd64b | 0 .../sess_6c676b2e4c18eff505c1853258b039cc | 0 .../sess_6d894c43c030d8dbe071de186700c5c0 | 0 .../sess_6da8ac394f5fd3b79b6eb9681f810c15 | 0 .../sess_6e5401ff80e23d4dbf547c4a894fc211 | 0 .../sess_6f3a5c611d7a16c43ca25127894ed8f0 | 0 .../sess_6f84b5c8b742313a8b748867bb5dfbc4 | 0 .../sess_6f98287cfdd3ff93e952baba05fd5c31 | 0 .../sess_6fd95d60ca7ff94f3c602ff4753e6a87 | 0 .../sess_700631b2fa80717f2d07a6017d5ff030 | 0 .../sess_700aaf47edb8ff558be85c5bd9ff529e | 0 .../sess_70a66109b64b26d0ac67524166cb72cd | 0 .../sess_70c642898f8f0725b81e4d285eec0787 | 0 .../sess_70ddcec0c3417113fc4c072fa39fe331 | 0 .../sess_70f9c044b76efda9274b32b5ae345d56 | 0 .../sess_7108ed71a1a0054c7ab7c7c2068e8923 | 0 .../sess_71187da2021a77805c96d85b76867d92 | 0 .../sess_712f6993fea7e24e5126734f0aca9a68 | 0 .../sess_71a643c2efaef2d77620f637e5637cf2 | 0 .../sess_71d766001d452c1d949ce85cd327574c | 0 .../sess_720f14d25a4f2ce52fe9d3933e014771 | 0 .../sess_72515b3f3d38bd85e703ef3a80b1bfa9 | 0 .../sess_726860093a0a7908e0e9f66e709ef6b5 | 0 .../sess_72ab8f6fd0fecbe3e0976c623333be7b | 0 .../sess_72d5d6b9df6a55503533e97e340d2311 | 0 .../sess_72d7576d9bfaea5245d54bc6f5ed732a | 0 .../sess_72f0fc1c0ead3468d75f171ff2353eac | 0 .../sess_72f8a1f9caa69b25fa64c6185238c6e7 | 0 .../sess_7316664ff3b366d41d1a0f1d29a21e44 | 0 .../sess_7327bbf8e70879c93cb9fcf18287ef57 | 0 .../sess_73372d764d0b64494a123b95b092e293 | 0 .../sess_73c80e71dcc72c4c3bd422dc2d1c6f22 | 0 .../sess_73fd9c9fd27ea5b4b7e513723fbe0d01 | 1 + .../sess_7401b0da36a0bf586a412bdc32c01932 | 0 .../sess_74169a80690859946b251a7e2789c06f | 0 .../sess_7433d5d741f0c55568cb183264a757d7 | 0 .../sess_755543053755f9827042ecd578a32ce3 | 0 .../sess_757ed514e8bcf21fe484abe2639fd9c8 | 0 .../sess_75ed4967153522837eebaba2104a96bf | 0 .../sess_76036580c2a0124ef45fab001e1b8e85 | 0 .../sess_764eb3d09d7acdadf02a1b1b9e652d38 | 0 .../sess_7751d4611abdb82cdd8307b7db2a94a7 | 0 .../sess_777222d786447c44cba1f61c40722851 | 0 .../sess_78ade15b28ae2ac4544641f92908c944 | 0 .../sess_790eb3970d7871971226a752514c9021 | 0 .../sess_7a39b110e92a44f5cf2cc27318fa8acf | 0 .../sess_7a49c9790485b15f032f1889edfb5e74 | 0 .../sess_7a758afdfa5c19238907ee69e4f1b8e1 | 0 .../sess_7a942e09063d7d053ece5084917a59c6 | 0 .../sess_7adb1835b69eb442280acb1a34e3e4fc | 0 .../sess_7b1b6e128d0bc1b40a30954812f3f2bc | 0 .../sess_7b30a67240cb34b565faab1ee1a43759 | 0 .../sess_7b3b609d4c8713d06961731c28fdfd8e | 0 .../sess_7bb08b912f4e650b85412aefe4225981 | 0 .../sess_7bb50e18de85135463e20f8d52dbd679 | 0 .../sess_7c34f05d08306cd0e725b262006af9c3 | 0 .../sess_7c4815b8c0d9564d5c330dc755b89be4 | 0 .../sess_7c5d6ce984c7f26e83b67c6a785f2919 | 0 .../sess_7cee3f52567d4d995de6c242576e80e6 | 1 + .../sess_7d496df6ac57de8fe4c46d48271fd6b6 | 0 .../sess_7e2cde5aa2397f9c928af07c857d9059 | 0 .../sess_7e394c0ae018145fbf007d66dae56fc1 | 0 .../sess_7e44e9b5e4618ac9c5d5c50156aa9481 | 0 .../sess_7eb154203930535a374d572b26905543 | 0 .../sess_7ebd209f80c9a78715a398ca29339183 | 0 .../sess_7ecdf19414bb4c4fea4ca19850a7a539 | 0 .../sess_7eef0ffbe7eb689711c83038742d15c7 | 0 .../sess_7f11e5dde38e860a69e208aaa622312b | 0 .../sess_7f97b81d9a078302523e924f6cd12861 | 0 .../sess_7faaa1325462a16eb0c09a78eb816c3c | 0 .../sess_7fab50507d10a6d85e79235af15d32d2 | 0 .../sess_802f12cc03180b848c62d75314cea4ad | 0 .../sess_80ddf16c4abf6269af3663c2773029e3 | 0 .../sess_81367cb6c7667b5ce00936f7e4c75ed5 | 0 .../sess_821cee680ccebd68e213681a79f95074 | 0 .../sess_82c9e9a8ca48d231b1a6143016f12161 | 0 .../sess_8390ca0087f926e6dc6acfc1726feac3 | 0 .../sess_83f1a287583b2bc46cd195e1e3b2f943 | 0 .../sess_84323f8c4143d694e44c9fedcb9f9df8 | 0 .../sess_8506d9ad640e8db4c5464ae18b21aeaa | 0 .../sess_854a85d95cf30e598d3dd8071c524182 | 0 .../sess_8556739092f403601805097e7c5515d7 | 0 .../sess_8578f708494846a3ded7c61783b1424d | 0 .../sess_85bde53b19790499e9dd46a1728f2644 | 0 .../sess_8609c314bca4daf7b8d418593296f29d | 1 + .../sess_8631135c2359b355b28b12b251d7cf20 | 0 .../sess_869e70a50cdab0e5dfa7f41decbe164e | 0 .../sess_86b6cee8d0e002bd2cde78c01432c98b | 0 .../sess_87765df5421b4b08ce0285280372e9bd | 0 .../sess_889d219494675742314ef1f2307e5059 | 0 .../sess_88af4a1e93188192b41b898c96537666 | 0 .../sess_88b7f3ed2893c08c3d3a90674de9828f | 0 .../sess_88e7499f47401ce614b4b65ac8356131 | 0 .../sess_891ef4846f2cdff9342aeba0a22d5f98 | 1 + .../sess_892e5cf319f4e18afdb304abb39c54e4 | 0 .../sess_89625043f67d25fa8715915de6bb79c5 | 0 .../sess_89a3b9f54d67342368079a0ac616276e | 0 .../sess_89d0ffe96d3b0ad881121413908b1b83 | 0 .../sess_8a6817782ff0d92875e6235d8ad776fc | 0 .../sess_8aa40af232be8a3ca5282ef5f551afde | 0 .../sess_8ad98e7026500f40515eae94a2fde2ea | 0 .../sess_8b186147e95653d8a3b6c748d2482dcc | 0 .../sess_8b63e5d2df259415a70db075ccaee244 | 0 .../sess_8b8a7cdf7636a6d2aedba08f756d3c8d | 0 .../sess_8b99977c6b9caff09ba87062b2bccadb | 0 .../sess_8c02e46467ce79c739e0a68444178b2b | 0 .../sess_8c6134b631065799b0ec7bee4049c4ab | 0 .../sess_8c7302f6a643e4dee153b7d114ed8783 | 0 .../sess_8c909900d173078a178b390295c8d241 | 0 .../sess_8d2904d8920390de86ee53bd52ff2f95 | 1 + .../sess_8d3b9af6665ae182d39cfecb43e8c67c | 0 .../sess_8d6b378636ae0e195107452f07a9a1b6 | 0 .../sess_8e2e34092cc5092b33f80789bd8cb5dd | 0 .../sess_8eb44802c4c08e84235edd0bf26ac87d | 0 .../sess_8ef6f0dee24d8b4ca5d6d12fc78846d3 | 0 .../sess_8f45c8b37f65f5a3d96d99bbc42afd6a | 0 .../sess_8f59107da2dd4018eaaf396b9d4a5a36 | 0 .../sess_8f7dbcbed4677531f01a98f09b78e579 | 0 .../sess_8fe1ec9a1ace4c1e42a2f556662301f2 | 0 .../sess_9064761135752f81f7ad8b6ebd661023 | 0 .../sess_907c362f4f2f6e400df8ac189f9f1eb1 | 0 .../sess_9087759ac2d7809daa14034e92904b12 | 0 .../sess_90b94e25c780902f5f539c288bfcf537 | 0 .../sess_90ede0d038bcbb6aceea8fbde52f2641 | 0 .../sess_90fe76e7383355fe08eb307219e2d816 | 1 + .../sess_9198bcfeff6d315cf4fafa6370065a03 | 0 .../sess_92c58ef6f3c99152d2ba186841a33c7b | 0 .../sess_92efa0c840cc52c16596b78a5df32412 | 0 .../sess_93f7f0d85f2643a76ef136e26da9e002 | 0 .../sess_945189d26d0ba3469d510a86ca265d85 | 0 .../sess_947481d841466c3a8daac3ad85aa115a | 0 .../sess_947988622ee7c2525e6cd5d894c98ef1 | 0 .../sess_948b86a8e2a1e31167679e4a1470c860 | 0 .../sess_94e7ec5946942ead16448a728abcdc37 | 0 .../sess_953a407fb72605ba0a9a187e23cfe770 | 0 .../sess_959d4c4a242dcfeb97c61a3ea2412504 | 0 .../sess_95f18066efe5fc4f9cc8d0593c8439bd | 0 .../sess_9644fd126a3afc481ee73045a503baa4 | 0 .../sess_967b281af59e4a6dcf1cc1fbc3265a9f | 0 .../sess_975c0b932ae0b2f7e84907a50b8ef1b1 | 0 .../sess_97f50b3d8687d8d47bd563960e1ff455 | 0 .../sess_98892de18bd4ec4aebcc5bee99ab94ac | 0 .../sess_98bf4125aefc7ddc22a20c48a9619989 | 0 .../sess_98d9a575de9d3f13469f22cccc63825b | 0 .../sess_98ea1635d552d9a6892dd0ef71435f1d | 0 .../sess_9959bb4c68666277b4b2c0282a90487f | 0 .../sess_995dec3fc6c2f46d050ebb554f56cd66 | 0 .../sess_99ad47b9cdcc23e23f1e7795dab1738a | 0 .../sess_9a0e601461b3d9b104215919d000998b | 0 .../sess_9a7db4f72bf6ba3b89119eb1577e6915 | 0 .../sess_9a81f6a69cf9a752f08a42af7c04dd7e | 0 .../sess_9b0faa79ebcc182ad631d2427d6ccc41 | 0 .../sess_9b9ffd3f5d1b3b7e7fb4ab8021ee6724 | 0 .../sess_9c87061c647a1863348a102fda0a485b | 0 .../sess_9c98903028ece4cf33487e12338563b0 | 0 .../sess_9cc05cc46ce95864ad3e87c922bd7d6e | 0 .../sess_9cc7d48622827c73b5c2800b90da6c71 | 0 .../sess_9cfbca439e6352c64927f83dfba56d33 | 0 .../sess_9d0f8c2d99f91d53540c981a669307b0 | 0 .../sess_9d585af32abcbce5c19f7dc88013b509 | 0 .../sess_9d95d6e5b127e827982ebd70a7780ac5 | 0 .../sess_9defc0b6e6f8e6a06175fd732c55f9f4 | 0 .../sess_9e0ac6eb04ae0415245380aff8a7fe18 | 0 .../sess_9e48c6c6d01f1fd30c8ebe200c54ae74 | 0 .../sess_9ea742913c11d08c3a459e7cbdc48ea7 | 0 .../sess_9eaed2cf9bc04a09871029820a0febdf | 0 .../sess_9f24d1e11f4da71f33ba80b598bf4635 | 0 .../sess_9f669749877accc7939a55a65470df73 | 0 .../sess_9fd8760c7cb04dc202cb2fcbd65fa171 | 0 .../sess_9fddcfde761802254047b04993e2653a | 0 .../sess_9fee4660d995f9fd6a5586af67f086ea | 0 .../sess_9ff67287131509f45cc8628249181573 | 0 .../sess_a04f945480a6c46840307cfd5cb133c7 | 0 .../sess_a080edfb79e15193b1a986d2bd1c8fa4 | 0 .../sess_a0cf773a2425baa5763a3c5151c1e6a3 | 0 .../sess_a0ece2f1244996e6289cf22fbe69523b | 0 .../sess_a14487d530470e659ef4624121d3c2c1 | 0 .../sess_a267492beef8994b2bea7a841d1a0cd8 | 0 .../sess_a285d45c3472af1ffc36a684c856ec99 | 0 .../sess_a35bdd52888e241d85684a6d02a68bde | 0 .../sess_a3a3b332d752eebbfaa33f846f2011e8 | 0 .../sess_a3d22e835cba5db3a924784879c04be4 | 0 .../sess_a407d22c03234511a8692574b1afdb55 | 0 .../sess_a40a10b60dfb97885a13a60cd56ab03d | 0 .../sess_a41afc440e071af84d3aa05ac5272fc3 | 0 .../sess_a4599efbaa95f71a3a07ef11cbdb2982 | 0 .../sess_a50197cb3ff817a6e011f450bfc33d48 | 0 .../sess_a50e65f017e7c264acc21fba0639a32a | 0 .../sess_a559140c7253e91fcd6a8f6f19e05bff | 0 .../sess_a594961a601d02783f91faf40e98312c | 0 .../sess_a5e3f86f5e484f1b76040bba1e5e14a8 | 0 .../sess_a6ad3eb821ff648a929de3ed3197bcb2 | 0 .../sess_a7cb6058536c2c10eb132b9ea2273034 | 0 .../sess_a7fa2f402ec1a091b24e4dbe172fb7ce | 0 .../sess_a80186e2242223f495ecfdcca4153e08 | 0 .../sess_a81980865d0ec43ef0c5583e07965859 | 0 .../sess_a87aa73edef2fa7a6931806265cea0c9 | 0 .../sess_a894ac582bee396f28b4a5b6d76632d1 | 0 .../sess_a8eed80b4e6c374bce30583a334fd1be | 0 .../sess_a8f1dd35649e9f13895d193df5dcb5ba | 0 .../sess_a9196e31df6affc38cd0927354660e20 | 0 .../sess_a95b3114ad98063dd3ba2e3ff6ee4089 | 0 .../sess_a9641cbe82cd2fba425d8bf3f1fa632b | 0 .../sess_a9dc429cc9b5c625c3b61d1647124d00 | 0 .../sess_aa0e7054e4831c8e95c9d968a25ca3c4 | 0 .../sess_aa6859d5e79333e0b7fbf8152c956007 | 0 .../sess_aa8f8b163bab75cba3311559cd7cc687 | 0 .../sess_ab4e2933297bec79168036dc7f3d1c4c | 0 .../sess_ab893deedb9409a65c6b4c2e1fd21135 | 0 .../sess_ab89791d24d1aaadefc456a1086a2560 | 0 .../sess_abb03b95526f02aafe816672af681ebc | 0 .../sess_abb7a002d65efea430cb0af30b78a078 | 0 .../sess_ac0e42e33c97dd59d436c667a5b3eead | 0 .../sess_ac4027a844896a8a22c308c293a15f65 | 0 .../sess_acab22928f8fdc15eb012b3494e41797 | 0 .../sess_ad5c7d07120b55ddac12237c4bab0e2f | 0 .../sess_ada72e41e49e00d5f656e5da43eb6bc6 | 0 .../sess_ada8c2049e1c9327b3a50dc425f6bfbe | 0 .../sess_ae1d8a22efd12912343d561876fdfdc5 | 0 .../sess_ae234126326f18ee35485c944b7e5848 | 0 .../sess_ae34a72b4fa6668803ea46d383ea6f24 | 0 .../sess_ae52f8b68722f536aa70b7590035f9e3 | 0 .../sess_ae64540044c09c7b795ced78505c9b4d | 0 .../sess_ae70ef607969820ee6fa532c7c516c0b | 0 .../sess_aeafd646edb77412e928721505f984c7 | 0 .../sess_aeb687357e6f692b2199e9e800d56673 | 0 .../sess_aefb95455125a664bb95a1c2ef06f8ad | 0 .../sess_af4792ba9e683db497c8912f32609da9 | 0 .../sess_afab05d39b52ab8998daa2eda66b6aee | 0 .../sess_afeb67ef6a5ce209e756766ec3ffe2ce | 0 .../sess_b024fc7d8e3356fcadfeeeb2d14b1815 | 0 .../sess_b0c1002f2db893b0a6af43e0b577dfe3 | 0 .../sess_b11cbd54af2f7c7e43bcc82b0279e10b | 0 .../sess_b17c2592ffc464b4ba0b679af70ded2b | 0 .../sess_b1929d13ac18473aeea275a695808d5c | 0 .../sess_b1da207d6c31fc9946ba3621f021ae80 | 0 .../sess_b237e22685289229a1db2608891cd30c | 0 .../sess_b2a31fa222e71d201828baf1ecf324d2 | 0 .../sess_b2eaddc565cbb2b2e87616e79caeeffa | 0 .../sess_b359782f5ccdab7e806fbeddf150dcbc | 0 .../sess_b36f9ea866728a5fd33680170aa21c86 | 0 .../sess_b3a6ab8bfc108f99d05e83799ba0c606 | 0 .../sess_b3b8e7ee9034bc9159f2b56875fd7cc8 | 0 .../sess_b41c95ac09de774cf44fb1e10f5e904d | 0 .../sess_b44275f8a8435fbf5025d135126479b1 | 0 .../sess_b491790afd14563692ea36be5588eabb | 0 .../sess_b49f8d76aa16a1f4aa34d99f4cc1e1b4 | 0 .../sess_b4dbf3766611612508d08f46c9b190f2 | 1 + .../sess_b4ea676cb3216a36a1693463ef5b7c82 | 0 .../sess_b529a32f36544fee5e632be00fac097c | 0 .../sess_b53d30d81ffaadae86b647f47f6a1160 | 0 .../sess_b53d83093d2c2083ed7132b123982264 | 0 .../sess_b5510f652a52854c7651f6918feb977e | 0 .../sess_b57a0d89a948fe34f8dacda9cb43bae4 | 0 .../sess_b58721ac9be6edd4764bf6f8780cefc1 | 0 .../sess_b5a60f9daa55f9734f493d6ac0458573 | 0 .../sess_b6ec78ab609d0df2fd508e9163c253e7 | 0 .../sess_b6f50ca4efe83c023d25e4ebaf6487db | 0 .../sess_b79d07314aef27e7f198bbb8b140a36a | 0 .../sess_b820236352b2730de43cf836422e206e | 0 .../sess_b84c49ccaa5176f3f60d430f254f4009 | 0 .../sess_b908569ab6c58bd1746f5f4fd534f934 | 0 .../sess_b91a01b1e5d207e45e16934c8ac97ee8 | 0 .../sess_b9c58c1055898aec49f2b876bee0989a | 0 .../sess_bac0d94744e5d5ef89296100c18f0090 | 0 .../sess_bac2cfc9597d42d210a3862582579fa8 | 0 .../sess_bb4a64bd891cc061c3adefcb44477af4 | 0 .../sess_bb87f20b1e8a384d46d56453cad1e43c | 0 .../sess_bc2eeb565a25074bbb1662c9574c76e6 | 0 .../sess_bc471f7dacb30aaac0280f97b8b2a40a | 0 .../sess_bc6a3acc6bd9424ccd4f53381763a765 | 0 .../sess_bc9a6fcb99473c1d425a69fc74e2816b | 0 .../sess_bcba56fadcb6d5fa33aecbc0accdf024 | 0 .../sess_bd4a221108dfce794cd7f70d523dd168 | 0 .../sess_bdb7dda4a0ef116d409e5e69c1ac3fad | 0 .../sess_bdec0ef240e9a5bb1a666d8984758976 | 0 .../sess_be317991eb08458fb8e9abcf374bed36 | 0 .../sess_bf428a4caf8a7d4c47d26f32e16fe043 | 0 .../sess_bfe6ee4113b5571017455a38ee1e2317 | 0 .../sess_c018a7218b523ecc2ade22f7aedd38f1 | 0 .../sess_c064150f27b1dab4111a60ccc6f9d2fa | 0 .../sess_c0ed531b0f4e24c41d874a91cba8c32e | 0 .../sess_c1f34ca67059e96e6bcd732eea2446bf | 0 .../sess_c1fe8bbe901ba4706855bd9eb723ba70 | 0 .../sess_c21b23debb7c016fc702f87a0294f3e8 | 1 + .../sess_c2426b9d31ca734eabd4e282665bcb9d | 1 + .../sess_c26296dc097c2f01694ee80ae77eb6e9 | 0 .../sess_c2ee1217613c2a80e949b8716990ca88 | 0 .../sess_c38c51006dab56733498c50180b3fabb | 0 .../sess_c3a730d7b6a59d913d50e31df3e4c3a5 | 0 .../sess_c40e44707ce35c5ef27e8802aa39e47f | 0 .../sess_c441f7bf34ccd6ae14805365c2ab89c2 | 0 .../sess_c45b5e61c6d053c4f763d91407f3c91a | 0 .../sess_c475fb1bab33463d842d886993c1571f | 0 .../sess_c47945969b61a07537f19f946a384741 | 0 .../sess_c48155c30b39c8362f86aef75c221163 | 0 .../sess_c542af71129f343b6408eade9270bd54 | 0 .../sess_c54748085a5be98912b747457ee83e87 | 1 + .../sess_c54891068be346717c001cd9135f2f1a | 0 .../sess_c57f6f9decec3156c65ae42ec51d39d2 | 0 .../sess_c621bb0f553660ec429aec2ca0f43023 | 0 .../sess_c6429f27fce95541dab9e8ed4952d4bf | 0 .../sess_c6bf258aae59585a0e4d3baa156de525 | 0 .../sess_c6cc348dc8a6b160339d4efeb4ad5a73 | 0 .../sess_c70b845bd7ace83384142056d6460d23 | 0 .../sess_c721d5b6b93058bc391ed27a94c9b40f | 0 .../sess_c75f86e937db953edf0dc710594a1a34 | 0 .../sess_c78586d22fded1470a8af498186b8e7a | 0 .../sess_c845a8b285996a7f190591807a2e5da3 | 0 .../sess_c84f8233f589f47a1d1f3c95256a6d54 | 0 .../sess_c8c9fc84e7bd6bb28854c759734a4aaf | 0 .../sess_ca26d03afac9fb59c49d2459b766b0e2 | 0 .../sess_cb10ef5334885e0f4bb07e60b95e4bea | 0 .../sess_cb810d18223327abb72bbbce488255a2 | 0 .../sess_cc6ca9b9be103498b3b3f9bebbfcca8e | 0 .../sess_cc7cc7d4d0e038443bc7f215fcf60584 | 0 .../sess_ccbfa7c487f33d217038668dba1c7f98 | 0 .../sess_cd426fff8fdfa8c52522fb101ed1c200 | 0 .../sess_cd6936d236e56a0958f326724caa9f66 | 0 .../sess_cd9a052a6c053712db213bcbd569b448 | 0 .../sess_cdb2d2c089e55ec49e126ffaee9da500 | 0 .../sess_cde83bbf5f7883231a8bcefdf4b5749f | 0 .../sess_cdf9116287630763b1acbe585ed5e0a2 | 0 .../sess_ce5934a087e64591d7f2d218c00ea365 | 0 .../sess_ce8a8426d076db8a3d564204d4e29b24 | 0 .../sess_cea4f34bc0b31c76efbc1ff1a6dd88bb | 0 .../sess_cf7075fc4f5727c449d2b7fa06303609 | 0 .../sess_d0054e42522dc3082a38d55702832585 | 0 .../sess_d053186c866c8aaafb3c49f5fed373fb | 0 .../sess_d082df7384cf4c3855e911af2e4a6d68 | 0 .../sess_d1b6c144193e51c481f30adaa6f5ddb3 | 0 .../sess_d1d621d535d1444562ca2d1b7a43400a | 0 .../sess_d256ce4f2ded89a00bcf19cbcb1bed64 | 0 .../sess_d27d2227ad5637ccbac3579310e60b6d | 0 .../sess_d311a3d90755123ef2e6e86c68eefcf3 | 0 .../sess_d35e6e95a5fdda69f32a5a8bdf8964ac | 0 .../sess_d36cf7d1753dc1ef650e4ad031194f3b | 0 .../sess_d37c9bba5a42b3760ac4fedfe02002ce | 0 .../sess_d438212c8192544d26843d87bfda1e5b | 0 .../sess_d44df14d2838d90e63d1c5ffeee18b63 | 0 .../sess_d48edd5255a24ae738e3a13c31f7bdb2 | 0 .../sess_d51b539ad255419ea6fd83ba3e9caa39 | 0 .../sess_d5ee3033b6819cfd67d2ef4d8feadeae | 0 .../sess_d6173d6126d730e93fa9cd6774f53be7 | 0 .../sess_d655d61f4acc04d61814576220dda1a4 | 0 .../sess_d761d9849d639d0449a853e9965d2558 | 0 .../sess_d764f7b5069d52949a1249384c2bff40 | 0 .../sess_d82cdb26dd412905a1b72ed027a14fe7 | 0 .../sess_d866f688079ed7f6798f6b2c37d479f9 | 0 .../sess_d9079a969cbbecca3cbeab898a7b3d3a | 0 .../sess_d936dba3104d21be80694fa09c3f2e76 | 0 .../sess_d9507094e57544f8c58778996fe93796 | 0 .../sess_d9de9a32167fd3c3625cc28150d3dc59 | 0 .../sess_da17ec2d008cbcf1fccdade5739c1aa3 | 0 .../sess_da4be22b9206de27363e8a6a0e53ed6f | 0 .../sess_da63331a5ba2e04a7f539fa874066438 | 0 .../sess_da8ca3466e87e6afa9199bbf808a6e99 | 0 .../sess_db1dc99bb1ae9f864d590d8d4951ea24 | 0 .../sess_db3413134a45f8caafce26c4f5bf31c9 | 0 .../sess_db5cdce7ad0e207e29c025a404926a56 | 0 .../sess_db6ae4770e9b9d1af93a1aa4823f8b56 | 0 .../sess_db815a56ecd54cd9683ab4a1e5fe00d7 | 0 .../sess_dbe10898134fde8c830ce6125d63547d | 0 .../sess_dc7ca58b2438ace110816de0b89071cb | 0 .../sess_dcd8063123fb5dc4da53f99e3be8a2f6 | 0 .../sess_dce185e29b874f97b24d0246486f3634 | 0 .../sess_dcf331017d5be4b352f8f7234fbf6e50 | 0 .../sess_dcf395666a298642aa662bc593439f57 | 0 .../sess_dd395de0ebabc53b439f4fc9e32d60fc | 0 .../sess_de195005e2ff63bd109984d154a398a3 | 0 .../sess_de495cd1a69c28af3b9398e92e94d21e | 0 .../sess_dfcf80bc29fce6abfed5ac34bebf7c5c | 0 .../sess_e02cf9db82152079377afac29dee9f7e | 0 .../sess_e0d389d5ada1be1011461856fe6026d7 | 0 .../sess_e0fc214acacaab4e1609c3b68e4a04ee | 0 .../sess_e10dbe6740069aee59b04e33d58750cc | 0 .../sess_e13a7684ee74435983312a287efe4c85 | 0 .../sess_e180f60c178c65bb2e9d312b4530a437 | 0 .../sess_e214cc8e60797ecf4e3cbc8dad77b351 | 0 .../sess_e2463cceb2b637358d505ca930d980e5 | 0 .../sess_e28398cafa24c8e0e56db5aed7749dc7 | 0 .../sess_e2c69c8fb699f8ccdd772818cc7a843b | 0 .../sess_e30bb2069903f00d5b2b403e63af7538 | 0 .../sess_e32f151f178fc3473d264598c4cdb3db | 0 .../sess_e3cd81ee2a416b06478fbb48384a4536 | 1 + .../sess_e3df5ff0de2d27045da872d3673acd8f | 0 .../sess_e454faf4a50341fd4fe7d3d7a4a8043a | 0 .../sess_e4e5ec23b16a1f4fa28b55e9fabb367d | 0 .../sess_e50445bf32d3a170f42828268268b95f | 0 .../sess_e511429ccbb1b5bf87fd2654d2c93dfb | 0 .../sess_e512cfa96becba816ff098acd58d67d9 | 0 .../sess_e595133187ee681f4d193ad6d3d98bf7 | 0 .../sess_e62417d00dfab625e2e867bc95c0e6b3 | 0 .../sess_e7294a484f93c841c720dfddaad2fa75 | 0 .../sess_e78b800b29f053b098e046e43e2b5e32 | 0 .../sess_e79da2fabd48f5e436d0a2ae92858814 | 0 .../sess_e7a0cb5ac086583a7c4f433f5566dfe0 | 0 .../sess_e7bf81464f2a282d2b5c37384544d992 | 0 .../sess_e7e0d10f34c130abe39787681ecc7c54 | 0 .../sess_e87478078e020e24d0779351c0f969b6 | 0 .../sess_e8a7bca6e77dfea8a08a9d85ea373b3e | 0 .../sess_ea261e39262abaa69f59218d58152e24 | 0 .../sess_eaca2607ee393620327eea92f80f61dc | 0 .../sess_eafee1426b1ca0cba1fef01042da24af | 0 .../sess_eb0b3aa7fb842573df159a3cbfbc118c | 0 .../sess_eb1423e9f00c7c286a8de731475317ed | 0 .../sess_eb5dc235fb964cb087b0581dc5470dcb | 0 .../sess_eb632c7b379753ce9e0678cc6e1fc4b7 | 0 .../sess_eba6bb7a7e9f84453ff471f0de9c858a | 0 .../sess_ec5dd9dcf14935e06480e595c5378408 | 0 .../sess_ece749dc3266e76f800a73634c290360 | 0 .../sess_ed3e4c977232325dc987613963520ae2 | 0 .../sess_ed5a34fc673a1877970f68358b1c8a2e | 0 .../sess_ed720542b7344966b8a96736178ecaf8 | 0 .../sess_edae8a6f1230dc09b4cb5a54d21be367 | 0 .../sess_ede9c689e49df248407a15a84fc3ab6e | 0 .../sess_ef7f937ea6a6f62a765a435db96aca63 | 0 .../sess_effb84e824295ad73fbe605ac6fe2f88 | 0 .../sess_f0e668329a30681f9bfbc3cb07c4f5cd | 0 .../sess_f0f092cee32c29d95e9bd7c7c30e20f2 | 0 .../sess_f194bc98e8e4096a6b45e7fb11d139b2 | 0 .../sess_f1adc2c0c56dc66a742546fd7f916722 | 0 .../sess_f1b44223c5587e630f814a8dccd57813 | 0 .../sess_f1b6dc29404814c5b7b518731d3ad515 | 0 .../sess_f1ed9645499bb8f575b5dc55017f6482 | 0 .../sess_f1f8d1c3218c6362dcf0711b6e569ec6 | 0 .../sess_f21a3956f18d80fdec12b29b03404111 | 0 .../sess_f2b140d9118c0885e76b7bb1a5b232d0 | 0 .../sess_f2c30586d756f874aa3952a62be05345 | 0 .../sess_f341b63a04b60a2b5b7df9ad6450e9f2 | 0 .../sess_f349b233d747db9b4973592d72ba103e | 0 .../sess_f36a1f4d2ff411d7442cfccbeff9c92f | 0 .../sess_f4a545daab3a7bbc90c7313ce16cd200 | 0 .../sess_f4a6ba717bff574a5108716d3c1b76e8 | 0 .../sess_f54673683192fda4d1c05bc417825a4c | 0 .../sess_f57e67551a8adca075901267474c5e02 | 0 .../sess_f7434453f5c9fefe400762eea3329b9f | 0 .../sess_f86b00b21a16ae362e3e55e5cbbb49ad | 0 .../sess_f88b647727d942b3ff67893febfa83ec | 0 .../sess_f9047caa910fe889f68f301f00706b3b | 0 .../sess_f9288a644472ee1e11539a71da2c16ac | 0 .../sess_f953c927cd6f250afb7d4bc8791a2506 | 0 .../sess_fa32595e8f8e23ab874e9247eb036ec1 | 0 .../sess_fabbd025bed38d86b6c790c3e2ae219e | 0 .../sess_facf7bf4f85611dce0bc1077747eb283 | 0 .../sess_fbd736474197b68a63d2aa66da52e238 | 0 .../sess_fbf559b7fcb34e39382b7beb9e3a3052 | 0 .../sess_fc283dec0f73891e83fc082f138c358e | 0 .../sess_fd3e050332ff331670681adaa7f3a04a | 0 .../sess_fe0b40307095880975721f2e56e653cb | 0 .../sess_fe92bea57dd460a4bde6b5a3b5b9f844 | 0 .../sess_fedd27c97c990386b6b30a35d8da84dc | 0 .../sess_ff4c8da7fd10a870809494bfdefa4cb9 | 0 .../sess_ffb15114fcaa6705fce00c84bef2acf8 | 0 .../sess_ffd5b0789f866679e1b3167ed8323759 | 0 .../sess_ffddcc106e6d3e46d1caa3c2dfddf4da | 0 .../sess_ffdef08140c48ce0375a0a3d6313da12 | 0 .../sess_ffe009470987847df4966990ac35d661 | 0 .../Http/Controllers/InstructorController.php | 428 ++++++++++++++ .../Controllers/StudentAuthController.php | 167 ++++++ .../Http/Controllers/StudentController.php | 222 +++++++ .../app/Http/Controllers/TenantController.php | 115 ++++ .../Controllers/WorkTimesheetController.php | 128 +++++ .../Repositories/WorkTimesheetRepository.php | 53 ++ www/api.fahrschultermin.de/debug.php | 77 +++ www/api.fahrschultermin.de/public/index.php | 83 ++- www/api.fahrschultermin.de/test_auth.php | 50 ++ .../test_full_dispatch.php | 55 ++ www/api.fahrschultermin.de/test_http_sim.php | 61 ++ .../test_json_input.php | 32 ++ www/api.fahrschultermin.de/test_minimal.php | 25 + www/api.fahrschultermin.de/test_password.php | 41 ++ www/api.fahrschultermin.de/test_request.php | 39 ++ www/debug_test.php | 25 + .../Controllers/AppointmentsController.php | 6 + .../Repositories/AppointmentRepository.php | 10 +- .../AppointmentRequestRepository.php | 104 ++++ .../InstructorAvailabilityRepository.php | 65 +++ .../InstructorBreakRuleRepository.php | 96 ++++ .../InstructorHomeToTenantRepository.php | 56 ++ .../app/Repositories/InstructorRepository.php | 37 ++ .../InstructorTenantRepository.php | 70 +++ .../InstructorTravelTimeRepository.php | 66 +++ .../InstructorVacationRepository.php | 54 ++ .../PrivateAppointmentRepository.php | 56 ++ .../Repositories/StudentTenantRepository.php | 77 +++ .../TenantRegistrationCodeRepository.php | 57 ++ ..._type_allows_student_and_notes_private.sql | 22 + .../016_consolidate_lesson_types.sql | 39 ++ .../migrations/017_student_booking_schema.sql | 202 +++++++ .../public/assets/assets/main-F76uNDS1.js | 4 +- .../public/zeiterfassung.html | 541 ++++++++++++++++++ .../storage/database/app.sqlite | Bin 0 -> 126976 bytes .../database/app.sqlite.bak-20260422203538 | 0 .../app.sqlite.bak-users-20260422204004 | Bin 0 -> 73728 bytes .../sess_0006a50819ddb4c83f8927e073726008 | 0 .../sess_00309f86663d537153fcb66685d86df7 | 0 .../sess_00409744a4c7e3bb92d2eb4076f6f9f1 | 0 .../sess_004433bebe95f3233eb3afeacee557d2 | 0 .../sess_01276da25030fe7610e51db01928f92e | 0 .../sess_013cba357db97c86d9affd6f490425d2 | 0 .../sess_01db5cc5dc2af4df786929887036129e | 0 .../sess_025b3db5cdc9307a49bbc4e7c8569322 | 0 .../sess_02ce241714378b298d820865b9f207fa | 0 .../sess_0302bb8051aea505d498b8118cf07cbc | 0 .../sess_033690960537713e4b7584067e851174 | 0 .../sess_0387d0c9c23e92d7c5da1f59219d140a | 0 .../sess_03ab912d95e7096bd5041a815feaaac6 | 0 .../sess_03bec560a62e6282f3c00ee39e0b5185 | 0 .../sess_03f80785c72e82de6dbb20e633f8e70b | 0 .../sess_04921a0b3787740b88c11ab3b2b4d4dc | 0 .../sess_04ba107751852addceb705f95cb01636 | 0 .../sess_04e7eeca7ab45efc4714e55d0e488aab | 0 .../sess_04ec6601cf72642f743fb0ad41d466b8 | 0 .../sess_0521642416caa2fbc62e3f8298109cf1 | 0 .../sess_052b45081be50758c250257b9a65d04a | 0 .../sess_05fa0356f494ca589061fb1f1cda9f38 | 0 .../sess_05fc405f00dd93a93a4e02d048ecfdef | 0 .../sess_05fe8c42f2d6ce2eb298f3773fe7aa46 | 0 .../sess_06322d63f2b61ed856911309f0491b02 | 0 .../sess_068031028f6af11d6206ba4e176e106f | 0 .../sess_06b2450149fb978337c3b300c5177c89 | 0 .../sess_06d7f7359a7c55810206e5722284344a | 0 .../sess_076f1f9b3f3607d85628e676aa3deeef | 0 .../sess_0781485879098048ec9ec26f838e230b | 0 .../sess_0854ff9e45ba1f5477fbbad5e54698f3 | 0 .../sess_087a67b3949d5d39a90f231048dbc168 | 0 .../sess_09013b1da99e4f816a92e860b645e65b | 1 + .../sess_0979ad1bce9bd29c7f78266fb0db9496 | 0 .../sess_09a34f83873ab7845a54ad0e1f123da2 | 0 .../sess_0a1062c7493039579c5b1ccf819017cf | 0 .../sess_0a3a77bb3b296f6ef7c2c58f223522dc | 0 .../sess_0a457d212ce74a302f1245eaadd68550 | 0 .../sess_0a8407b10f0705fb94abc083ef7629a1 | 0 .../sess_0a918e1bf4f81d888710bb6eabccf5b7 | 0 .../sess_0b02235f6c9d7a8fdd13403950def66a | 0 .../sess_0b4c3e7eeb7915687615efbcb7ad351a | 0 .../sess_0b689f1efcd64909309b788ce0aa77a8 | 0 .../sess_0b7a6dd656b413dfa91158a8f36533fe | 0 .../sess_0bee18c4dceb5b1916bfb6d16f940237 | 1 + .../sess_0c6b2d92ad51dc235c8a326a6f15bbde | 0 .../sess_0cc19484bb603a199b713e1506f53ba0 | 0 .../sess_0cf9ec95094969104196ac2547eb3cc5 | 0 .../sess_0d444063fde0f76a4a254962ecc24900 | 0 .../sess_0d674f078cb072a267fab787102b842c | 0 .../sess_0dca6c782e6359327b87294c5be46f45 | 0 .../sess_0df4eb5e781f33fdf8978650b309cd49 | 0 .../sess_0e04def9d162ce239e6947e6321ae8ee | 0 .../sess_0e42f0bcb89c3342f5fd4f7440391e29 | 0 .../sess_0e5c1480078e1d9b5f84eba3b40bb77e | 0 .../sess_0e60def1012c7eb8dcd4e9c711f82bd1 | 0 .../sess_0e98d5ac2834ebc5bf78d2d5f0c91fea | 0 .../sess_0e9b111cebcfc845371544ee02e6ba44 | 0 .../sess_0f07e83c587cd6ba9d4374bad4a0e050 | 1 + .../sess_0f1d85f511393a35984d6532feb1861d | 0 .../sess_0f20d94dbfd397db961146e58523b752 | 0 .../sess_0f2589aadc148fbb95ddae15307b2ea9 | 0 .../sess_0fb288edf359a988efacf668fabed004 | 0 .../sess_0ff28449277d7fe69998599d5c19d934 | 0 .../sess_10009006bdabf68eb0fab5eb37ca2fe3 | 0 .../sess_110b899ae4b4a0c037123345c4e49d27 | 0 .../sess_117db80a15a6ba699f9678b99a1d9de7 | 0 .../sess_1202777c449219fd2aac7b5ffb07de17 | 0 .../sess_1247d7fb0d48d3e756c4dffbc1270e2a | 0 .../sess_124be71b45653d1b3f57538d487fa105 | 0 .../sess_125bf65956128d9a6750548f818c3348 | 0 .../sess_128a4fda8650abb78800f45754ad48be | 0 .../sess_12a821bed8de2d9944c9f8796684d638 | 0 .../sess_14624d98ed0c363b2a095334c7a924c4 | 0 .../sess_14b57fe7649feaf9706aea2773911e8a | 0 .../sess_1506f280114920a5be86dc1437738d5d | 0 .../sess_15ff36ba1e9ccbb86875e7ffe82f935f | 0 .../sess_165fb060e3ebc3fc704b851ac1d01598 | 0 .../sess_16732beb713293cfc64b4a62d20e5cf0 | 0 .../sess_16ef8072fdc04f0357bc1654ccd9b88b | 0 .../sess_16f69df80eee2cd72267465123374bed | 0 .../sess_176074fcad8595788ae0cdc97b974c18 | 0 .../sess_176897c620d6f055c1dbcb598905cc3b | 0 .../sess_1779168ab91c36713aafe5bf02bcc109 | 0 .../sess_1790c58801f9b2487b8c093cc82be8e9 | 0 .../sess_17a0585e95ccd9c51a580839630dc92b | 0 .../sess_180e9fa06a637da36cb1f76f4d0ccb9a | 0 .../sess_18c8679e062e0a332aca916fe40c6859 | 0 .../sess_18e0ea982f318fbb1005dfcda8a8bdf1 | 0 .../sess_192aef27a1fce8903cf7d28379fa497c | 0 .../sess_192bccfcedba055ff11758985c9485f7 | 0 .../sess_194c7cd0b03e84efe00b5484ca2d8aac | 0 .../sess_1950a2ef1291ec9df468535c6b0fc191 | 0 .../sess_19770a921c71cfc42485c3ac4615611b | 0 .../sess_19c0d2d4a229fd60d2099992ea8690a0 | 1 + .../sess_19fa167361a1d43b543976961054333c | 0 .../sess_1a912668b674f48b5ffc7de9e824aa5e | 0 .../sess_1aee1ec32f6fc52fcde579475a6fdf90 | 0 .../sess_1af06631c6a9c42ffe0961f03c21c73b | 0 .../sess_1b62f79b6a161a44066e889fb5890864 | 0 .../sess_1bc34aea1212c788f139097c26f6a232 | 0 .../sess_1bd8426e10991ec9c1856c793196522a | 0 .../sess_1c19ba48c4d66fb010544f9777b0b4f7 | 0 .../sess_1c1eb9653903a3f2ca0cd68b2e2a31c8 | 0 .../sess_1c1f7b1efd31a1165e99abede59cbc0a | 0 .../sess_1c8fd85f2d4fa6c41130f08f87b3068d | 0 .../sess_1daa8c750fa07697f55057eba9f07208 | 0 .../sess_1dfacd171678bbfd3713e55ee6e1c0b0 | 0 .../sess_1e0fe014daf164ddf58fba633fb433ad | 0 .../sess_1e19408096523f324050696e40e78c2c | 0 .../sess_1e3b112ac09b1917473e8d5d1f050a13 | 0 .../sess_1e3fa8cca3f53f006f75140261d3a5d5 | 0 .../sess_1e4606b18533461a9fcd557a11b1339d | 0 .../sess_1e576ef7adf606c5bdcb7341f2dfc7ee | 0 .../sess_1e607b0b5e8b9b44045b78c038ebd342 | 0 .../sess_1e752925b000debefcf8e1b6f462bfc2 | 0 .../sess_1ec360e5d18fce3b53084f54c50523f6 | 0 .../sess_1f3d92dbc703b68c7e795e5f51c1dc19 | 0 .../sess_1f6c34e49bd50f27424f86a2c7f0b2a2 | 0 .../sess_1fb01a63321004f84edde1c2f01b9660 | 0 .../sess_1fb1ae525d180aa6cb59e558984c2072 | 0 .../sess_2041982bc34bf965174b00a51558b7e0 | 0 .../sess_2043674c95a6ab2d3f71332b76dda4ce | 0 .../sess_204d8a56472208a59a1aa70ab737fe89 | 0 .../sess_2123c53e3d0044bd7f2e5cef59a41837 | 0 .../sess_21b513631be5e9c0dfd1b2d517f84398 | 0 .../sess_21d59f9f57be2f40d95c6857ba79b486 | 0 .../sess_21e0c4efd285609f6d2a1f54f58971ff | 1 + .../sess_220bdce3ff2cfd02d7e5fd4851774fdb | 0 .../sess_2230676d13498deef61f7ed59dcdeca3 | 0 .../sess_2292fab487ebf06df95e0036beac57f1 | 0 .../sess_22a4e3c7055808fd4c5a2963e52ffa36 | 0 .../sess_22af559095a190687e0d544b8eee6644 | 0 .../sess_22d9645a0764cd276e1010062ce58708 | 0 .../sess_22f0a00861d4da216089fde3b14fe2ef | 0 .../sess_23a4409db0925d7a59ad29e42bc4884f | 0 .../sess_24390c9968cd8821cd7bc5f2aca39df9 | 0 .../sess_2456d2263cd6a8256041d4b3c89ff307 | 0 .../sess_2470d1af939f31518959c76496aa7a6d | 0 .../sess_248cfaa4e75cbdb490cfe8dce5aeaedc | 0 .../sess_24a867c9d2c15b255232a9d8e1e84f38 | 0 .../sess_24e2550cf6651e9d8a37e82da464ce5d | 0 .../sess_2503f4a5cbae9324cbf3b95b5b97df27 | 0 .../sess_254c55b9ab44a7e81392a361205f9608 | 0 .../sess_255b248494b7bc41838d5e03d8c63f2a | 0 .../sess_2576dfcf73df11914c84eea9030362ee | 0 .../sess_258ba55b7396886da889d6c57b622370 | 0 .../sess_258d74a6647426ebba4ce0f0a0c67c66 | 0 .../sess_25bc952c7944ac5478e505b7d07617b8 | 0 .../sess_25bfb2e99746cac127855aca7dd51a66 | 0 .../sess_2619421cbb71439a217328646badf18b | 0 .../sess_264744b8536d1f0a4942fd4884126ad1 | 0 .../sess_26475586b26e819cbe7ed6b7c91f4353 | 0 .../sess_26817db6139724550add44986d13955b | 0 .../sess_2694eee41fc1488b7bb8cade34db7095 | 0 .../sess_271efa4ff2d546b1e0a65b6f3ddaf929 | 0 .../sess_272592a07264fcde1b184f03d1b63a3d | 0 .../sess_279218f8e2c26131e4ca39dbe14eb7fe | 0 .../sess_27dd196fe7ad686655daf28dbc6ebd34 | 0 .../sess_28136e39aa3fb81f6ea47823e5cff14e | 0 .../sess_2878c22f67e22d014964d1bc29fc80d2 | 0 .../sess_2941e7e905f15d5da543295c53736cb2 | 0 .../sess_2960b8132f30396e838b0ebdbb96dcaf | 0 .../sess_29a45c491f6998a47aa0b0f66fd83f3a | 0 .../sess_29ccade580d9dad87ac1a35ff275d2d0 | 0 .../sess_2a16d5f4beece112f2e67a4bcb781a07 | 0 .../sess_2a64bf1990c00f2b2bc1836c1b96fd2f | 0 .../sess_2ae5a56ff11d55dbcf879524b71f88c9 | 0 .../sess_2b67b6a92be7fcd363c23a7e494057dc | 0 .../sess_2c5719342be78ab42c03ddf812474077 | 0 .../sess_2ce24f0bd9ec5909e465b42fa8d33670 | 0 .../sess_2d1c750f56256eb9ba589168d9b17b65 | 0 .../sess_2d1cba190840b33e6d54f9befd89ae52 | 1 + .../sess_2eb1f32411558996d87b5d8567dcd94a | 0 .../sess_2f4233260393ed2a31e00ac9e2969636 | 0 .../sess_2f52d9fd84045a93ce4bb3a5be6a31e0 | 0 .../sess_2fb5535ba46f2edcc64f6b7c581b9c7f | 0 .../sess_2fb9f36e4449aa120905dde208f60510 | 0 .../sess_2fe3e2083c13b197a53c094156873f5e | 0 .../sess_309ebde30dd24884b70bd8df07279749 | 0 .../sess_311959d4bc3d190ae2499291e9b42220 | 0 .../sess_31210c92118f237f856a58f569a84141 | 0 .../sess_3160a2a2b74ee599bee5f62ac40e1591 | 0 .../sess_31eaca9ab96ee1d5a814bf3202eb3c22 | 0 .../sess_3211e64538a844841fd3167dd09dbef7 | 0 .../sess_3212a8af9dd17c466ab8ea91052c3cc4 | 0 .../sess_32932fd7d38fd6c8279e60d44e48e922 | 0 .../sess_32c66a45bdf19b9f50de688ea047e696 | 0 .../sess_334e686c0876c391c42fb587b3bfa976 | 0 .../sess_338deba0ad100d079f68ea413a6e3048 | 0 .../sess_345384363d30f764d4244e131c21d36d | 0 .../sess_354431ef347b5ba5cfcc7273061da04e | 0 .../sess_35a3681412ab8e77fc498a7699733368 | 0 .../sess_35abdcd2565ba6cebc1d5323c51976ce | 0 .../sess_36323cbf08bff404511acc133ecddf9b | 0 .../sess_3689a40959fc96cd6f3c50abc1203ff7 | 0 .../sess_37282ed122e7b4eaaff47a3883309651 | 0 .../sess_379e5c9dcce57030269caa935f44b608 | 0 .../sess_382329b5050c26c18ecedab29e115c9e | 0 .../sess_386bd2888cf16751430e80917a8f85fd | 0 .../sess_387a266ab74eed24c6346816390f7769 | 0 .../sess_38f8f88f7abb27ac17d9375a5a4cba31 | 0 .../sess_3bcfeced08df9ceb2af9241e3e4204e5 | 0 .../sess_3c6086415f4fcb8af3e7af3207b96d8f | 0 .../sess_3c6c3f9430ffcd8fda0687736ec2d718 | 0 .../sess_3c7b2b014f20046f58b831fd0ad34c9f | 0 .../sess_3d6993df0f462c4ffff7556ec530e435 | 0 .../sess_3da2ba55a8c5d696a5814b42dc0b2e14 | 0 .../sess_3db0a0533c6ffd6078c55906988a594b | 0 .../sess_3dd3b5c1c41191d551256202b307b743 | 0 .../sess_3df20e7c93a01e37ead25a82abcfa7d8 | 0 .../sess_3e081a2d79c698f00a94dff6db2e962f | 0 .../sess_3e0baa6f9f93aef56b4c28cbe7f0892a | 0 .../sess_3e34570e03450813b46f65a5332d9c1e | 0 .../sess_3eba8aa5bdff2f16073b6b03a879f828 | 0 .../sess_3f4d92dfc22756dc3491753c3710d58d | 0 .../sess_3f5d80074117dcf2345eb24489088752 | 0 .../sess_3f7f9ebea56914012835d097cf04ca92 | 0 .../sess_3fae57c4628e0e6de8700f2aefb0b86a | 0 .../sess_3fde18821c79b139e6cb19e20b72a0d0 | 0 .../sess_407b46f6532a47e17ec5babe6c30bc4f | 0 .../sess_409dcbc73298f3e020b7c60a3432fde7 | 0 .../sess_40f2a96e744926f0865a52dcbac9a160 | 0 .../sess_412a5eb5d0597f918c0bb9bab03b2faf | 0 .../sess_413faf6f188d744d9522dd2fcf41495f | 0 .../sess_415f109c0c4fe4d2a1d4979ad5d9bb24 | 0 .../sess_4181fda8edd2d12b37f109fad33ac6a7 | 0 .../sess_420cdf45b5c2330160d506a70eadb33c | 0 .../sess_4247a0c7906fc90ae18a64adaf1aee2c | 0 .../sess_4254feb9a5b1a5b349f5dc3556d1a201 | 0 .../sess_4338f754256fd9a05ffce8620f13f07b | 0 .../sess_4368d3d721b713fe8f89cfbb6e1005dd | 0 .../sess_44b7c1306ee68cfa7a89fbc4e043209a | 0 .../sess_456a3e7bbad68012698a87d1072c1b38 | 0 .../sess_4587b7a5e1e5ef217166c56763ce423f | 0 .../sess_45a98e9ab472b14179dd14db5f14bbff | 0 .../sess_469e07211c9f0f2764b6d7a2c851ee46 | 0 .../sess_46e2408207b1c95658fb7b626ee6958e | 0 .../sess_46e8ebf399968a345980d5a95e8c06ff | 0 .../sess_47b05bb74c2af2828b06cbf5e3ff8477 | 0 .../sess_47bf2f6a7024254fa8971c7a85b0910d | 0 .../sess_4853a954dcf6e2ad4cfa31535490e110 | 0 .../sess_497ce6e38a9c5c1015e856f7589548ad | 0 .../sess_49a86631804a0d6da2aee719475f739a | 0 .../sess_49c66d8f15ac3c3053c8534b28fd2ef0 | 0 .../sess_4a45962e93cea2ed73e1665936bc73a3 | 0 .../sess_4a6501b0989d99b4dab93189ce30fad6 | 0 .../sess_4a8e405b702f60027e17800b0a9ef45b | 0 .../sess_4c9a1aaceaab78fddc55765178473d79 | 0 .../sess_4caafb58e3487c63db3e767199e558f2 | 0 .../sess_4ce44d9d6463335e836ae3de308d4b34 | 0 .../sess_4cfbbd26846fb01c6f9d339c557b03c8 | 0 .../sess_4d29d6096a285987a2b2e625068ff5ed | 0 .../sess_4d7c6193ca1cb655e8d82dfe9c6c7eb4 | 0 .../sess_4e38e1eb4f49de672fed6ed540681642 | 0 .../sess_4e71c4ae079c79d06b3b4199b37c8d6e | 0 .../sess_4e8ab1cc7be41320be57403a5764d80a | 0 .../sess_4f3604d0593e4bad5b3c1a2ee7fbcbe0 | 0 .../sess_4f758a3b77fb168967f90b28555033d0 | 0 .../sess_4f9fd9991f5f3d30dc0ac97c372b731c | 0 .../sess_4fb189b01ba2f06e00ef5f9b5a7f23da | 0 .../sess_4fbfeca9a5fb8855a221e690ef502b38 | 0 .../sess_4fc591afe53506325ffb61b8bb9c88fb | 0 .../sess_4fc8717e82c041e7287a910c435a73fd | 0 .../sess_509426117c6882aa1596380c7cf47a87 | 0 .../sess_50f11f6d3fd52e9b97404943e55c90cc | 0 .../sess_5104ac2b3efed41e0167e8ac230efad4 | 0 .../sess_510691ebd56ac6658a8f0d05def56656 | 0 .../sess_519f45ae9f86f930f3abdcb63300057f | 0 .../sess_51e0aff602241718c4b8416f452b580b | 0 .../sess_51e0b1db7e62b4fdcdd8306d0c23943d | 0 .../sess_5283908e9b80eb47d1f52f4d4400605d | 0 .../sess_5294cb940dc83bcbd4bf57b17a00ba3f | 0 .../sess_5341c2fcecad979f5311ceac1bd28718 | 0 .../sess_53926f2b0c401a2668cdb0ee55739e71 | 0 .../sess_54e9093941ef282786b14c14e94470eb | 0 .../sess_54f3fb06c0aca2a55c2dd5ec4d25512c | 0 .../sess_55398030f9196b2640c7bad95056c2a5 | 0 .../sess_55b079046d294c1c69f287fd4337d04b | 0 .../sess_55e94cdd1ed910c96f22fc4e1f73c0bd | 1 + .../sess_56355cbfc04402cf36d92f821bae5090 | 0 .../sess_564322bb4ff156306233922d7d4fb345 | 0 .../sess_574d1c7a48adf43155170876b326b4aa | 0 .../sess_57744595846324be63e78c3aa28a4f33 | 0 .../sess_5775e29dec1dea074f4fc9c32fa3a34b | 0 .../sess_57e47e62da11c1399a779afd1e78b75e | 0 .../sess_583371d8396ad02e05cf1ed99be48719 | 0 .../sess_587bf41af67b4725cf167a5652622640 | 0 .../sess_5971640cd75544c110ed69c8b2704b38 | 0 .../sess_59d0ba86b7085a23b070b559be815e6d | 0 .../sess_59d76f244bb8bfebfc4b923adbc15419 | 0 .../sess_59df6b8eff37a8f46f806ef70d53862d | 0 .../sess_5a4ae2edb0253f99e7e0d768060c5e83 | 0 .../sess_5afc92d91483488a5e2756611359c02a | 0 .../sess_5b9ebb48cacc40324a7627ed5cd03ea3 | 0 .../sess_5baa5d3d23563e43afa7446486dd72f8 | 0 .../sess_5c0025a4a3bc7edc09d828efe2b28cac | 0 .../sess_5c0f0b21134060b451e12ef30081f540 | 0 .../sess_5c2d18b62ab5f4e62bf93bf01d6a8abc | 0 .../sess_5c49211f43aa46d12999628ab1a4bd28 | 0 .../sess_5c572f6972ef0bbc0fdb158726375a0f | 0 .../sess_5d5cd460d1d5835451d781e05c3adf89 | 0 .../sess_5da65e8529c7df8023ed18faa35d5a9d | 0 .../sess_5e286521edc4303f85116330fe10e8a7 | 0 .../sess_5e7a74bf5069e74d295e89a325872261 | 0 .../sess_5eaa995db9fb15a416a357e533b6b131 | 0 .../sess_5ecbe991d413b362c14567f1f073f4cd | 0 .../sess_60668ea8e3890507478e420a56ddbd5f | 0 .../sess_6143815503ebf3f82c544d21c1b7ad5b | 0 .../sess_61b414feb765617dd3fb38f7eceffcfe | 0 .../sess_61d6228ae940365ff8bbd93a87303a02 | 0 .../sess_61e9d1453030accdf287a54e918ee927 | 0 .../sess_61f558ea9155fbeba01a42a2931d7b7f | 0 .../sess_621511f8d7c30ebc5aa01ce455b8ebaf | 0 .../sess_621ef44ae59c047ff712c18d050011e5 | 0 .../sess_6299dfcc8ce40cafed3a335a341a6336 | 0 .../sess_629f9dd62d1204aba62dfefe65fb0d74 | 0 .../sess_62b5d304a31805cce616357f0e59a9f2 | 0 .../sess_63a233987c509e08fae79917349f9bb3 | 0 .../sess_63e9364f0c944608a17504f9b91784b3 | 0 .../sess_63e9c9819a2de781d792f1d2a60851ba | 0 .../sess_6477fc14a025865e68f2dea333bebea8 | 0 .../sess_64ff70ad1229278f497281f0214ccef0 | 0 .../sess_65468453a39a8a4dfaa1082215ca9836 | 0 .../sess_65b339d0b1ab7066ca6b44ecd8facc40 | 0 .../sess_666d42c52b9ccca9698401d63202cbbf | 0 .../sess_66f307a1423bb488cc26a24cc5c4a42a | 0 .../sess_6719a214f44edb74418e6e890bf5ac08 | 0 .../sess_676be8f36a7e54aa1863917c871eb567 | 0 .../sess_6779e0e89629bc58388284b76bd76192 | 0 .../sess_67c0a94fd2af88c6fe4e5fcd34036c36 | 0 .../sess_67d11c2d90f54a43e96d16bf3c7c45ce | 0 .../sess_685d40735c321a9ffb35d557798cb1b5 | 0 .../sess_686f0c41b1eaaa6c33131bff43058b77 | 0 .../sess_68be2f0cedefb60f859f4c4d9c2a880f | 0 .../sess_6908fafa1345473eb21f5cb9dcb153e6 | 0 .../sess_692cbc36b4318bfe850fc4aede3832db | 0 .../sess_6972dfd4b41c386eeb36cb9140582224 | 0 .../sess_69c6be375919a05a7948264c4db4e9dc | 0 .../sess_69dea144c3340502072b895a59b41b24 | 1 + .../sess_6a4b12a9d81b4f56fdab7814f88b59bd | 0 .../sess_6adef316d6f11d1cfcb4765ee289aa95 | 0 .../sess_6b3dc5917d807ae794a1ed28fc9e99f5 | 0 .../sess_6b45e848464ad9143282b3909897ee8b | 0 .../sess_6b8aea5fa4705e2c8928d1ec20387164 | 0 .../sess_6bf7d9cc4cb88d9fe96dafc8ed1288a9 | 0 .../sess_6c0ab8e461aebf50d0ed2a344176ff8d | 0 .../sess_6c601c0358a1d9724a5252cd756bd64b | 0 .../sess_6c676b2e4c18eff505c1853258b039cc | 0 .../sess_6d894c43c030d8dbe071de186700c5c0 | 0 .../sess_6da8ac394f5fd3b79b6eb9681f810c15 | 0 .../sess_6e5401ff80e23d4dbf547c4a894fc211 | 0 .../sess_6f3a5c611d7a16c43ca25127894ed8f0 | 0 .../sess_6f84b5c8b742313a8b748867bb5dfbc4 | 0 .../sess_6f98287cfdd3ff93e952baba05fd5c31 | 0 .../sess_6fd95d60ca7ff94f3c602ff4753e6a87 | 0 .../sess_700631b2fa80717f2d07a6017d5ff030 | 0 .../sess_700aaf47edb8ff558be85c5bd9ff529e | 0 .../sess_70a66109b64b26d0ac67524166cb72cd | 0 .../sess_70c642898f8f0725b81e4d285eec0787 | 0 .../sess_70ddcec0c3417113fc4c072fa39fe331 | 0 .../sess_70f9c044b76efda9274b32b5ae345d56 | 0 .../sess_7108ed71a1a0054c7ab7c7c2068e8923 | 0 .../sess_71187da2021a77805c96d85b76867d92 | 0 .../sess_712f6993fea7e24e5126734f0aca9a68 | 0 .../sess_71a643c2efaef2d77620f637e5637cf2 | 0 .../sess_71d766001d452c1d949ce85cd327574c | 0 .../sess_720f14d25a4f2ce52fe9d3933e014771 | 0 .../sess_72515b3f3d38bd85e703ef3a80b1bfa9 | 0 .../sess_726860093a0a7908e0e9f66e709ef6b5 | 0 .../sess_72ab8f6fd0fecbe3e0976c623333be7b | 0 .../sess_72d5d6b9df6a55503533e97e340d2311 | 0 .../sess_72d7576d9bfaea5245d54bc6f5ed732a | 0 .../sess_72f0fc1c0ead3468d75f171ff2353eac | 0 .../sess_72f8a1f9caa69b25fa64c6185238c6e7 | 0 .../sess_7316664ff3b366d41d1a0f1d29a21e44 | 0 .../sess_7327bbf8e70879c93cb9fcf18287ef57 | 0 .../sess_73372d764d0b64494a123b95b092e293 | 0 .../sess_73c80e71dcc72c4c3bd422dc2d1c6f22 | 0 .../sess_73fd9c9fd27ea5b4b7e513723fbe0d01 | 1 + .../sess_7401b0da36a0bf586a412bdc32c01932 | 0 .../sess_74169a80690859946b251a7e2789c06f | 0 .../sess_7433d5d741f0c55568cb183264a757d7 | 0 .../sess_755543053755f9827042ecd578a32ce3 | 0 .../sess_757ed514e8bcf21fe484abe2639fd9c8 | 0 .../sess_75ed4967153522837eebaba2104a96bf | 0 .../sess_76036580c2a0124ef45fab001e1b8e85 | 0 .../sess_764eb3d09d7acdadf02a1b1b9e652d38 | 0 .../sess_7751d4611abdb82cdd8307b7db2a94a7 | 0 .../sess_777222d786447c44cba1f61c40722851 | 0 .../sess_78ade15b28ae2ac4544641f92908c944 | 0 .../sess_790eb3970d7871971226a752514c9021 | 0 .../sess_7a39b110e92a44f5cf2cc27318fa8acf | 0 .../sess_7a49c9790485b15f032f1889edfb5e74 | 0 .../sess_7a758afdfa5c19238907ee69e4f1b8e1 | 0 .../sess_7a942e09063d7d053ece5084917a59c6 | 0 .../sess_7adb1835b69eb442280acb1a34e3e4fc | 0 .../sess_7b1b6e128d0bc1b40a30954812f3f2bc | 0 .../sess_7b30a67240cb34b565faab1ee1a43759 | 0 .../sess_7b3b609d4c8713d06961731c28fdfd8e | 0 .../sess_7bb08b912f4e650b85412aefe4225981 | 0 .../sess_7bb50e18de85135463e20f8d52dbd679 | 0 .../sess_7c34f05d08306cd0e725b262006af9c3 | 0 .../sess_7c4815b8c0d9564d5c330dc755b89be4 | 0 .../sess_7c5d6ce984c7f26e83b67c6a785f2919 | 0 .../sess_7cee3f52567d4d995de6c242576e80e6 | 1 + .../sess_7d496df6ac57de8fe4c46d48271fd6b6 | 0 .../sess_7e2cde5aa2397f9c928af07c857d9059 | 0 .../sess_7e394c0ae018145fbf007d66dae56fc1 | 0 .../sess_7e44e9b5e4618ac9c5d5c50156aa9481 | 0 .../sess_7eb154203930535a374d572b26905543 | 0 .../sess_7ebd209f80c9a78715a398ca29339183 | 0 .../sess_7ecdf19414bb4c4fea4ca19850a7a539 | 0 .../sess_7eef0ffbe7eb689711c83038742d15c7 | 0 .../sess_7f11e5dde38e860a69e208aaa622312b | 0 .../sess_7f97b81d9a078302523e924f6cd12861 | 0 .../sess_7faaa1325462a16eb0c09a78eb816c3c | 0 .../sess_7fab50507d10a6d85e79235af15d32d2 | 0 .../sess_802f12cc03180b848c62d75314cea4ad | 0 .../sess_80ddf16c4abf6269af3663c2773029e3 | 0 .../sess_81367cb6c7667b5ce00936f7e4c75ed5 | 0 .../sess_821cee680ccebd68e213681a79f95074 | 0 .../sess_82c9e9a8ca48d231b1a6143016f12161 | 0 .../sess_8390ca0087f926e6dc6acfc1726feac3 | 0 .../sess_83f1a287583b2bc46cd195e1e3b2f943 | 0 .../sess_84323f8c4143d694e44c9fedcb9f9df8 | 0 .../sess_8506d9ad640e8db4c5464ae18b21aeaa | 0 .../sess_854a85d95cf30e598d3dd8071c524182 | 0 .../sess_8556739092f403601805097e7c5515d7 | 0 .../sess_8578f708494846a3ded7c61783b1424d | 0 .../sess_85bde53b19790499e9dd46a1728f2644 | 0 .../sess_8609c314bca4daf7b8d418593296f29d | 1 + .../sess_8631135c2359b355b28b12b251d7cf20 | 0 .../sess_869e70a50cdab0e5dfa7f41decbe164e | 0 .../sess_86b6cee8d0e002bd2cde78c01432c98b | 0 .../sess_87765df5421b4b08ce0285280372e9bd | 0 .../sess_889d219494675742314ef1f2307e5059 | 0 .../sess_88af4a1e93188192b41b898c96537666 | 0 .../sess_88b7f3ed2893c08c3d3a90674de9828f | 0 .../sess_88e7499f47401ce614b4b65ac8356131 | 0 .../sess_891ef4846f2cdff9342aeba0a22d5f98 | 1 + .../sess_892e5cf319f4e18afdb304abb39c54e4 | 0 .../sess_89625043f67d25fa8715915de6bb79c5 | 0 .../sess_89a3b9f54d67342368079a0ac616276e | 0 .../sess_89d0ffe96d3b0ad881121413908b1b83 | 0 .../sess_8a6817782ff0d92875e6235d8ad776fc | 0 .../sess_8aa40af232be8a3ca5282ef5f551afde | 0 .../sess_8ad98e7026500f40515eae94a2fde2ea | 0 .../sess_8b186147e95653d8a3b6c748d2482dcc | 0 .../sess_8b63e5d2df259415a70db075ccaee244 | 0 .../sess_8b8a7cdf7636a6d2aedba08f756d3c8d | 0 .../sess_8b99977c6b9caff09ba87062b2bccadb | 0 .../sess_8c02e46467ce79c739e0a68444178b2b | 0 .../sess_8c6134b631065799b0ec7bee4049c4ab | 0 .../sess_8c7302f6a643e4dee153b7d114ed8783 | 0 .../sess_8c909900d173078a178b390295c8d241 | 0 .../sess_8d2904d8920390de86ee53bd52ff2f95 | 1 + .../sess_8d3b9af6665ae182d39cfecb43e8c67c | 0 .../sess_8d6b378636ae0e195107452f07a9a1b6 | 0 .../sess_8e2e34092cc5092b33f80789bd8cb5dd | 0 .../sess_8eb44802c4c08e84235edd0bf26ac87d | 0 .../sess_8ef6f0dee24d8b4ca5d6d12fc78846d3 | 0 .../sess_8f45c8b37f65f5a3d96d99bbc42afd6a | 0 .../sess_8f59107da2dd4018eaaf396b9d4a5a36 | 0 .../sess_8f7dbcbed4677531f01a98f09b78e579 | 0 .../sess_8fe1ec9a1ace4c1e42a2f556662301f2 | 0 .../sess_9064761135752f81f7ad8b6ebd661023 | 0 .../sess_907c362f4f2f6e400df8ac189f9f1eb1 | 0 .../sess_9087759ac2d7809daa14034e92904b12 | 0 .../sess_90b94e25c780902f5f539c288bfcf537 | 0 .../sess_90ede0d038bcbb6aceea8fbde52f2641 | 0 .../sess_90fe76e7383355fe08eb307219e2d816 | 1 + .../sess_9198bcfeff6d315cf4fafa6370065a03 | 0 .../sess_92c58ef6f3c99152d2ba186841a33c7b | 0 .../sess_92efa0c840cc52c16596b78a5df32412 | 0 .../sess_93f7f0d85f2643a76ef136e26da9e002 | 0 .../sess_945189d26d0ba3469d510a86ca265d85 | 0 .../sess_947481d841466c3a8daac3ad85aa115a | 0 .../sess_947988622ee7c2525e6cd5d894c98ef1 | 0 .../sess_948b86a8e2a1e31167679e4a1470c860 | 0 .../sess_94e7ec5946942ead16448a728abcdc37 | 0 .../sess_953a407fb72605ba0a9a187e23cfe770 | 0 .../sess_959d4c4a242dcfeb97c61a3ea2412504 | 0 .../sess_95f18066efe5fc4f9cc8d0593c8439bd | 0 .../sess_9644fd126a3afc481ee73045a503baa4 | 0 .../sess_967b281af59e4a6dcf1cc1fbc3265a9f | 0 .../sess_975c0b932ae0b2f7e84907a50b8ef1b1 | 0 .../sess_97f50b3d8687d8d47bd563960e1ff455 | 0 .../sess_98892de18bd4ec4aebcc5bee99ab94ac | 0 .../sess_98bf4125aefc7ddc22a20c48a9619989 | 0 .../sess_98d9a575de9d3f13469f22cccc63825b | 0 .../sess_98ea1635d552d9a6892dd0ef71435f1d | 0 .../sess_9959bb4c68666277b4b2c0282a90487f | 0 .../sess_995dec3fc6c2f46d050ebb554f56cd66 | 0 .../sess_99ad47b9cdcc23e23f1e7795dab1738a | 0 .../sess_9a0e601461b3d9b104215919d000998b | 0 .../sess_9a7db4f72bf6ba3b89119eb1577e6915 | 0 .../sess_9a81f6a69cf9a752f08a42af7c04dd7e | 0 .../sess_9b0faa79ebcc182ad631d2427d6ccc41 | 0 .../sess_9b9ffd3f5d1b3b7e7fb4ab8021ee6724 | 0 .../sess_9c87061c647a1863348a102fda0a485b | 0 .../sess_9c98903028ece4cf33487e12338563b0 | 0 .../sess_9cc05cc46ce95864ad3e87c922bd7d6e | 0 .../sess_9cc7d48622827c73b5c2800b90da6c71 | 0 .../sess_9cfbca439e6352c64927f83dfba56d33 | 0 .../sess_9d0f8c2d99f91d53540c981a669307b0 | 0 .../sess_9d585af32abcbce5c19f7dc88013b509 | 0 .../sess_9d95d6e5b127e827982ebd70a7780ac5 | 0 .../sess_9defc0b6e6f8e6a06175fd732c55f9f4 | 0 .../sess_9e0ac6eb04ae0415245380aff8a7fe18 | 0 .../sess_9e48c6c6d01f1fd30c8ebe200c54ae74 | 0 .../sess_9ea742913c11d08c3a459e7cbdc48ea7 | 0 .../sess_9eaed2cf9bc04a09871029820a0febdf | 0 .../sess_9f24d1e11f4da71f33ba80b598bf4635 | 0 .../sess_9f669749877accc7939a55a65470df73 | 0 .../sess_9fd8760c7cb04dc202cb2fcbd65fa171 | 0 .../sess_9fddcfde761802254047b04993e2653a | 0 .../sess_9fee4660d995f9fd6a5586af67f086ea | 0 .../sess_9ff67287131509f45cc8628249181573 | 0 .../sess_a04f945480a6c46840307cfd5cb133c7 | 0 .../sess_a080edfb79e15193b1a986d2bd1c8fa4 | 0 .../sess_a0cf773a2425baa5763a3c5151c1e6a3 | 0 .../sess_a0ece2f1244996e6289cf22fbe69523b | 0 .../sess_a14487d530470e659ef4624121d3c2c1 | 0 .../sess_a267492beef8994b2bea7a841d1a0cd8 | 0 .../sess_a285d45c3472af1ffc36a684c856ec99 | 0 .../sess_a35bdd52888e241d85684a6d02a68bde | 0 .../sess_a3a3b332d752eebbfaa33f846f2011e8 | 0 .../sess_a3d22e835cba5db3a924784879c04be4 | 0 .../sess_a407d22c03234511a8692574b1afdb55 | 0 .../sess_a40a10b60dfb97885a13a60cd56ab03d | 0 .../sess_a41afc440e071af84d3aa05ac5272fc3 | 0 .../sess_a4599efbaa95f71a3a07ef11cbdb2982 | 0 .../sess_a50197cb3ff817a6e011f450bfc33d48 | 0 .../sess_a50e65f017e7c264acc21fba0639a32a | 0 .../sess_a559140c7253e91fcd6a8f6f19e05bff | 0 .../sess_a594961a601d02783f91faf40e98312c | 0 .../sess_a5e3f86f5e484f1b76040bba1e5e14a8 | 0 .../sess_a6ad3eb821ff648a929de3ed3197bcb2 | 0 .../sess_a7cb6058536c2c10eb132b9ea2273034 | 0 .../sess_a7fa2f402ec1a091b24e4dbe172fb7ce | 0 .../sess_a80186e2242223f495ecfdcca4153e08 | 0 .../sess_a81980865d0ec43ef0c5583e07965859 | 0 .../sess_a87aa73edef2fa7a6931806265cea0c9 | 0 .../sess_a894ac582bee396f28b4a5b6d76632d1 | 0 .../sess_a8eed80b4e6c374bce30583a334fd1be | 0 .../sess_a8f1dd35649e9f13895d193df5dcb5ba | 0 .../sess_a9196e31df6affc38cd0927354660e20 | 0 .../sess_a95b3114ad98063dd3ba2e3ff6ee4089 | 0 .../sess_a9641cbe82cd2fba425d8bf3f1fa632b | 0 .../sess_a9dc429cc9b5c625c3b61d1647124d00 | 0 .../sess_aa0e7054e4831c8e95c9d968a25ca3c4 | 0 .../sess_aa6859d5e79333e0b7fbf8152c956007 | 0 .../sess_aa8f8b163bab75cba3311559cd7cc687 | 0 .../sess_ab4e2933297bec79168036dc7f3d1c4c | 0 .../sess_ab893deedb9409a65c6b4c2e1fd21135 | 0 .../sess_ab89791d24d1aaadefc456a1086a2560 | 0 .../sess_abb03b95526f02aafe816672af681ebc | 0 .../sess_abb7a002d65efea430cb0af30b78a078 | 0 .../sess_ac0e42e33c97dd59d436c667a5b3eead | 0 .../sess_ac4027a844896a8a22c308c293a15f65 | 0 .../sess_acab22928f8fdc15eb012b3494e41797 | 0 .../sess_ad5c7d07120b55ddac12237c4bab0e2f | 0 .../sess_ada72e41e49e00d5f656e5da43eb6bc6 | 0 .../sess_ada8c2049e1c9327b3a50dc425f6bfbe | 0 .../sess_ae1d8a22efd12912343d561876fdfdc5 | 0 .../sess_ae234126326f18ee35485c944b7e5848 | 0 .../sess_ae34a72b4fa6668803ea46d383ea6f24 | 0 .../sess_ae52f8b68722f536aa70b7590035f9e3 | 0 .../sess_ae64540044c09c7b795ced78505c9b4d | 0 .../sess_ae70ef607969820ee6fa532c7c516c0b | 0 .../sess_aeafd646edb77412e928721505f984c7 | 0 .../sess_aeb687357e6f692b2199e9e800d56673 | 0 .../sess_aefb95455125a664bb95a1c2ef06f8ad | 0 .../sess_af4792ba9e683db497c8912f32609da9 | 0 .../sess_afab05d39b52ab8998daa2eda66b6aee | 0 .../sess_afeb67ef6a5ce209e756766ec3ffe2ce | 0 .../sess_b024fc7d8e3356fcadfeeeb2d14b1815 | 0 .../sess_b0c1002f2db893b0a6af43e0b577dfe3 | 0 .../sess_b11cbd54af2f7c7e43bcc82b0279e10b | 0 .../sess_b17c2592ffc464b4ba0b679af70ded2b | 0 .../sess_b1929d13ac18473aeea275a695808d5c | 0 .../sess_b1da207d6c31fc9946ba3621f021ae80 | 0 .../sess_b237e22685289229a1db2608891cd30c | 0 .../sess_b2a31fa222e71d201828baf1ecf324d2 | 0 .../sess_b2eaddc565cbb2b2e87616e79caeeffa | 0 .../sess_b359782f5ccdab7e806fbeddf150dcbc | 0 .../sess_b36f9ea866728a5fd33680170aa21c86 | 0 .../sess_b3a6ab8bfc108f99d05e83799ba0c606 | 0 .../sess_b3b8e7ee9034bc9159f2b56875fd7cc8 | 0 .../sess_b41c95ac09de774cf44fb1e10f5e904d | 0 .../sess_b44275f8a8435fbf5025d135126479b1 | 0 .../sess_b491790afd14563692ea36be5588eabb | 0 .../sess_b49f8d76aa16a1f4aa34d99f4cc1e1b4 | 0 .../sess_b4dbf3766611612508d08f46c9b190f2 | 1 + .../sess_b4ea676cb3216a36a1693463ef5b7c82 | 0 .../sess_b529a32f36544fee5e632be00fac097c | 0 .../sess_b53d30d81ffaadae86b647f47f6a1160 | 0 .../sess_b53d83093d2c2083ed7132b123982264 | 0 .../sess_b5510f652a52854c7651f6918feb977e | 0 .../sess_b57a0d89a948fe34f8dacda9cb43bae4 | 0 .../sess_b58721ac9be6edd4764bf6f8780cefc1 | 0 .../sess_b5a60f9daa55f9734f493d6ac0458573 | 0 .../sess_b6ec78ab609d0df2fd508e9163c253e7 | 0 .../sess_b6f50ca4efe83c023d25e4ebaf6487db | 0 .../sess_b79d07314aef27e7f198bbb8b140a36a | 0 .../sess_b820236352b2730de43cf836422e206e | 0 .../sess_b84c49ccaa5176f3f60d430f254f4009 | 0 .../sess_b908569ab6c58bd1746f5f4fd534f934 | 0 .../sess_b91a01b1e5d207e45e16934c8ac97ee8 | 0 .../sess_b9c58c1055898aec49f2b876bee0989a | 0 .../sess_bac0d94744e5d5ef89296100c18f0090 | 0 .../sess_bac2cfc9597d42d210a3862582579fa8 | 0 .../sess_bb4a64bd891cc061c3adefcb44477af4 | 0 .../sess_bb87f20b1e8a384d46d56453cad1e43c | 0 .../sess_bc2eeb565a25074bbb1662c9574c76e6 | 0 .../sess_bc471f7dacb30aaac0280f97b8b2a40a | 0 .../sess_bc6a3acc6bd9424ccd4f53381763a765 | 0 .../sess_bc9a6fcb99473c1d425a69fc74e2816b | 0 .../sess_bcba56fadcb6d5fa33aecbc0accdf024 | 0 .../sess_bd4a221108dfce794cd7f70d523dd168 | 0 .../sess_bdb7dda4a0ef116d409e5e69c1ac3fad | 0 .../sess_bdec0ef240e9a5bb1a666d8984758976 | 0 .../sess_be317991eb08458fb8e9abcf374bed36 | 0 .../sess_bf428a4caf8a7d4c47d26f32e16fe043 | 0 .../sess_bfe6ee4113b5571017455a38ee1e2317 | 0 .../sess_c018a7218b523ecc2ade22f7aedd38f1 | 0 .../sess_c064150f27b1dab4111a60ccc6f9d2fa | 0 .../sess_c0ed531b0f4e24c41d874a91cba8c32e | 0 .../sess_c1f34ca67059e96e6bcd732eea2446bf | 0 .../sess_c1fe8bbe901ba4706855bd9eb723ba70 | 0 .../sess_c21b23debb7c016fc702f87a0294f3e8 | 1 + .../sess_c2426b9d31ca734eabd4e282665bcb9d | 1 + .../sess_c26296dc097c2f01694ee80ae77eb6e9 | 0 .../sess_c2ee1217613c2a80e949b8716990ca88 | 0 .../sess_c38c51006dab56733498c50180b3fabb | 0 .../sess_c3a730d7b6a59d913d50e31df3e4c3a5 | 0 .../sess_c40e44707ce35c5ef27e8802aa39e47f | 0 .../sess_c441f7bf34ccd6ae14805365c2ab89c2 | 0 .../sess_c45b5e61c6d053c4f763d91407f3c91a | 0 .../sess_c475fb1bab33463d842d886993c1571f | 0 .../sess_c47945969b61a07537f19f946a384741 | 0 .../sess_c48155c30b39c8362f86aef75c221163 | 0 .../sess_c542af71129f343b6408eade9270bd54 | 0 .../sess_c54748085a5be98912b747457ee83e87 | 1 + .../sess_c54891068be346717c001cd9135f2f1a | 0 .../sess_c57f6f9decec3156c65ae42ec51d39d2 | 0 .../sess_c621bb0f553660ec429aec2ca0f43023 | 0 .../sess_c6429f27fce95541dab9e8ed4952d4bf | 0 .../sess_c6bf258aae59585a0e4d3baa156de525 | 0 .../sess_c6cc348dc8a6b160339d4efeb4ad5a73 | 0 .../sess_c70b845bd7ace83384142056d6460d23 | 0 .../sess_c721d5b6b93058bc391ed27a94c9b40f | 0 .../sess_c75f86e937db953edf0dc710594a1a34 | 0 .../sess_c78586d22fded1470a8af498186b8e7a | 0 .../sess_c845a8b285996a7f190591807a2e5da3 | 0 .../sess_c84f8233f589f47a1d1f3c95256a6d54 | 0 .../sess_c8c9fc84e7bd6bb28854c759734a4aaf | 0 .../sess_ca26d03afac9fb59c49d2459b766b0e2 | 0 .../sess_cb10ef5334885e0f4bb07e60b95e4bea | 0 .../sess_cb810d18223327abb72bbbce488255a2 | 0 .../sess_cc6ca9b9be103498b3b3f9bebbfcca8e | 0 .../sess_cc7cc7d4d0e038443bc7f215fcf60584 | 0 .../sess_ccbfa7c487f33d217038668dba1c7f98 | 0 .../sess_cd426fff8fdfa8c52522fb101ed1c200 | 0 .../sess_cd6936d236e56a0958f326724caa9f66 | 0 .../sess_cd9a052a6c053712db213bcbd569b448 | 0 .../sess_cdb2d2c089e55ec49e126ffaee9da500 | 0 .../sess_cde83bbf5f7883231a8bcefdf4b5749f | 0 .../sess_cdf9116287630763b1acbe585ed5e0a2 | 0 .../sess_ce5934a087e64591d7f2d218c00ea365 | 0 .../sess_ce8a8426d076db8a3d564204d4e29b24 | 0 .../sess_cea4f34bc0b31c76efbc1ff1a6dd88bb | 0 .../sess_cf7075fc4f5727c449d2b7fa06303609 | 0 .../sess_d0054e42522dc3082a38d55702832585 | 0 .../sess_d053186c866c8aaafb3c49f5fed373fb | 0 .../sess_d082df7384cf4c3855e911af2e4a6d68 | 0 .../sess_d1b6c144193e51c481f30adaa6f5ddb3 | 0 .../sess_d1d621d535d1444562ca2d1b7a43400a | 0 .../sess_d256ce4f2ded89a00bcf19cbcb1bed64 | 0 .../sess_d27d2227ad5637ccbac3579310e60b6d | 0 .../sess_d311a3d90755123ef2e6e86c68eefcf3 | 0 .../sess_d35e6e95a5fdda69f32a5a8bdf8964ac | 0 .../sess_d36cf7d1753dc1ef650e4ad031194f3b | 0 .../sess_d37c9bba5a42b3760ac4fedfe02002ce | 0 .../sess_d438212c8192544d26843d87bfda1e5b | 0 .../sess_d44df14d2838d90e63d1c5ffeee18b63 | 0 .../sess_d48edd5255a24ae738e3a13c31f7bdb2 | 0 .../sess_d51b539ad255419ea6fd83ba3e9caa39 | 0 .../sess_d5ee3033b6819cfd67d2ef4d8feadeae | 0 .../sess_d6173d6126d730e93fa9cd6774f53be7 | 0 .../sess_d655d61f4acc04d61814576220dda1a4 | 0 .../sess_d761d9849d639d0449a853e9965d2558 | 0 .../sess_d764f7b5069d52949a1249384c2bff40 | 0 .../sess_d82cdb26dd412905a1b72ed027a14fe7 | 0 .../sess_d866f688079ed7f6798f6b2c37d479f9 | 0 .../sess_d9079a969cbbecca3cbeab898a7b3d3a | 0 .../sess_d936dba3104d21be80694fa09c3f2e76 | 0 .../sess_d9507094e57544f8c58778996fe93796 | 0 .../sess_d9de9a32167fd3c3625cc28150d3dc59 | 0 .../sess_da17ec2d008cbcf1fccdade5739c1aa3 | 0 .../sess_da4be22b9206de27363e8a6a0e53ed6f | 0 .../sess_da63331a5ba2e04a7f539fa874066438 | 0 .../sess_da8ca3466e87e6afa9199bbf808a6e99 | 0 .../sess_db1dc99bb1ae9f864d590d8d4951ea24 | 0 .../sess_db3413134a45f8caafce26c4f5bf31c9 | 0 .../sess_db5cdce7ad0e207e29c025a404926a56 | 0 .../sess_db6ae4770e9b9d1af93a1aa4823f8b56 | 0 .../sess_db815a56ecd54cd9683ab4a1e5fe00d7 | 0 .../sess_dbe10898134fde8c830ce6125d63547d | 0 .../sess_dc7ca58b2438ace110816de0b89071cb | 0 .../sess_dcd8063123fb5dc4da53f99e3be8a2f6 | 0 .../sess_dce185e29b874f97b24d0246486f3634 | 0 .../sess_dcf331017d5be4b352f8f7234fbf6e50 | 0 .../sess_dcf395666a298642aa662bc593439f57 | 0 .../sess_dd395de0ebabc53b439f4fc9e32d60fc | 0 .../sess_de195005e2ff63bd109984d154a398a3 | 0 .../sess_de495cd1a69c28af3b9398e92e94d21e | 0 .../sess_dfcf80bc29fce6abfed5ac34bebf7c5c | 0 .../sess_e02cf9db82152079377afac29dee9f7e | 0 .../sess_e0d389d5ada1be1011461856fe6026d7 | 0 .../sess_e0fc214acacaab4e1609c3b68e4a04ee | 0 .../sess_e10dbe6740069aee59b04e33d58750cc | 0 .../sess_e13a7684ee74435983312a287efe4c85 | 0 .../sess_e180f60c178c65bb2e9d312b4530a437 | 0 .../sess_e214cc8e60797ecf4e3cbc8dad77b351 | 0 .../sess_e2463cceb2b637358d505ca930d980e5 | 0 .../sess_e28398cafa24c8e0e56db5aed7749dc7 | 0 .../sess_e2c69c8fb699f8ccdd772818cc7a843b | 0 .../sess_e30bb2069903f00d5b2b403e63af7538 | 0 .../sess_e32f151f178fc3473d264598c4cdb3db | 0 .../sess_e3cd81ee2a416b06478fbb48384a4536 | 1 + .../sess_e3df5ff0de2d27045da872d3673acd8f | 0 .../sess_e454faf4a50341fd4fe7d3d7a4a8043a | 0 .../sess_e4e5ec23b16a1f4fa28b55e9fabb367d | 0 .../sess_e50445bf32d3a170f42828268268b95f | 0 .../sess_e511429ccbb1b5bf87fd2654d2c93dfb | 0 .../sess_e512cfa96becba816ff098acd58d67d9 | 0 .../sess_e595133187ee681f4d193ad6d3d98bf7 | 0 .../sess_e62417d00dfab625e2e867bc95c0e6b3 | 0 .../sess_e7294a484f93c841c720dfddaad2fa75 | 0 .../sess_e78b800b29f053b098e046e43e2b5e32 | 0 .../sess_e79da2fabd48f5e436d0a2ae92858814 | 0 .../sess_e7a0cb5ac086583a7c4f433f5566dfe0 | 0 .../sess_e7bf81464f2a282d2b5c37384544d992 | 0 .../sess_e7e0d10f34c130abe39787681ecc7c54 | 0 .../sess_e87478078e020e24d0779351c0f969b6 | 0 .../sess_e8a7bca6e77dfea8a08a9d85ea373b3e | 0 .../sess_ea261e39262abaa69f59218d58152e24 | 0 .../sess_eaca2607ee393620327eea92f80f61dc | 0 .../sess_eafee1426b1ca0cba1fef01042da24af | 0 .../sess_eb0b3aa7fb842573df159a3cbfbc118c | 0 .../sess_eb1423e9f00c7c286a8de731475317ed | 0 .../sess_eb5dc235fb964cb087b0581dc5470dcb | 0 .../sess_eb632c7b379753ce9e0678cc6e1fc4b7 | 0 .../sess_eba6bb7a7e9f84453ff471f0de9c858a | 0 .../sess_ec5dd9dcf14935e06480e595c5378408 | 0 .../sess_ece749dc3266e76f800a73634c290360 | 0 .../sess_ed3e4c977232325dc987613963520ae2 | 0 .../sess_ed5a34fc673a1877970f68358b1c8a2e | 0 .../sess_ed720542b7344966b8a96736178ecaf8 | 0 .../sess_edae8a6f1230dc09b4cb5a54d21be367 | 0 .../sess_ede9c689e49df248407a15a84fc3ab6e | 0 .../sess_ef7f937ea6a6f62a765a435db96aca63 | 0 .../sess_effb84e824295ad73fbe605ac6fe2f88 | 0 .../sess_f0e668329a30681f9bfbc3cb07c4f5cd | 0 .../sess_f0f092cee32c29d95e9bd7c7c30e20f2 | 0 .../sess_f194bc98e8e4096a6b45e7fb11d139b2 | 0 .../sess_f1adc2c0c56dc66a742546fd7f916722 | 0 .../sess_f1b44223c5587e630f814a8dccd57813 | 0 .../sess_f1b6dc29404814c5b7b518731d3ad515 | 0 .../sess_f1ed9645499bb8f575b5dc55017f6482 | 0 .../sess_f1f8d1c3218c6362dcf0711b6e569ec6 | 0 .../sess_f21a3956f18d80fdec12b29b03404111 | 0 .../sess_f2b140d9118c0885e76b7bb1a5b232d0 | 0 .../sess_f2c30586d756f874aa3952a62be05345 | 0 .../sess_f341b63a04b60a2b5b7df9ad6450e9f2 | 0 .../sess_f349b233d747db9b4973592d72ba103e | 0 .../sess_f36a1f4d2ff411d7442cfccbeff9c92f | 0 .../sess_f4a545daab3a7bbc90c7313ce16cd200 | 0 .../sess_f4a6ba717bff574a5108716d3c1b76e8 | 0 .../sess_f54673683192fda4d1c05bc417825a4c | 0 .../sess_f57e67551a8adca075901267474c5e02 | 0 .../sess_f7434453f5c9fefe400762eea3329b9f | 0 .../sess_f86b00b21a16ae362e3e55e5cbbb49ad | 0 .../sess_f88b647727d942b3ff67893febfa83ec | 0 .../sess_f9047caa910fe889f68f301f00706b3b | 0 .../sess_f9288a644472ee1e11539a71da2c16ac | 0 .../sess_f953c927cd6f250afb7d4bc8791a2506 | 0 .../sess_fa32595e8f8e23ab874e9247eb036ec1 | 0 .../sess_fabbd025bed38d86b6c790c3e2ae219e | 0 .../sess_facf7bf4f85611dce0bc1077747eb283 | 0 .../sess_fbd736474197b68a63d2aa66da52e238 | 0 .../sess_fbf559b7fcb34e39382b7beb9e3a3052 | 0 .../sess_fc283dec0f73891e83fc082f138c358e | 0 .../sess_fd3e050332ff331670681adaa7f3a04a | 0 .../sess_fe0b40307095880975721f2e56e653cb | 0 .../sess_fe92bea57dd460a4bde6b5a3b5b9f844 | 0 .../sess_fedd27c97c990386b6b30a35d8da84dc | 0 .../sess_ff4c8da7fd10a870809494bfdefa4cb9 | 0 .../sess_ffb15114fcaa6705fce00c84bef2acf8 | 0 .../sess_ffd5b0789f866679e1b3167ed8323759 | 0 .../sess_ffddcc106e6d3e46d1caa3c2dfddf4da | 0 .../sess_ffdef08140c48ce0375a0a3d6313da12 | 0 .../sess_ffe009470987847df4966990ac35d661 | 0 1727 files changed, 7746 insertions(+), 7 deletions(-) create mode 100644 api/README.md create mode 100644 api/composer.json create mode 100644 api/public/index.php create mode 100644 api/reset_pass.php create mode 100644 api/src/Config/Container.php create mode 100644 api/src/Controllers/AppointmentsController.php create mode 100644 api/src/Controllers/AuthController.php create mode 100644 api/src/Controllers/BootstrapController.php create mode 100644 api/src/Controllers/InstructorsController.php create mode 100644 api/src/Controllers/LessonTypesController.php create mode 100644 api/src/Controllers/StudentsController.php create mode 100644 api/src/Controllers/TemplatesController.php create mode 100644 api/src/Middleware/JwtMiddleware.php create mode 100644 api/src/Models/Appointment.php create mode 100644 api/src/Models/Instructor.php create mode 100644 api/src/Models/LessonType.php create mode 100644 api/src/Models/LessonTypeTemplateVisibility.php create mode 100644 api/src/Models/LicenseClassTemplate.php create mode 100644 api/src/Models/LicenseTemplateRequirement.php create mode 100644 api/src/Models/Student.php create mode 100644 api/src/Models/Tenant.php create mode 100644 api/src/Models/User.php create mode 100644 api/src/Routes/api.php create mode 100644 api/test_db.php create mode 100644 api/test_login.php create mode 100644 backup/fahrschultermin_20260518_223313/docs/README.txt create mode 100644 backup/fahrschultermin_20260518_223313/www/api.fahrschultermin.de/public/index.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/AppointmentsController.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/AuthController.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/BootstrapController.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/InstructorsController.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/ReferenceDataController.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/StudentsController.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/TenantsController.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/UsersController.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/AppointmentRepository.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/BaseRepository.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/InstructorRepository.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/ReferenceDataRepository.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/StudentRepository.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/TenantRepository.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/UserRepository.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Services/CalendarService.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Services/HolidayService.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Services/RequirementService.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Services/SunTimesService.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Support/Auth.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Support/Database.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Support/Request.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Support/Response.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Support/Router.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/bootstrap.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/bin/migrate.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/bin/seed.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/config/app.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/001_initial.sql create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/002_instructors_user_link.sql create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/003_backfill_instructor_user_links.sql create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/004_lesson_type_template_visibility.sql create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/005_tenant_location_sun.sql create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/006_tenant_federal_state.sql create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/007_student_prior_completed_units.sql create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/008_lesson_type_system_key.sql create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/009_requirement_phase_labels.sql create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/010_student_email.sql create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/010_students_contact_flags.sql create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/011_student_needs_glasses.sql create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/012_split_combination_templates.sql create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/013_combo_generic_key_cleanup.sql create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/014_legacy_student_key_aliases_to_lesson_type_keys.sql create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/015_instructor_pre_start_buffer.sql create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/.htaccess create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/assets/.vite/manifest.json create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/assets/assets/main-C54NZrM5.css create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/assets/assets/main-F76uNDS1.js create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/index.html create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/index.php create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/database/app.sqlite create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/database/app.sqlite.bak-20260422203538 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/database/app.sqlite.bak-users-20260422204004 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0006a50819ddb4c83f8927e073726008 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_00309f86663d537153fcb66685d86df7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_00409744a4c7e3bb92d2eb4076f6f9f1 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_004433bebe95f3233eb3afeacee557d2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_01276da25030fe7610e51db01928f92e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_013cba357db97c86d9affd6f490425d2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_01db5cc5dc2af4df786929887036129e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_025b3db5cdc9307a49bbc4e7c8569322 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_02ce241714378b298d820865b9f207fa create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0302bb8051aea505d498b8118cf07cbc create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_033690960537713e4b7584067e851174 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0387d0c9c23e92d7c5da1f59219d140a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_03ab912d95e7096bd5041a815feaaac6 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_03bec560a62e6282f3c00ee39e0b5185 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_03f80785c72e82de6dbb20e633f8e70b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_04921a0b3787740b88c11ab3b2b4d4dc create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_04ba107751852addceb705f95cb01636 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_04e7eeca7ab45efc4714e55d0e488aab create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_04ec6601cf72642f743fb0ad41d466b8 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0521642416caa2fbc62e3f8298109cf1 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_052b45081be50758c250257b9a65d04a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_05fa0356f494ca589061fb1f1cda9f38 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_05fc405f00dd93a93a4e02d048ecfdef create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_05fe8c42f2d6ce2eb298f3773fe7aa46 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_06322d63f2b61ed856911309f0491b02 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_068031028f6af11d6206ba4e176e106f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_06b2450149fb978337c3b300c5177c89 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_06d7f7359a7c55810206e5722284344a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_076f1f9b3f3607d85628e676aa3deeef create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0781485879098048ec9ec26f838e230b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0854ff9e45ba1f5477fbbad5e54698f3 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_087a67b3949d5d39a90f231048dbc168 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_09013b1da99e4f816a92e860b645e65b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0979ad1bce9bd29c7f78266fb0db9496 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_09a34f83873ab7845a54ad0e1f123da2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0a1062c7493039579c5b1ccf819017cf create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0a3a77bb3b296f6ef7c2c58f223522dc create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0a457d212ce74a302f1245eaadd68550 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0a8407b10f0705fb94abc083ef7629a1 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0a918e1bf4f81d888710bb6eabccf5b7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0b02235f6c9d7a8fdd13403950def66a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0b4c3e7eeb7915687615efbcb7ad351a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0b689f1efcd64909309b788ce0aa77a8 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0b7a6dd656b413dfa91158a8f36533fe create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0bee18c4dceb5b1916bfb6d16f940237 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0c6b2d92ad51dc235c8a326a6f15bbde create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0cc19484bb603a199b713e1506f53ba0 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0cf9ec95094969104196ac2547eb3cc5 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0d444063fde0f76a4a254962ecc24900 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0d674f078cb072a267fab787102b842c create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0dca6c782e6359327b87294c5be46f45 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0df4eb5e781f33fdf8978650b309cd49 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0e04def9d162ce239e6947e6321ae8ee create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0e42f0bcb89c3342f5fd4f7440391e29 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0e5c1480078e1d9b5f84eba3b40bb77e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0e60def1012c7eb8dcd4e9c711f82bd1 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0e98d5ac2834ebc5bf78d2d5f0c91fea create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0e9b111cebcfc845371544ee02e6ba44 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0f07e83c587cd6ba9d4374bad4a0e050 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0f1d85f511393a35984d6532feb1861d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0f20d94dbfd397db961146e58523b752 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0f2589aadc148fbb95ddae15307b2ea9 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0fb288edf359a988efacf668fabed004 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_0ff28449277d7fe69998599d5c19d934 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_10009006bdabf68eb0fab5eb37ca2fe3 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_110b899ae4b4a0c037123345c4e49d27 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_117db80a15a6ba699f9678b99a1d9de7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1202777c449219fd2aac7b5ffb07de17 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1247d7fb0d48d3e756c4dffbc1270e2a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_124be71b45653d1b3f57538d487fa105 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_125bf65956128d9a6750548f818c3348 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_128a4fda8650abb78800f45754ad48be create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_12a821bed8de2d9944c9f8796684d638 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_14624d98ed0c363b2a095334c7a924c4 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_14b57fe7649feaf9706aea2773911e8a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1506f280114920a5be86dc1437738d5d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_15ff36ba1e9ccbb86875e7ffe82f935f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_165fb060e3ebc3fc704b851ac1d01598 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_16732beb713293cfc64b4a62d20e5cf0 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_16ef8072fdc04f0357bc1654ccd9b88b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_16f69df80eee2cd72267465123374bed create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_176074fcad8595788ae0cdc97b974c18 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_176897c620d6f055c1dbcb598905cc3b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1779168ab91c36713aafe5bf02bcc109 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1790c58801f9b2487b8c093cc82be8e9 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_17a0585e95ccd9c51a580839630dc92b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_180e9fa06a637da36cb1f76f4d0ccb9a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_18c8679e062e0a332aca916fe40c6859 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_18e0ea982f318fbb1005dfcda8a8bdf1 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_192aef27a1fce8903cf7d28379fa497c create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_192bccfcedba055ff11758985c9485f7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_194c7cd0b03e84efe00b5484ca2d8aac create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1950a2ef1291ec9df468535c6b0fc191 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_19770a921c71cfc42485c3ac4615611b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_19c0d2d4a229fd60d2099992ea8690a0 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_19fa167361a1d43b543976961054333c create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1a912668b674f48b5ffc7de9e824aa5e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1aee1ec32f6fc52fcde579475a6fdf90 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1af06631c6a9c42ffe0961f03c21c73b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1b62f79b6a161a44066e889fb5890864 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1bc34aea1212c788f139097c26f6a232 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1bd8426e10991ec9c1856c793196522a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1c19ba48c4d66fb010544f9777b0b4f7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1c1eb9653903a3f2ca0cd68b2e2a31c8 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1c1f7b1efd31a1165e99abede59cbc0a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1c8fd85f2d4fa6c41130f08f87b3068d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1daa8c750fa07697f55057eba9f07208 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1dfacd171678bbfd3713e55ee6e1c0b0 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1e0fe014daf164ddf58fba633fb433ad create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1e19408096523f324050696e40e78c2c create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1e3b112ac09b1917473e8d5d1f050a13 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1e3fa8cca3f53f006f75140261d3a5d5 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1e4606b18533461a9fcd557a11b1339d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1e576ef7adf606c5bdcb7341f2dfc7ee create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1e607b0b5e8b9b44045b78c038ebd342 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1e752925b000debefcf8e1b6f462bfc2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1ec360e5d18fce3b53084f54c50523f6 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1f3d92dbc703b68c7e795e5f51c1dc19 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1f6c34e49bd50f27424f86a2c7f0b2a2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1fb01a63321004f84edde1c2f01b9660 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_1fb1ae525d180aa6cb59e558984c2072 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2041982bc34bf965174b00a51558b7e0 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2043674c95a6ab2d3f71332b76dda4ce create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_204d8a56472208a59a1aa70ab737fe89 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2123c53e3d0044bd7f2e5cef59a41837 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_21b513631be5e9c0dfd1b2d517f84398 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_21d59f9f57be2f40d95c6857ba79b486 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_21e0c4efd285609f6d2a1f54f58971ff create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_220bdce3ff2cfd02d7e5fd4851774fdb create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2230676d13498deef61f7ed59dcdeca3 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2292fab487ebf06df95e0036beac57f1 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_22a4e3c7055808fd4c5a2963e52ffa36 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_22af559095a190687e0d544b8eee6644 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_22d9645a0764cd276e1010062ce58708 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_22f0a00861d4da216089fde3b14fe2ef create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_23a4409db0925d7a59ad29e42bc4884f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_24390c9968cd8821cd7bc5f2aca39df9 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2456d2263cd6a8256041d4b3c89ff307 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2470d1af939f31518959c76496aa7a6d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_248cfaa4e75cbdb490cfe8dce5aeaedc create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_24a867c9d2c15b255232a9d8e1e84f38 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_24e2550cf6651e9d8a37e82da464ce5d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2503f4a5cbae9324cbf3b95b5b97df27 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_254c55b9ab44a7e81392a361205f9608 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_255b248494b7bc41838d5e03d8c63f2a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2576dfcf73df11914c84eea9030362ee create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_258ba55b7396886da889d6c57b622370 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_258d74a6647426ebba4ce0f0a0c67c66 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_25bc952c7944ac5478e505b7d07617b8 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_25bfb2e99746cac127855aca7dd51a66 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2619421cbb71439a217328646badf18b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_264744b8536d1f0a4942fd4884126ad1 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_26475586b26e819cbe7ed6b7c91f4353 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_26817db6139724550add44986d13955b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2694eee41fc1488b7bb8cade34db7095 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_271efa4ff2d546b1e0a65b6f3ddaf929 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_272592a07264fcde1b184f03d1b63a3d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_279218f8e2c26131e4ca39dbe14eb7fe create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_27dd196fe7ad686655daf28dbc6ebd34 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_28136e39aa3fb81f6ea47823e5cff14e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2878c22f67e22d014964d1bc29fc80d2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2941e7e905f15d5da543295c53736cb2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2960b8132f30396e838b0ebdbb96dcaf create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_29a45c491f6998a47aa0b0f66fd83f3a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_29ccade580d9dad87ac1a35ff275d2d0 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2a16d5f4beece112f2e67a4bcb781a07 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2a64bf1990c00f2b2bc1836c1b96fd2f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2ae5a56ff11d55dbcf879524b71f88c9 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2b67b6a92be7fcd363c23a7e494057dc create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2c5719342be78ab42c03ddf812474077 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2ce24f0bd9ec5909e465b42fa8d33670 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2d1c750f56256eb9ba589168d9b17b65 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2d1cba190840b33e6d54f9befd89ae52 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2eb1f32411558996d87b5d8567dcd94a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2f4233260393ed2a31e00ac9e2969636 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2f52d9fd84045a93ce4bb3a5be6a31e0 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2fb5535ba46f2edcc64f6b7c581b9c7f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2fb9f36e4449aa120905dde208f60510 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_2fe3e2083c13b197a53c094156873f5e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_309ebde30dd24884b70bd8df07279749 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_311959d4bc3d190ae2499291e9b42220 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_31210c92118f237f856a58f569a84141 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_3160a2a2b74ee599bee5f62ac40e1591 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_31eaca9ab96ee1d5a814bf3202eb3c22 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_3211e64538a844841fd3167dd09dbef7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_3212a8af9dd17c466ab8ea91052c3cc4 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_32932fd7d38fd6c8279e60d44e48e922 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_32c66a45bdf19b9f50de688ea047e696 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_334e686c0876c391c42fb587b3bfa976 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_338deba0ad100d079f68ea413a6e3048 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_345384363d30f764d4244e131c21d36d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_354431ef347b5ba5cfcc7273061da04e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_35a3681412ab8e77fc498a7699733368 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_35abdcd2565ba6cebc1d5323c51976ce create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_36323cbf08bff404511acc133ecddf9b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_3689a40959fc96cd6f3c50abc1203ff7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_37282ed122e7b4eaaff47a3883309651 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_379e5c9dcce57030269caa935f44b608 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_382329b5050c26c18ecedab29e115c9e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_386bd2888cf16751430e80917a8f85fd create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_387a266ab74eed24c6346816390f7769 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_38f8f88f7abb27ac17d9375a5a4cba31 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_3bcfeced08df9ceb2af9241e3e4204e5 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_3c6086415f4fcb8af3e7af3207b96d8f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_3c6c3f9430ffcd8fda0687736ec2d718 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_3c7b2b014f20046f58b831fd0ad34c9f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_3d6993df0f462c4ffff7556ec530e435 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_3da2ba55a8c5d696a5814b42dc0b2e14 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_3db0a0533c6ffd6078c55906988a594b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_3dd3b5c1c41191d551256202b307b743 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_3df20e7c93a01e37ead25a82abcfa7d8 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_3e081a2d79c698f00a94dff6db2e962f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_3e0baa6f9f93aef56b4c28cbe7f0892a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_3e34570e03450813b46f65a5332d9c1e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_3eba8aa5bdff2f16073b6b03a879f828 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_3f4d92dfc22756dc3491753c3710d58d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_3f5d80074117dcf2345eb24489088752 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_3f7f9ebea56914012835d097cf04ca92 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_3fae57c4628e0e6de8700f2aefb0b86a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_3fde18821c79b139e6cb19e20b72a0d0 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_407b46f6532a47e17ec5babe6c30bc4f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_409dcbc73298f3e020b7c60a3432fde7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_40f2a96e744926f0865a52dcbac9a160 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_412a5eb5d0597f918c0bb9bab03b2faf create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_413faf6f188d744d9522dd2fcf41495f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_415f109c0c4fe4d2a1d4979ad5d9bb24 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4181fda8edd2d12b37f109fad33ac6a7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_420cdf45b5c2330160d506a70eadb33c create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4247a0c7906fc90ae18a64adaf1aee2c create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4254feb9a5b1a5b349f5dc3556d1a201 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4338f754256fd9a05ffce8620f13f07b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4368d3d721b713fe8f89cfbb6e1005dd create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_44b7c1306ee68cfa7a89fbc4e043209a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_456a3e7bbad68012698a87d1072c1b38 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4587b7a5e1e5ef217166c56763ce423f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_45a98e9ab472b14179dd14db5f14bbff create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_469e07211c9f0f2764b6d7a2c851ee46 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_46e2408207b1c95658fb7b626ee6958e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_46e8ebf399968a345980d5a95e8c06ff create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_47b05bb74c2af2828b06cbf5e3ff8477 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_47bf2f6a7024254fa8971c7a85b0910d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4853a954dcf6e2ad4cfa31535490e110 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_497ce6e38a9c5c1015e856f7589548ad create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_49a86631804a0d6da2aee719475f739a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_49c66d8f15ac3c3053c8534b28fd2ef0 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4a45962e93cea2ed73e1665936bc73a3 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4a6501b0989d99b4dab93189ce30fad6 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4a8e405b702f60027e17800b0a9ef45b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4c9a1aaceaab78fddc55765178473d79 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4caafb58e3487c63db3e767199e558f2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4ce44d9d6463335e836ae3de308d4b34 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4cfbbd26846fb01c6f9d339c557b03c8 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4d29d6096a285987a2b2e625068ff5ed create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4d7c6193ca1cb655e8d82dfe9c6c7eb4 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4e38e1eb4f49de672fed6ed540681642 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4e71c4ae079c79d06b3b4199b37c8d6e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4e8ab1cc7be41320be57403a5764d80a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4f3604d0593e4bad5b3c1a2ee7fbcbe0 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4f758a3b77fb168967f90b28555033d0 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4f9fd9991f5f3d30dc0ac97c372b731c create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4fb189b01ba2f06e00ef5f9b5a7f23da create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4fbfeca9a5fb8855a221e690ef502b38 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4fc591afe53506325ffb61b8bb9c88fb create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_4fc8717e82c041e7287a910c435a73fd create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_509426117c6882aa1596380c7cf47a87 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_50f11f6d3fd52e9b97404943e55c90cc create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_5104ac2b3efed41e0167e8ac230efad4 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_510691ebd56ac6658a8f0d05def56656 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_519f45ae9f86f930f3abdcb63300057f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_51e0aff602241718c4b8416f452b580b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_51e0b1db7e62b4fdcdd8306d0c23943d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_5283908e9b80eb47d1f52f4d4400605d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_5294cb940dc83bcbd4bf57b17a00ba3f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_5341c2fcecad979f5311ceac1bd28718 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_53926f2b0c401a2668cdb0ee55739e71 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_54e9093941ef282786b14c14e94470eb create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_54f3fb06c0aca2a55c2dd5ec4d25512c create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_55398030f9196b2640c7bad95056c2a5 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_55b079046d294c1c69f287fd4337d04b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_55e94cdd1ed910c96f22fc4e1f73c0bd create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_56355cbfc04402cf36d92f821bae5090 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_564322bb4ff156306233922d7d4fb345 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_574d1c7a48adf43155170876b326b4aa create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_57744595846324be63e78c3aa28a4f33 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_5775e29dec1dea074f4fc9c32fa3a34b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_57e47e62da11c1399a779afd1e78b75e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_583371d8396ad02e05cf1ed99be48719 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_587bf41af67b4725cf167a5652622640 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_5971640cd75544c110ed69c8b2704b38 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_59d0ba86b7085a23b070b559be815e6d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_59d76f244bb8bfebfc4b923adbc15419 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_59df6b8eff37a8f46f806ef70d53862d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_5a4ae2edb0253f99e7e0d768060c5e83 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_5afc92d91483488a5e2756611359c02a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_5b9ebb48cacc40324a7627ed5cd03ea3 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_5baa5d3d23563e43afa7446486dd72f8 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_5c0025a4a3bc7edc09d828efe2b28cac create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_5c0f0b21134060b451e12ef30081f540 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_5c2d18b62ab5f4e62bf93bf01d6a8abc create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_5c49211f43aa46d12999628ab1a4bd28 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_5c572f6972ef0bbc0fdb158726375a0f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_5d5cd460d1d5835451d781e05c3adf89 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_5da65e8529c7df8023ed18faa35d5a9d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_5e286521edc4303f85116330fe10e8a7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_5e7a74bf5069e74d295e89a325872261 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_5eaa995db9fb15a416a357e533b6b131 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_5ecbe991d413b362c14567f1f073f4cd create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_60668ea8e3890507478e420a56ddbd5f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_6143815503ebf3f82c544d21c1b7ad5b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_61b414feb765617dd3fb38f7eceffcfe create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_61d6228ae940365ff8bbd93a87303a02 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_61e9d1453030accdf287a54e918ee927 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_61f558ea9155fbeba01a42a2931d7b7f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_621511f8d7c30ebc5aa01ce455b8ebaf create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_621ef44ae59c047ff712c18d050011e5 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_6299dfcc8ce40cafed3a335a341a6336 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_629f9dd62d1204aba62dfefe65fb0d74 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_62b5d304a31805cce616357f0e59a9f2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_63a233987c509e08fae79917349f9bb3 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_63e9364f0c944608a17504f9b91784b3 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_63e9c9819a2de781d792f1d2a60851ba create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_6477fc14a025865e68f2dea333bebea8 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_64ff70ad1229278f497281f0214ccef0 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_65468453a39a8a4dfaa1082215ca9836 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_65b339d0b1ab7066ca6b44ecd8facc40 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_666d42c52b9ccca9698401d63202cbbf create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_66f307a1423bb488cc26a24cc5c4a42a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_6719a214f44edb74418e6e890bf5ac08 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_676be8f36a7e54aa1863917c871eb567 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_6779e0e89629bc58388284b76bd76192 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_67c0a94fd2af88c6fe4e5fcd34036c36 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_67d11c2d90f54a43e96d16bf3c7c45ce create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_685d40735c321a9ffb35d557798cb1b5 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_686f0c41b1eaaa6c33131bff43058b77 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_68be2f0cedefb60f859f4c4d9c2a880f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_6908fafa1345473eb21f5cb9dcb153e6 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_692cbc36b4318bfe850fc4aede3832db create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_6972dfd4b41c386eeb36cb9140582224 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_69c6be375919a05a7948264c4db4e9dc create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_69dea144c3340502072b895a59b41b24 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_6a4b12a9d81b4f56fdab7814f88b59bd create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_6adef316d6f11d1cfcb4765ee289aa95 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_6b3dc5917d807ae794a1ed28fc9e99f5 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_6b45e848464ad9143282b3909897ee8b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_6b8aea5fa4705e2c8928d1ec20387164 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_6bf7d9cc4cb88d9fe96dafc8ed1288a9 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_6c0ab8e461aebf50d0ed2a344176ff8d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_6c601c0358a1d9724a5252cd756bd64b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_6c676b2e4c18eff505c1853258b039cc create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_6d894c43c030d8dbe071de186700c5c0 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_6da8ac394f5fd3b79b6eb9681f810c15 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_6e5401ff80e23d4dbf547c4a894fc211 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_6f3a5c611d7a16c43ca25127894ed8f0 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_6f84b5c8b742313a8b748867bb5dfbc4 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_6f98287cfdd3ff93e952baba05fd5c31 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_6fd95d60ca7ff94f3c602ff4753e6a87 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_700631b2fa80717f2d07a6017d5ff030 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_700aaf47edb8ff558be85c5bd9ff529e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_70a66109b64b26d0ac67524166cb72cd create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_70c642898f8f0725b81e4d285eec0787 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_70ddcec0c3417113fc4c072fa39fe331 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_70f9c044b76efda9274b32b5ae345d56 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7108ed71a1a0054c7ab7c7c2068e8923 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_71187da2021a77805c96d85b76867d92 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_712f6993fea7e24e5126734f0aca9a68 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_71a643c2efaef2d77620f637e5637cf2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_71d766001d452c1d949ce85cd327574c create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_720f14d25a4f2ce52fe9d3933e014771 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_72515b3f3d38bd85e703ef3a80b1bfa9 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_726860093a0a7908e0e9f66e709ef6b5 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_72ab8f6fd0fecbe3e0976c623333be7b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_72d5d6b9df6a55503533e97e340d2311 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_72d7576d9bfaea5245d54bc6f5ed732a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_72f0fc1c0ead3468d75f171ff2353eac create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_72f8a1f9caa69b25fa64c6185238c6e7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7316664ff3b366d41d1a0f1d29a21e44 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7327bbf8e70879c93cb9fcf18287ef57 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_73372d764d0b64494a123b95b092e293 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_73c80e71dcc72c4c3bd422dc2d1c6f22 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_73fd9c9fd27ea5b4b7e513723fbe0d01 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7401b0da36a0bf586a412bdc32c01932 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_74169a80690859946b251a7e2789c06f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7433d5d741f0c55568cb183264a757d7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_755543053755f9827042ecd578a32ce3 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_757ed514e8bcf21fe484abe2639fd9c8 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_75ed4967153522837eebaba2104a96bf create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_76036580c2a0124ef45fab001e1b8e85 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_764eb3d09d7acdadf02a1b1b9e652d38 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7751d4611abdb82cdd8307b7db2a94a7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_777222d786447c44cba1f61c40722851 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_78ade15b28ae2ac4544641f92908c944 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_790eb3970d7871971226a752514c9021 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7a39b110e92a44f5cf2cc27318fa8acf create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7a49c9790485b15f032f1889edfb5e74 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7a758afdfa5c19238907ee69e4f1b8e1 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7a942e09063d7d053ece5084917a59c6 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7adb1835b69eb442280acb1a34e3e4fc create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7b1b6e128d0bc1b40a30954812f3f2bc create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7b30a67240cb34b565faab1ee1a43759 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7b3b609d4c8713d06961731c28fdfd8e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7bb08b912f4e650b85412aefe4225981 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7bb50e18de85135463e20f8d52dbd679 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7c34f05d08306cd0e725b262006af9c3 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7c4815b8c0d9564d5c330dc755b89be4 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7c5d6ce984c7f26e83b67c6a785f2919 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7cee3f52567d4d995de6c242576e80e6 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7d496df6ac57de8fe4c46d48271fd6b6 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7e2cde5aa2397f9c928af07c857d9059 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7e394c0ae018145fbf007d66dae56fc1 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7e44e9b5e4618ac9c5d5c50156aa9481 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7eb154203930535a374d572b26905543 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7ebd209f80c9a78715a398ca29339183 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7ecdf19414bb4c4fea4ca19850a7a539 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7eef0ffbe7eb689711c83038742d15c7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7f11e5dde38e860a69e208aaa622312b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7f97b81d9a078302523e924f6cd12861 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7faaa1325462a16eb0c09a78eb816c3c create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_7fab50507d10a6d85e79235af15d32d2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_802f12cc03180b848c62d75314cea4ad create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_80ddf16c4abf6269af3663c2773029e3 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_81367cb6c7667b5ce00936f7e4c75ed5 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_821cee680ccebd68e213681a79f95074 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_82c9e9a8ca48d231b1a6143016f12161 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8390ca0087f926e6dc6acfc1726feac3 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_83f1a287583b2bc46cd195e1e3b2f943 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_84323f8c4143d694e44c9fedcb9f9df8 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8506d9ad640e8db4c5464ae18b21aeaa create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_854a85d95cf30e598d3dd8071c524182 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8556739092f403601805097e7c5515d7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8578f708494846a3ded7c61783b1424d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_85bde53b19790499e9dd46a1728f2644 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8609c314bca4daf7b8d418593296f29d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8631135c2359b355b28b12b251d7cf20 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_869e70a50cdab0e5dfa7f41decbe164e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_86b6cee8d0e002bd2cde78c01432c98b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_87765df5421b4b08ce0285280372e9bd create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_889d219494675742314ef1f2307e5059 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_88af4a1e93188192b41b898c96537666 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_88b7f3ed2893c08c3d3a90674de9828f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_88e7499f47401ce614b4b65ac8356131 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_891ef4846f2cdff9342aeba0a22d5f98 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_892e5cf319f4e18afdb304abb39c54e4 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_89625043f67d25fa8715915de6bb79c5 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_89a3b9f54d67342368079a0ac616276e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_89d0ffe96d3b0ad881121413908b1b83 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8a6817782ff0d92875e6235d8ad776fc create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8aa40af232be8a3ca5282ef5f551afde create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8ad98e7026500f40515eae94a2fde2ea create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8b186147e95653d8a3b6c748d2482dcc create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8b63e5d2df259415a70db075ccaee244 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8b8a7cdf7636a6d2aedba08f756d3c8d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8b99977c6b9caff09ba87062b2bccadb create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8c02e46467ce79c739e0a68444178b2b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8c6134b631065799b0ec7bee4049c4ab create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8c7302f6a643e4dee153b7d114ed8783 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8c909900d173078a178b390295c8d241 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8d2904d8920390de86ee53bd52ff2f95 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8d3b9af6665ae182d39cfecb43e8c67c create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8d6b378636ae0e195107452f07a9a1b6 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8e2e34092cc5092b33f80789bd8cb5dd create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8eb44802c4c08e84235edd0bf26ac87d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8ef6f0dee24d8b4ca5d6d12fc78846d3 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8f45c8b37f65f5a3d96d99bbc42afd6a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8f59107da2dd4018eaaf396b9d4a5a36 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8f7dbcbed4677531f01a98f09b78e579 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_8fe1ec9a1ace4c1e42a2f556662301f2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9064761135752f81f7ad8b6ebd661023 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_907c362f4f2f6e400df8ac189f9f1eb1 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9087759ac2d7809daa14034e92904b12 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_90b94e25c780902f5f539c288bfcf537 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_90ede0d038bcbb6aceea8fbde52f2641 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_90fe76e7383355fe08eb307219e2d816 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9198bcfeff6d315cf4fafa6370065a03 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_92c58ef6f3c99152d2ba186841a33c7b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_92efa0c840cc52c16596b78a5df32412 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_93f7f0d85f2643a76ef136e26da9e002 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_945189d26d0ba3469d510a86ca265d85 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_947481d841466c3a8daac3ad85aa115a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_947988622ee7c2525e6cd5d894c98ef1 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_948b86a8e2a1e31167679e4a1470c860 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_94e7ec5946942ead16448a728abcdc37 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_953a407fb72605ba0a9a187e23cfe770 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_959d4c4a242dcfeb97c61a3ea2412504 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_95f18066efe5fc4f9cc8d0593c8439bd create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9644fd126a3afc481ee73045a503baa4 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_967b281af59e4a6dcf1cc1fbc3265a9f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_975c0b932ae0b2f7e84907a50b8ef1b1 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_97f50b3d8687d8d47bd563960e1ff455 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_98892de18bd4ec4aebcc5bee99ab94ac create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_98bf4125aefc7ddc22a20c48a9619989 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_98d9a575de9d3f13469f22cccc63825b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_98ea1635d552d9a6892dd0ef71435f1d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9959bb4c68666277b4b2c0282a90487f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_995dec3fc6c2f46d050ebb554f56cd66 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_99ad47b9cdcc23e23f1e7795dab1738a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9a0e601461b3d9b104215919d000998b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9a7db4f72bf6ba3b89119eb1577e6915 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9a81f6a69cf9a752f08a42af7c04dd7e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9b0faa79ebcc182ad631d2427d6ccc41 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9b9ffd3f5d1b3b7e7fb4ab8021ee6724 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9c87061c647a1863348a102fda0a485b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9c98903028ece4cf33487e12338563b0 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9cc05cc46ce95864ad3e87c922bd7d6e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9cc7d48622827c73b5c2800b90da6c71 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9cfbca439e6352c64927f83dfba56d33 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9d0f8c2d99f91d53540c981a669307b0 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9d585af32abcbce5c19f7dc88013b509 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9d95d6e5b127e827982ebd70a7780ac5 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9defc0b6e6f8e6a06175fd732c55f9f4 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9e0ac6eb04ae0415245380aff8a7fe18 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9e48c6c6d01f1fd30c8ebe200c54ae74 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9ea742913c11d08c3a459e7cbdc48ea7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9eaed2cf9bc04a09871029820a0febdf create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9f24d1e11f4da71f33ba80b598bf4635 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9f669749877accc7939a55a65470df73 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9fd8760c7cb04dc202cb2fcbd65fa171 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9fddcfde761802254047b04993e2653a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9fee4660d995f9fd6a5586af67f086ea create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_9ff67287131509f45cc8628249181573 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a04f945480a6c46840307cfd5cb133c7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a080edfb79e15193b1a986d2bd1c8fa4 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a0cf773a2425baa5763a3c5151c1e6a3 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a0ece2f1244996e6289cf22fbe69523b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a14487d530470e659ef4624121d3c2c1 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a267492beef8994b2bea7a841d1a0cd8 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a285d45c3472af1ffc36a684c856ec99 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a35bdd52888e241d85684a6d02a68bde create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a3a3b332d752eebbfaa33f846f2011e8 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a3d22e835cba5db3a924784879c04be4 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a407d22c03234511a8692574b1afdb55 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a40a10b60dfb97885a13a60cd56ab03d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a41afc440e071af84d3aa05ac5272fc3 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a4599efbaa95f71a3a07ef11cbdb2982 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a50197cb3ff817a6e011f450bfc33d48 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a50e65f017e7c264acc21fba0639a32a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a559140c7253e91fcd6a8f6f19e05bff create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a594961a601d02783f91faf40e98312c create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a5e3f86f5e484f1b76040bba1e5e14a8 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a6ad3eb821ff648a929de3ed3197bcb2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a7cb6058536c2c10eb132b9ea2273034 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a7fa2f402ec1a091b24e4dbe172fb7ce create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a80186e2242223f495ecfdcca4153e08 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a81980865d0ec43ef0c5583e07965859 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a87aa73edef2fa7a6931806265cea0c9 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a894ac582bee396f28b4a5b6d76632d1 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a8eed80b4e6c374bce30583a334fd1be create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a8f1dd35649e9f13895d193df5dcb5ba create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a9196e31df6affc38cd0927354660e20 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a95b3114ad98063dd3ba2e3ff6ee4089 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a9641cbe82cd2fba425d8bf3f1fa632b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_a9dc429cc9b5c625c3b61d1647124d00 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_aa0e7054e4831c8e95c9d968a25ca3c4 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_aa6859d5e79333e0b7fbf8152c956007 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_aa8f8b163bab75cba3311559cd7cc687 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ab4e2933297bec79168036dc7f3d1c4c create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ab893deedb9409a65c6b4c2e1fd21135 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ab89791d24d1aaadefc456a1086a2560 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_abb03b95526f02aafe816672af681ebc create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_abb7a002d65efea430cb0af30b78a078 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ac0e42e33c97dd59d436c667a5b3eead create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ac4027a844896a8a22c308c293a15f65 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_acab22928f8fdc15eb012b3494e41797 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ad5c7d07120b55ddac12237c4bab0e2f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ada72e41e49e00d5f656e5da43eb6bc6 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ada8c2049e1c9327b3a50dc425f6bfbe create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ae1d8a22efd12912343d561876fdfdc5 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ae234126326f18ee35485c944b7e5848 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ae34a72b4fa6668803ea46d383ea6f24 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ae52f8b68722f536aa70b7590035f9e3 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ae64540044c09c7b795ced78505c9b4d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ae70ef607969820ee6fa532c7c516c0b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_aeafd646edb77412e928721505f984c7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_aeb687357e6f692b2199e9e800d56673 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_aefb95455125a664bb95a1c2ef06f8ad create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_af4792ba9e683db497c8912f32609da9 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_afab05d39b52ab8998daa2eda66b6aee create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_afeb67ef6a5ce209e756766ec3ffe2ce create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b024fc7d8e3356fcadfeeeb2d14b1815 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b0c1002f2db893b0a6af43e0b577dfe3 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b11cbd54af2f7c7e43bcc82b0279e10b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b17c2592ffc464b4ba0b679af70ded2b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b1929d13ac18473aeea275a695808d5c create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b1da207d6c31fc9946ba3621f021ae80 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b237e22685289229a1db2608891cd30c create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b2a31fa222e71d201828baf1ecf324d2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b2eaddc565cbb2b2e87616e79caeeffa create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b359782f5ccdab7e806fbeddf150dcbc create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b36f9ea866728a5fd33680170aa21c86 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b3a6ab8bfc108f99d05e83799ba0c606 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b3b8e7ee9034bc9159f2b56875fd7cc8 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b41c95ac09de774cf44fb1e10f5e904d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b44275f8a8435fbf5025d135126479b1 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b491790afd14563692ea36be5588eabb create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b49f8d76aa16a1f4aa34d99f4cc1e1b4 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b4dbf3766611612508d08f46c9b190f2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b4ea676cb3216a36a1693463ef5b7c82 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b529a32f36544fee5e632be00fac097c create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b53d30d81ffaadae86b647f47f6a1160 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b53d83093d2c2083ed7132b123982264 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b5510f652a52854c7651f6918feb977e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b57a0d89a948fe34f8dacda9cb43bae4 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b58721ac9be6edd4764bf6f8780cefc1 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b5a60f9daa55f9734f493d6ac0458573 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b6ec78ab609d0df2fd508e9163c253e7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b6f50ca4efe83c023d25e4ebaf6487db create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b79d07314aef27e7f198bbb8b140a36a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b820236352b2730de43cf836422e206e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b84c49ccaa5176f3f60d430f254f4009 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b908569ab6c58bd1746f5f4fd534f934 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b91a01b1e5d207e45e16934c8ac97ee8 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_b9c58c1055898aec49f2b876bee0989a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_bac0d94744e5d5ef89296100c18f0090 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_bac2cfc9597d42d210a3862582579fa8 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_bb4a64bd891cc061c3adefcb44477af4 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_bb87f20b1e8a384d46d56453cad1e43c create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_bc2eeb565a25074bbb1662c9574c76e6 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_bc471f7dacb30aaac0280f97b8b2a40a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_bc6a3acc6bd9424ccd4f53381763a765 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_bc9a6fcb99473c1d425a69fc74e2816b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_bcba56fadcb6d5fa33aecbc0accdf024 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_bd4a221108dfce794cd7f70d523dd168 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_bdb7dda4a0ef116d409e5e69c1ac3fad create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_bdec0ef240e9a5bb1a666d8984758976 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_be317991eb08458fb8e9abcf374bed36 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_bf428a4caf8a7d4c47d26f32e16fe043 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_bfe6ee4113b5571017455a38ee1e2317 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c018a7218b523ecc2ade22f7aedd38f1 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c064150f27b1dab4111a60ccc6f9d2fa create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c0ed531b0f4e24c41d874a91cba8c32e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c1f34ca67059e96e6bcd732eea2446bf create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c1fe8bbe901ba4706855bd9eb723ba70 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c21b23debb7c016fc702f87a0294f3e8 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c2426b9d31ca734eabd4e282665bcb9d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c26296dc097c2f01694ee80ae77eb6e9 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c2ee1217613c2a80e949b8716990ca88 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c38c51006dab56733498c50180b3fabb create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c3a730d7b6a59d913d50e31df3e4c3a5 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c40e44707ce35c5ef27e8802aa39e47f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c441f7bf34ccd6ae14805365c2ab89c2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c45b5e61c6d053c4f763d91407f3c91a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c475fb1bab33463d842d886993c1571f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c47945969b61a07537f19f946a384741 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c48155c30b39c8362f86aef75c221163 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c542af71129f343b6408eade9270bd54 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c54748085a5be98912b747457ee83e87 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c54891068be346717c001cd9135f2f1a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c57f6f9decec3156c65ae42ec51d39d2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c621bb0f553660ec429aec2ca0f43023 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c6429f27fce95541dab9e8ed4952d4bf create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c6bf258aae59585a0e4d3baa156de525 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c6cc348dc8a6b160339d4efeb4ad5a73 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c70b845bd7ace83384142056d6460d23 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c721d5b6b93058bc391ed27a94c9b40f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c75f86e937db953edf0dc710594a1a34 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c78586d22fded1470a8af498186b8e7a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c845a8b285996a7f190591807a2e5da3 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c84f8233f589f47a1d1f3c95256a6d54 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_c8c9fc84e7bd6bb28854c759734a4aaf create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ca26d03afac9fb59c49d2459b766b0e2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_cb10ef5334885e0f4bb07e60b95e4bea create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_cb810d18223327abb72bbbce488255a2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_cc6ca9b9be103498b3b3f9bebbfcca8e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_cc7cc7d4d0e038443bc7f215fcf60584 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ccbfa7c487f33d217038668dba1c7f98 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_cd426fff8fdfa8c52522fb101ed1c200 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_cd6936d236e56a0958f326724caa9f66 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_cd9a052a6c053712db213bcbd569b448 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_cdb2d2c089e55ec49e126ffaee9da500 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_cde83bbf5f7883231a8bcefdf4b5749f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_cdf9116287630763b1acbe585ed5e0a2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ce5934a087e64591d7f2d218c00ea365 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ce8a8426d076db8a3d564204d4e29b24 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_cea4f34bc0b31c76efbc1ff1a6dd88bb create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_cf7075fc4f5727c449d2b7fa06303609 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d0054e42522dc3082a38d55702832585 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d053186c866c8aaafb3c49f5fed373fb create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d082df7384cf4c3855e911af2e4a6d68 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d1b6c144193e51c481f30adaa6f5ddb3 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d1d621d535d1444562ca2d1b7a43400a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d256ce4f2ded89a00bcf19cbcb1bed64 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d27d2227ad5637ccbac3579310e60b6d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d311a3d90755123ef2e6e86c68eefcf3 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d35e6e95a5fdda69f32a5a8bdf8964ac create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d36cf7d1753dc1ef650e4ad031194f3b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d37c9bba5a42b3760ac4fedfe02002ce create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d438212c8192544d26843d87bfda1e5b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d44df14d2838d90e63d1c5ffeee18b63 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d48edd5255a24ae738e3a13c31f7bdb2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d51b539ad255419ea6fd83ba3e9caa39 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d5ee3033b6819cfd67d2ef4d8feadeae create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d6173d6126d730e93fa9cd6774f53be7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d655d61f4acc04d61814576220dda1a4 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d761d9849d639d0449a853e9965d2558 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d764f7b5069d52949a1249384c2bff40 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d82cdb26dd412905a1b72ed027a14fe7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d866f688079ed7f6798f6b2c37d479f9 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d9079a969cbbecca3cbeab898a7b3d3a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d936dba3104d21be80694fa09c3f2e76 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d9507094e57544f8c58778996fe93796 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_d9de9a32167fd3c3625cc28150d3dc59 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_da17ec2d008cbcf1fccdade5739c1aa3 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_da4be22b9206de27363e8a6a0e53ed6f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_da63331a5ba2e04a7f539fa874066438 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_da8ca3466e87e6afa9199bbf808a6e99 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_db1dc99bb1ae9f864d590d8d4951ea24 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_db3413134a45f8caafce26c4f5bf31c9 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_db5cdce7ad0e207e29c025a404926a56 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_db6ae4770e9b9d1af93a1aa4823f8b56 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_db815a56ecd54cd9683ab4a1e5fe00d7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_dbe10898134fde8c830ce6125d63547d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_dc7ca58b2438ace110816de0b89071cb create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_dcd8063123fb5dc4da53f99e3be8a2f6 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_dce185e29b874f97b24d0246486f3634 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_dcf331017d5be4b352f8f7234fbf6e50 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_dcf395666a298642aa662bc593439f57 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_dd395de0ebabc53b439f4fc9e32d60fc create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_de195005e2ff63bd109984d154a398a3 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_de495cd1a69c28af3b9398e92e94d21e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_dfcf80bc29fce6abfed5ac34bebf7c5c create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e02cf9db82152079377afac29dee9f7e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e0d389d5ada1be1011461856fe6026d7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e0fc214acacaab4e1609c3b68e4a04ee create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e10dbe6740069aee59b04e33d58750cc create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e13a7684ee74435983312a287efe4c85 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e180f60c178c65bb2e9d312b4530a437 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e214cc8e60797ecf4e3cbc8dad77b351 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e2463cceb2b637358d505ca930d980e5 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e28398cafa24c8e0e56db5aed7749dc7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e2c69c8fb699f8ccdd772818cc7a843b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e30bb2069903f00d5b2b403e63af7538 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e32f151f178fc3473d264598c4cdb3db create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e3cd81ee2a416b06478fbb48384a4536 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e3df5ff0de2d27045da872d3673acd8f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e454faf4a50341fd4fe7d3d7a4a8043a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e4e5ec23b16a1f4fa28b55e9fabb367d create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e50445bf32d3a170f42828268268b95f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e511429ccbb1b5bf87fd2654d2c93dfb create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e512cfa96becba816ff098acd58d67d9 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e595133187ee681f4d193ad6d3d98bf7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e62417d00dfab625e2e867bc95c0e6b3 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e7294a484f93c841c720dfddaad2fa75 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e78b800b29f053b098e046e43e2b5e32 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e79da2fabd48f5e436d0a2ae92858814 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e7a0cb5ac086583a7c4f433f5566dfe0 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e7bf81464f2a282d2b5c37384544d992 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e7e0d10f34c130abe39787681ecc7c54 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e87478078e020e24d0779351c0f969b6 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_e8a7bca6e77dfea8a08a9d85ea373b3e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ea261e39262abaa69f59218d58152e24 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_eaca2607ee393620327eea92f80f61dc create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_eafee1426b1ca0cba1fef01042da24af create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_eb0b3aa7fb842573df159a3cbfbc118c create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_eb1423e9f00c7c286a8de731475317ed create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_eb5dc235fb964cb087b0581dc5470dcb create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_eb632c7b379753ce9e0678cc6e1fc4b7 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_eba6bb7a7e9f84453ff471f0de9c858a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ec5dd9dcf14935e06480e595c5378408 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ece749dc3266e76f800a73634c290360 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ed3e4c977232325dc987613963520ae2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ed5a34fc673a1877970f68358b1c8a2e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ed720542b7344966b8a96736178ecaf8 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_edae8a6f1230dc09b4cb5a54d21be367 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ede9c689e49df248407a15a84fc3ab6e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ef7f937ea6a6f62a765a435db96aca63 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_effb84e824295ad73fbe605ac6fe2f88 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f0e668329a30681f9bfbc3cb07c4f5cd create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f0f092cee32c29d95e9bd7c7c30e20f2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f194bc98e8e4096a6b45e7fb11d139b2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f1adc2c0c56dc66a742546fd7f916722 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f1b44223c5587e630f814a8dccd57813 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f1b6dc29404814c5b7b518731d3ad515 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f1ed9645499bb8f575b5dc55017f6482 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f1f8d1c3218c6362dcf0711b6e569ec6 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f21a3956f18d80fdec12b29b03404111 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f2b140d9118c0885e76b7bb1a5b232d0 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f2c30586d756f874aa3952a62be05345 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f341b63a04b60a2b5b7df9ad6450e9f2 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f349b233d747db9b4973592d72ba103e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f36a1f4d2ff411d7442cfccbeff9c92f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f4a545daab3a7bbc90c7313ce16cd200 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f4a6ba717bff574a5108716d3c1b76e8 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f54673683192fda4d1c05bc417825a4c create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f57e67551a8adca075901267474c5e02 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f7434453f5c9fefe400762eea3329b9f create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f86b00b21a16ae362e3e55e5cbbb49ad create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f88b647727d942b3ff67893febfa83ec create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f9047caa910fe889f68f301f00706b3b create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f9288a644472ee1e11539a71da2c16ac create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_f953c927cd6f250afb7d4bc8791a2506 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_fa32595e8f8e23ab874e9247eb036ec1 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_fabbd025bed38d86b6c790c3e2ae219e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_facf7bf4f85611dce0bc1077747eb283 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_fbd736474197b68a63d2aa66da52e238 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_fbf559b7fcb34e39382b7beb9e3a3052 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_fc283dec0f73891e83fc082f138c358e create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_fd3e050332ff331670681adaa7f3a04a create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_fe0b40307095880975721f2e56e653cb create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_fe92bea57dd460a4bde6b5a3b5b9f844 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_fedd27c97c990386b6b30a35d8da84dc create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ff4c8da7fd10a870809494bfdefa4cb9 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ffb15114fcaa6705fce00c84bef2acf8 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ffd5b0789f866679e1b3167ed8323759 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ffddcc106e6d3e46d1caa3c2dfddf4da create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ffdef08140c48ce0375a0a3d6313da12 create mode 100644 backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/sessions/sess_ffe009470987847df4966990ac35d661 create mode 100644 www/api.fahrschultermin.de/app/Http/Controllers/InstructorController.php create mode 100644 www/api.fahrschultermin.de/app/Http/Controllers/StudentAuthController.php create mode 100644 www/api.fahrschultermin.de/app/Http/Controllers/StudentController.php create mode 100644 www/api.fahrschultermin.de/app/Http/Controllers/TenantController.php create mode 100644 www/api.fahrschultermin.de/app/Http/Controllers/WorkTimesheetController.php create mode 100644 www/api.fahrschultermin.de/app/Repositories/WorkTimesheetRepository.php create mode 100644 www/api.fahrschultermin.de/debug.php create mode 100644 www/api.fahrschultermin.de/test_auth.php create mode 100644 www/api.fahrschultermin.de/test_full_dispatch.php create mode 100644 www/api.fahrschultermin.de/test_http_sim.php create mode 100644 www/api.fahrschultermin.de/test_json_input.php create mode 100644 www/api.fahrschultermin.de/test_minimal.php create mode 100644 www/api.fahrschultermin.de/test_password.php create mode 100644 www/api.fahrschultermin.de/test_request.php create mode 100644 www/debug_test.php create mode 100644 www/fahrschultermin.de/app/Repositories/AppointmentRequestRepository.php create mode 100644 www/fahrschultermin.de/app/Repositories/InstructorAvailabilityRepository.php create mode 100644 www/fahrschultermin.de/app/Repositories/InstructorBreakRuleRepository.php create mode 100644 www/fahrschultermin.de/app/Repositories/InstructorHomeToTenantRepository.php create mode 100644 www/fahrschultermin.de/app/Repositories/InstructorTenantRepository.php create mode 100644 www/fahrschultermin.de/app/Repositories/InstructorTravelTimeRepository.php create mode 100644 www/fahrschultermin.de/app/Repositories/InstructorVacationRepository.php create mode 100644 www/fahrschultermin.de/app/Repositories/PrivateAppointmentRepository.php create mode 100644 www/fahrschultermin.de/app/Repositories/StudentTenantRepository.php create mode 100644 www/fahrschultermin.de/app/Repositories/TenantRegistrationCodeRepository.php create mode 100644 www/fahrschultermin.de/database/migrations/015_lesson_type_allows_student_and_notes_private.sql create mode 100644 www/fahrschultermin.de/database/migrations/016_consolidate_lesson_types.sql create mode 100644 www/fahrschultermin.de/database/migrations/017_student_booking_schema.sql create mode 100644 www/fahrschultermin.de/public/zeiterfassung.html create mode 100644 www/fahrschultermin.de/storage/database/app.sqlite create mode 100644 www/fahrschultermin.de/storage/database/app.sqlite.bak-20260422203538 create mode 100644 www/fahrschultermin.de/storage/database/app.sqlite.bak-users-20260422204004 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0006a50819ddb4c83f8927e073726008 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_00309f86663d537153fcb66685d86df7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_00409744a4c7e3bb92d2eb4076f6f9f1 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_004433bebe95f3233eb3afeacee557d2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_01276da25030fe7610e51db01928f92e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_013cba357db97c86d9affd6f490425d2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_01db5cc5dc2af4df786929887036129e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_025b3db5cdc9307a49bbc4e7c8569322 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_02ce241714378b298d820865b9f207fa create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0302bb8051aea505d498b8118cf07cbc create mode 100644 www/fahrschultermin.de/storage/sessions/sess_033690960537713e4b7584067e851174 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0387d0c9c23e92d7c5da1f59219d140a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_03ab912d95e7096bd5041a815feaaac6 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_03bec560a62e6282f3c00ee39e0b5185 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_03f80785c72e82de6dbb20e633f8e70b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_04921a0b3787740b88c11ab3b2b4d4dc create mode 100644 www/fahrschultermin.de/storage/sessions/sess_04ba107751852addceb705f95cb01636 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_04e7eeca7ab45efc4714e55d0e488aab create mode 100644 www/fahrschultermin.de/storage/sessions/sess_04ec6601cf72642f743fb0ad41d466b8 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0521642416caa2fbc62e3f8298109cf1 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_052b45081be50758c250257b9a65d04a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_05fa0356f494ca589061fb1f1cda9f38 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_05fc405f00dd93a93a4e02d048ecfdef create mode 100644 www/fahrschultermin.de/storage/sessions/sess_05fe8c42f2d6ce2eb298f3773fe7aa46 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_06322d63f2b61ed856911309f0491b02 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_068031028f6af11d6206ba4e176e106f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_06b2450149fb978337c3b300c5177c89 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_06d7f7359a7c55810206e5722284344a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_076f1f9b3f3607d85628e676aa3deeef create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0781485879098048ec9ec26f838e230b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0854ff9e45ba1f5477fbbad5e54698f3 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_087a67b3949d5d39a90f231048dbc168 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_09013b1da99e4f816a92e860b645e65b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0979ad1bce9bd29c7f78266fb0db9496 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_09a34f83873ab7845a54ad0e1f123da2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0a1062c7493039579c5b1ccf819017cf create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0a3a77bb3b296f6ef7c2c58f223522dc create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0a457d212ce74a302f1245eaadd68550 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0a8407b10f0705fb94abc083ef7629a1 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0a918e1bf4f81d888710bb6eabccf5b7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0b02235f6c9d7a8fdd13403950def66a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0b4c3e7eeb7915687615efbcb7ad351a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0b689f1efcd64909309b788ce0aa77a8 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0b7a6dd656b413dfa91158a8f36533fe create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0bee18c4dceb5b1916bfb6d16f940237 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0c6b2d92ad51dc235c8a326a6f15bbde create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0cc19484bb603a199b713e1506f53ba0 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0cf9ec95094969104196ac2547eb3cc5 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0d444063fde0f76a4a254962ecc24900 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0d674f078cb072a267fab787102b842c create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0dca6c782e6359327b87294c5be46f45 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0df4eb5e781f33fdf8978650b309cd49 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0e04def9d162ce239e6947e6321ae8ee create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0e42f0bcb89c3342f5fd4f7440391e29 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0e5c1480078e1d9b5f84eba3b40bb77e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0e60def1012c7eb8dcd4e9c711f82bd1 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0e98d5ac2834ebc5bf78d2d5f0c91fea create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0e9b111cebcfc845371544ee02e6ba44 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0f07e83c587cd6ba9d4374bad4a0e050 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0f1d85f511393a35984d6532feb1861d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0f20d94dbfd397db961146e58523b752 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0f2589aadc148fbb95ddae15307b2ea9 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0fb288edf359a988efacf668fabed004 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_0ff28449277d7fe69998599d5c19d934 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_10009006bdabf68eb0fab5eb37ca2fe3 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_110b899ae4b4a0c037123345c4e49d27 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_117db80a15a6ba699f9678b99a1d9de7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1202777c449219fd2aac7b5ffb07de17 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1247d7fb0d48d3e756c4dffbc1270e2a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_124be71b45653d1b3f57538d487fa105 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_125bf65956128d9a6750548f818c3348 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_128a4fda8650abb78800f45754ad48be create mode 100644 www/fahrschultermin.de/storage/sessions/sess_12a821bed8de2d9944c9f8796684d638 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_14624d98ed0c363b2a095334c7a924c4 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_14b57fe7649feaf9706aea2773911e8a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1506f280114920a5be86dc1437738d5d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_15ff36ba1e9ccbb86875e7ffe82f935f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_165fb060e3ebc3fc704b851ac1d01598 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_16732beb713293cfc64b4a62d20e5cf0 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_16ef8072fdc04f0357bc1654ccd9b88b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_16f69df80eee2cd72267465123374bed create mode 100644 www/fahrschultermin.de/storage/sessions/sess_176074fcad8595788ae0cdc97b974c18 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_176897c620d6f055c1dbcb598905cc3b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1779168ab91c36713aafe5bf02bcc109 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1790c58801f9b2487b8c093cc82be8e9 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_17a0585e95ccd9c51a580839630dc92b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_180e9fa06a637da36cb1f76f4d0ccb9a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_18c8679e062e0a332aca916fe40c6859 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_18e0ea982f318fbb1005dfcda8a8bdf1 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_192aef27a1fce8903cf7d28379fa497c create mode 100644 www/fahrschultermin.de/storage/sessions/sess_192bccfcedba055ff11758985c9485f7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_194c7cd0b03e84efe00b5484ca2d8aac create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1950a2ef1291ec9df468535c6b0fc191 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_19770a921c71cfc42485c3ac4615611b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_19c0d2d4a229fd60d2099992ea8690a0 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_19fa167361a1d43b543976961054333c create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1a912668b674f48b5ffc7de9e824aa5e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1aee1ec32f6fc52fcde579475a6fdf90 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1af06631c6a9c42ffe0961f03c21c73b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1b62f79b6a161a44066e889fb5890864 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1bc34aea1212c788f139097c26f6a232 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1bd8426e10991ec9c1856c793196522a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1c19ba48c4d66fb010544f9777b0b4f7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1c1eb9653903a3f2ca0cd68b2e2a31c8 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1c1f7b1efd31a1165e99abede59cbc0a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1c8fd85f2d4fa6c41130f08f87b3068d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1daa8c750fa07697f55057eba9f07208 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1dfacd171678bbfd3713e55ee6e1c0b0 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1e0fe014daf164ddf58fba633fb433ad create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1e19408096523f324050696e40e78c2c create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1e3b112ac09b1917473e8d5d1f050a13 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1e3fa8cca3f53f006f75140261d3a5d5 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1e4606b18533461a9fcd557a11b1339d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1e576ef7adf606c5bdcb7341f2dfc7ee create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1e607b0b5e8b9b44045b78c038ebd342 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1e752925b000debefcf8e1b6f462bfc2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1ec360e5d18fce3b53084f54c50523f6 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1f3d92dbc703b68c7e795e5f51c1dc19 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1f6c34e49bd50f27424f86a2c7f0b2a2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1fb01a63321004f84edde1c2f01b9660 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_1fb1ae525d180aa6cb59e558984c2072 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2041982bc34bf965174b00a51558b7e0 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2043674c95a6ab2d3f71332b76dda4ce create mode 100644 www/fahrschultermin.de/storage/sessions/sess_204d8a56472208a59a1aa70ab737fe89 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2123c53e3d0044bd7f2e5cef59a41837 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_21b513631be5e9c0dfd1b2d517f84398 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_21d59f9f57be2f40d95c6857ba79b486 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_21e0c4efd285609f6d2a1f54f58971ff create mode 100644 www/fahrschultermin.de/storage/sessions/sess_220bdce3ff2cfd02d7e5fd4851774fdb create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2230676d13498deef61f7ed59dcdeca3 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2292fab487ebf06df95e0036beac57f1 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_22a4e3c7055808fd4c5a2963e52ffa36 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_22af559095a190687e0d544b8eee6644 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_22d9645a0764cd276e1010062ce58708 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_22f0a00861d4da216089fde3b14fe2ef create mode 100644 www/fahrschultermin.de/storage/sessions/sess_23a4409db0925d7a59ad29e42bc4884f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_24390c9968cd8821cd7bc5f2aca39df9 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2456d2263cd6a8256041d4b3c89ff307 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2470d1af939f31518959c76496aa7a6d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_248cfaa4e75cbdb490cfe8dce5aeaedc create mode 100644 www/fahrschultermin.de/storage/sessions/sess_24a867c9d2c15b255232a9d8e1e84f38 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_24e2550cf6651e9d8a37e82da464ce5d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2503f4a5cbae9324cbf3b95b5b97df27 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_254c55b9ab44a7e81392a361205f9608 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_255b248494b7bc41838d5e03d8c63f2a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2576dfcf73df11914c84eea9030362ee create mode 100644 www/fahrschultermin.de/storage/sessions/sess_258ba55b7396886da889d6c57b622370 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_258d74a6647426ebba4ce0f0a0c67c66 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_25bc952c7944ac5478e505b7d07617b8 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_25bfb2e99746cac127855aca7dd51a66 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2619421cbb71439a217328646badf18b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_264744b8536d1f0a4942fd4884126ad1 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_26475586b26e819cbe7ed6b7c91f4353 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_26817db6139724550add44986d13955b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2694eee41fc1488b7bb8cade34db7095 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_271efa4ff2d546b1e0a65b6f3ddaf929 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_272592a07264fcde1b184f03d1b63a3d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_279218f8e2c26131e4ca39dbe14eb7fe create mode 100644 www/fahrschultermin.de/storage/sessions/sess_27dd196fe7ad686655daf28dbc6ebd34 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_28136e39aa3fb81f6ea47823e5cff14e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2878c22f67e22d014964d1bc29fc80d2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2941e7e905f15d5da543295c53736cb2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2960b8132f30396e838b0ebdbb96dcaf create mode 100644 www/fahrschultermin.de/storage/sessions/sess_29a45c491f6998a47aa0b0f66fd83f3a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_29ccade580d9dad87ac1a35ff275d2d0 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2a16d5f4beece112f2e67a4bcb781a07 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2a64bf1990c00f2b2bc1836c1b96fd2f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2ae5a56ff11d55dbcf879524b71f88c9 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2b67b6a92be7fcd363c23a7e494057dc create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2c5719342be78ab42c03ddf812474077 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2ce24f0bd9ec5909e465b42fa8d33670 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2d1c750f56256eb9ba589168d9b17b65 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2d1cba190840b33e6d54f9befd89ae52 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2eb1f32411558996d87b5d8567dcd94a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2f4233260393ed2a31e00ac9e2969636 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2f52d9fd84045a93ce4bb3a5be6a31e0 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2fb5535ba46f2edcc64f6b7c581b9c7f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2fb9f36e4449aa120905dde208f60510 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_2fe3e2083c13b197a53c094156873f5e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_309ebde30dd24884b70bd8df07279749 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_311959d4bc3d190ae2499291e9b42220 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_31210c92118f237f856a58f569a84141 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_3160a2a2b74ee599bee5f62ac40e1591 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_31eaca9ab96ee1d5a814bf3202eb3c22 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_3211e64538a844841fd3167dd09dbef7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_3212a8af9dd17c466ab8ea91052c3cc4 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_32932fd7d38fd6c8279e60d44e48e922 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_32c66a45bdf19b9f50de688ea047e696 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_334e686c0876c391c42fb587b3bfa976 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_338deba0ad100d079f68ea413a6e3048 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_345384363d30f764d4244e131c21d36d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_354431ef347b5ba5cfcc7273061da04e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_35a3681412ab8e77fc498a7699733368 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_35abdcd2565ba6cebc1d5323c51976ce create mode 100644 www/fahrschultermin.de/storage/sessions/sess_36323cbf08bff404511acc133ecddf9b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_3689a40959fc96cd6f3c50abc1203ff7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_37282ed122e7b4eaaff47a3883309651 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_379e5c9dcce57030269caa935f44b608 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_382329b5050c26c18ecedab29e115c9e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_386bd2888cf16751430e80917a8f85fd create mode 100644 www/fahrschultermin.de/storage/sessions/sess_387a266ab74eed24c6346816390f7769 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_38f8f88f7abb27ac17d9375a5a4cba31 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_3bcfeced08df9ceb2af9241e3e4204e5 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_3c6086415f4fcb8af3e7af3207b96d8f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_3c6c3f9430ffcd8fda0687736ec2d718 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_3c7b2b014f20046f58b831fd0ad34c9f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_3d6993df0f462c4ffff7556ec530e435 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_3da2ba55a8c5d696a5814b42dc0b2e14 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_3db0a0533c6ffd6078c55906988a594b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_3dd3b5c1c41191d551256202b307b743 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_3df20e7c93a01e37ead25a82abcfa7d8 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_3e081a2d79c698f00a94dff6db2e962f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_3e0baa6f9f93aef56b4c28cbe7f0892a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_3e34570e03450813b46f65a5332d9c1e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_3eba8aa5bdff2f16073b6b03a879f828 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_3f4d92dfc22756dc3491753c3710d58d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_3f5d80074117dcf2345eb24489088752 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_3f7f9ebea56914012835d097cf04ca92 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_3fae57c4628e0e6de8700f2aefb0b86a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_3fde18821c79b139e6cb19e20b72a0d0 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_407b46f6532a47e17ec5babe6c30bc4f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_409dcbc73298f3e020b7c60a3432fde7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_40f2a96e744926f0865a52dcbac9a160 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_412a5eb5d0597f918c0bb9bab03b2faf create mode 100644 www/fahrschultermin.de/storage/sessions/sess_413faf6f188d744d9522dd2fcf41495f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_415f109c0c4fe4d2a1d4979ad5d9bb24 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4181fda8edd2d12b37f109fad33ac6a7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_420cdf45b5c2330160d506a70eadb33c create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4247a0c7906fc90ae18a64adaf1aee2c create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4254feb9a5b1a5b349f5dc3556d1a201 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4338f754256fd9a05ffce8620f13f07b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4368d3d721b713fe8f89cfbb6e1005dd create mode 100644 www/fahrschultermin.de/storage/sessions/sess_44b7c1306ee68cfa7a89fbc4e043209a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_456a3e7bbad68012698a87d1072c1b38 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4587b7a5e1e5ef217166c56763ce423f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_45a98e9ab472b14179dd14db5f14bbff create mode 100644 www/fahrschultermin.de/storage/sessions/sess_469e07211c9f0f2764b6d7a2c851ee46 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_46e2408207b1c95658fb7b626ee6958e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_46e8ebf399968a345980d5a95e8c06ff create mode 100644 www/fahrschultermin.de/storage/sessions/sess_47b05bb74c2af2828b06cbf5e3ff8477 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_47bf2f6a7024254fa8971c7a85b0910d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4853a954dcf6e2ad4cfa31535490e110 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_497ce6e38a9c5c1015e856f7589548ad create mode 100644 www/fahrschultermin.de/storage/sessions/sess_49a86631804a0d6da2aee719475f739a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_49c66d8f15ac3c3053c8534b28fd2ef0 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4a45962e93cea2ed73e1665936bc73a3 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4a6501b0989d99b4dab93189ce30fad6 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4a8e405b702f60027e17800b0a9ef45b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4c9a1aaceaab78fddc55765178473d79 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4caafb58e3487c63db3e767199e558f2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4ce44d9d6463335e836ae3de308d4b34 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4cfbbd26846fb01c6f9d339c557b03c8 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4d29d6096a285987a2b2e625068ff5ed create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4d7c6193ca1cb655e8d82dfe9c6c7eb4 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4e38e1eb4f49de672fed6ed540681642 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4e71c4ae079c79d06b3b4199b37c8d6e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4e8ab1cc7be41320be57403a5764d80a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4f3604d0593e4bad5b3c1a2ee7fbcbe0 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4f758a3b77fb168967f90b28555033d0 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4f9fd9991f5f3d30dc0ac97c372b731c create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4fb189b01ba2f06e00ef5f9b5a7f23da create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4fbfeca9a5fb8855a221e690ef502b38 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4fc591afe53506325ffb61b8bb9c88fb create mode 100644 www/fahrschultermin.de/storage/sessions/sess_4fc8717e82c041e7287a910c435a73fd create mode 100644 www/fahrschultermin.de/storage/sessions/sess_509426117c6882aa1596380c7cf47a87 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_50f11f6d3fd52e9b97404943e55c90cc create mode 100644 www/fahrschultermin.de/storage/sessions/sess_5104ac2b3efed41e0167e8ac230efad4 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_510691ebd56ac6658a8f0d05def56656 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_519f45ae9f86f930f3abdcb63300057f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_51e0aff602241718c4b8416f452b580b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_51e0b1db7e62b4fdcdd8306d0c23943d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_5283908e9b80eb47d1f52f4d4400605d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_5294cb940dc83bcbd4bf57b17a00ba3f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_5341c2fcecad979f5311ceac1bd28718 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_53926f2b0c401a2668cdb0ee55739e71 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_54e9093941ef282786b14c14e94470eb create mode 100644 www/fahrschultermin.de/storage/sessions/sess_54f3fb06c0aca2a55c2dd5ec4d25512c create mode 100644 www/fahrschultermin.de/storage/sessions/sess_55398030f9196b2640c7bad95056c2a5 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_55b079046d294c1c69f287fd4337d04b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_55e94cdd1ed910c96f22fc4e1f73c0bd create mode 100644 www/fahrschultermin.de/storage/sessions/sess_56355cbfc04402cf36d92f821bae5090 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_564322bb4ff156306233922d7d4fb345 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_574d1c7a48adf43155170876b326b4aa create mode 100644 www/fahrschultermin.de/storage/sessions/sess_57744595846324be63e78c3aa28a4f33 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_5775e29dec1dea074f4fc9c32fa3a34b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_57e47e62da11c1399a779afd1e78b75e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_583371d8396ad02e05cf1ed99be48719 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_587bf41af67b4725cf167a5652622640 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_5971640cd75544c110ed69c8b2704b38 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_59d0ba86b7085a23b070b559be815e6d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_59d76f244bb8bfebfc4b923adbc15419 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_59df6b8eff37a8f46f806ef70d53862d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_5a4ae2edb0253f99e7e0d768060c5e83 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_5afc92d91483488a5e2756611359c02a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_5b9ebb48cacc40324a7627ed5cd03ea3 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_5baa5d3d23563e43afa7446486dd72f8 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_5c0025a4a3bc7edc09d828efe2b28cac create mode 100644 www/fahrschultermin.de/storage/sessions/sess_5c0f0b21134060b451e12ef30081f540 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_5c2d18b62ab5f4e62bf93bf01d6a8abc create mode 100644 www/fahrschultermin.de/storage/sessions/sess_5c49211f43aa46d12999628ab1a4bd28 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_5c572f6972ef0bbc0fdb158726375a0f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_5d5cd460d1d5835451d781e05c3adf89 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_5da65e8529c7df8023ed18faa35d5a9d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_5e286521edc4303f85116330fe10e8a7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_5e7a74bf5069e74d295e89a325872261 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_5eaa995db9fb15a416a357e533b6b131 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_5ecbe991d413b362c14567f1f073f4cd create mode 100644 www/fahrschultermin.de/storage/sessions/sess_60668ea8e3890507478e420a56ddbd5f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_6143815503ebf3f82c544d21c1b7ad5b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_61b414feb765617dd3fb38f7eceffcfe create mode 100644 www/fahrschultermin.de/storage/sessions/sess_61d6228ae940365ff8bbd93a87303a02 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_61e9d1453030accdf287a54e918ee927 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_61f558ea9155fbeba01a42a2931d7b7f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_621511f8d7c30ebc5aa01ce455b8ebaf create mode 100644 www/fahrschultermin.de/storage/sessions/sess_621ef44ae59c047ff712c18d050011e5 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_6299dfcc8ce40cafed3a335a341a6336 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_629f9dd62d1204aba62dfefe65fb0d74 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_62b5d304a31805cce616357f0e59a9f2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_63a233987c509e08fae79917349f9bb3 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_63e9364f0c944608a17504f9b91784b3 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_63e9c9819a2de781d792f1d2a60851ba create mode 100644 www/fahrschultermin.de/storage/sessions/sess_6477fc14a025865e68f2dea333bebea8 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_64ff70ad1229278f497281f0214ccef0 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_65468453a39a8a4dfaa1082215ca9836 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_65b339d0b1ab7066ca6b44ecd8facc40 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_666d42c52b9ccca9698401d63202cbbf create mode 100644 www/fahrschultermin.de/storage/sessions/sess_66f307a1423bb488cc26a24cc5c4a42a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_6719a214f44edb74418e6e890bf5ac08 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_676be8f36a7e54aa1863917c871eb567 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_6779e0e89629bc58388284b76bd76192 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_67c0a94fd2af88c6fe4e5fcd34036c36 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_67d11c2d90f54a43e96d16bf3c7c45ce create mode 100644 www/fahrschultermin.de/storage/sessions/sess_685d40735c321a9ffb35d557798cb1b5 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_686f0c41b1eaaa6c33131bff43058b77 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_68be2f0cedefb60f859f4c4d9c2a880f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_6908fafa1345473eb21f5cb9dcb153e6 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_692cbc36b4318bfe850fc4aede3832db create mode 100644 www/fahrschultermin.de/storage/sessions/sess_6972dfd4b41c386eeb36cb9140582224 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_69c6be375919a05a7948264c4db4e9dc create mode 100644 www/fahrschultermin.de/storage/sessions/sess_69dea144c3340502072b895a59b41b24 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_6a4b12a9d81b4f56fdab7814f88b59bd create mode 100644 www/fahrschultermin.de/storage/sessions/sess_6adef316d6f11d1cfcb4765ee289aa95 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_6b3dc5917d807ae794a1ed28fc9e99f5 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_6b45e848464ad9143282b3909897ee8b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_6b8aea5fa4705e2c8928d1ec20387164 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_6bf7d9cc4cb88d9fe96dafc8ed1288a9 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_6c0ab8e461aebf50d0ed2a344176ff8d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_6c601c0358a1d9724a5252cd756bd64b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_6c676b2e4c18eff505c1853258b039cc create mode 100644 www/fahrschultermin.de/storage/sessions/sess_6d894c43c030d8dbe071de186700c5c0 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_6da8ac394f5fd3b79b6eb9681f810c15 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_6e5401ff80e23d4dbf547c4a894fc211 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_6f3a5c611d7a16c43ca25127894ed8f0 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_6f84b5c8b742313a8b748867bb5dfbc4 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_6f98287cfdd3ff93e952baba05fd5c31 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_6fd95d60ca7ff94f3c602ff4753e6a87 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_700631b2fa80717f2d07a6017d5ff030 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_700aaf47edb8ff558be85c5bd9ff529e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_70a66109b64b26d0ac67524166cb72cd create mode 100644 www/fahrschultermin.de/storage/sessions/sess_70c642898f8f0725b81e4d285eec0787 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_70ddcec0c3417113fc4c072fa39fe331 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_70f9c044b76efda9274b32b5ae345d56 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7108ed71a1a0054c7ab7c7c2068e8923 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_71187da2021a77805c96d85b76867d92 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_712f6993fea7e24e5126734f0aca9a68 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_71a643c2efaef2d77620f637e5637cf2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_71d766001d452c1d949ce85cd327574c create mode 100644 www/fahrschultermin.de/storage/sessions/sess_720f14d25a4f2ce52fe9d3933e014771 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_72515b3f3d38bd85e703ef3a80b1bfa9 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_726860093a0a7908e0e9f66e709ef6b5 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_72ab8f6fd0fecbe3e0976c623333be7b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_72d5d6b9df6a55503533e97e340d2311 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_72d7576d9bfaea5245d54bc6f5ed732a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_72f0fc1c0ead3468d75f171ff2353eac create mode 100644 www/fahrschultermin.de/storage/sessions/sess_72f8a1f9caa69b25fa64c6185238c6e7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7316664ff3b366d41d1a0f1d29a21e44 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7327bbf8e70879c93cb9fcf18287ef57 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_73372d764d0b64494a123b95b092e293 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_73c80e71dcc72c4c3bd422dc2d1c6f22 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_73fd9c9fd27ea5b4b7e513723fbe0d01 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7401b0da36a0bf586a412bdc32c01932 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_74169a80690859946b251a7e2789c06f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7433d5d741f0c55568cb183264a757d7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_755543053755f9827042ecd578a32ce3 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_757ed514e8bcf21fe484abe2639fd9c8 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_75ed4967153522837eebaba2104a96bf create mode 100644 www/fahrschultermin.de/storage/sessions/sess_76036580c2a0124ef45fab001e1b8e85 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_764eb3d09d7acdadf02a1b1b9e652d38 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7751d4611abdb82cdd8307b7db2a94a7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_777222d786447c44cba1f61c40722851 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_78ade15b28ae2ac4544641f92908c944 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_790eb3970d7871971226a752514c9021 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7a39b110e92a44f5cf2cc27318fa8acf create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7a49c9790485b15f032f1889edfb5e74 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7a758afdfa5c19238907ee69e4f1b8e1 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7a942e09063d7d053ece5084917a59c6 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7adb1835b69eb442280acb1a34e3e4fc create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7b1b6e128d0bc1b40a30954812f3f2bc create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7b30a67240cb34b565faab1ee1a43759 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7b3b609d4c8713d06961731c28fdfd8e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7bb08b912f4e650b85412aefe4225981 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7bb50e18de85135463e20f8d52dbd679 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7c34f05d08306cd0e725b262006af9c3 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7c4815b8c0d9564d5c330dc755b89be4 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7c5d6ce984c7f26e83b67c6a785f2919 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7cee3f52567d4d995de6c242576e80e6 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7d496df6ac57de8fe4c46d48271fd6b6 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7e2cde5aa2397f9c928af07c857d9059 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7e394c0ae018145fbf007d66dae56fc1 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7e44e9b5e4618ac9c5d5c50156aa9481 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7eb154203930535a374d572b26905543 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7ebd209f80c9a78715a398ca29339183 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7ecdf19414bb4c4fea4ca19850a7a539 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7eef0ffbe7eb689711c83038742d15c7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7f11e5dde38e860a69e208aaa622312b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7f97b81d9a078302523e924f6cd12861 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7faaa1325462a16eb0c09a78eb816c3c create mode 100644 www/fahrschultermin.de/storage/sessions/sess_7fab50507d10a6d85e79235af15d32d2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_802f12cc03180b848c62d75314cea4ad create mode 100644 www/fahrschultermin.de/storage/sessions/sess_80ddf16c4abf6269af3663c2773029e3 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_81367cb6c7667b5ce00936f7e4c75ed5 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_821cee680ccebd68e213681a79f95074 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_82c9e9a8ca48d231b1a6143016f12161 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8390ca0087f926e6dc6acfc1726feac3 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_83f1a287583b2bc46cd195e1e3b2f943 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_84323f8c4143d694e44c9fedcb9f9df8 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8506d9ad640e8db4c5464ae18b21aeaa create mode 100644 www/fahrschultermin.de/storage/sessions/sess_854a85d95cf30e598d3dd8071c524182 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8556739092f403601805097e7c5515d7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8578f708494846a3ded7c61783b1424d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_85bde53b19790499e9dd46a1728f2644 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8609c314bca4daf7b8d418593296f29d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8631135c2359b355b28b12b251d7cf20 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_869e70a50cdab0e5dfa7f41decbe164e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_86b6cee8d0e002bd2cde78c01432c98b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_87765df5421b4b08ce0285280372e9bd create mode 100644 www/fahrschultermin.de/storage/sessions/sess_889d219494675742314ef1f2307e5059 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_88af4a1e93188192b41b898c96537666 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_88b7f3ed2893c08c3d3a90674de9828f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_88e7499f47401ce614b4b65ac8356131 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_891ef4846f2cdff9342aeba0a22d5f98 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_892e5cf319f4e18afdb304abb39c54e4 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_89625043f67d25fa8715915de6bb79c5 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_89a3b9f54d67342368079a0ac616276e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_89d0ffe96d3b0ad881121413908b1b83 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8a6817782ff0d92875e6235d8ad776fc create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8aa40af232be8a3ca5282ef5f551afde create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8ad98e7026500f40515eae94a2fde2ea create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8b186147e95653d8a3b6c748d2482dcc create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8b63e5d2df259415a70db075ccaee244 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8b8a7cdf7636a6d2aedba08f756d3c8d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8b99977c6b9caff09ba87062b2bccadb create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8c02e46467ce79c739e0a68444178b2b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8c6134b631065799b0ec7bee4049c4ab create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8c7302f6a643e4dee153b7d114ed8783 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8c909900d173078a178b390295c8d241 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8d2904d8920390de86ee53bd52ff2f95 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8d3b9af6665ae182d39cfecb43e8c67c create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8d6b378636ae0e195107452f07a9a1b6 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8e2e34092cc5092b33f80789bd8cb5dd create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8eb44802c4c08e84235edd0bf26ac87d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8ef6f0dee24d8b4ca5d6d12fc78846d3 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8f45c8b37f65f5a3d96d99bbc42afd6a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8f59107da2dd4018eaaf396b9d4a5a36 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8f7dbcbed4677531f01a98f09b78e579 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_8fe1ec9a1ace4c1e42a2f556662301f2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9064761135752f81f7ad8b6ebd661023 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_907c362f4f2f6e400df8ac189f9f1eb1 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9087759ac2d7809daa14034e92904b12 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_90b94e25c780902f5f539c288bfcf537 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_90ede0d038bcbb6aceea8fbde52f2641 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_90fe76e7383355fe08eb307219e2d816 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9198bcfeff6d315cf4fafa6370065a03 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_92c58ef6f3c99152d2ba186841a33c7b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_92efa0c840cc52c16596b78a5df32412 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_93f7f0d85f2643a76ef136e26da9e002 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_945189d26d0ba3469d510a86ca265d85 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_947481d841466c3a8daac3ad85aa115a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_947988622ee7c2525e6cd5d894c98ef1 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_948b86a8e2a1e31167679e4a1470c860 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_94e7ec5946942ead16448a728abcdc37 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_953a407fb72605ba0a9a187e23cfe770 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_959d4c4a242dcfeb97c61a3ea2412504 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_95f18066efe5fc4f9cc8d0593c8439bd create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9644fd126a3afc481ee73045a503baa4 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_967b281af59e4a6dcf1cc1fbc3265a9f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_975c0b932ae0b2f7e84907a50b8ef1b1 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_97f50b3d8687d8d47bd563960e1ff455 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_98892de18bd4ec4aebcc5bee99ab94ac create mode 100644 www/fahrschultermin.de/storage/sessions/sess_98bf4125aefc7ddc22a20c48a9619989 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_98d9a575de9d3f13469f22cccc63825b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_98ea1635d552d9a6892dd0ef71435f1d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9959bb4c68666277b4b2c0282a90487f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_995dec3fc6c2f46d050ebb554f56cd66 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_99ad47b9cdcc23e23f1e7795dab1738a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9a0e601461b3d9b104215919d000998b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9a7db4f72bf6ba3b89119eb1577e6915 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9a81f6a69cf9a752f08a42af7c04dd7e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9b0faa79ebcc182ad631d2427d6ccc41 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9b9ffd3f5d1b3b7e7fb4ab8021ee6724 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9c87061c647a1863348a102fda0a485b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9c98903028ece4cf33487e12338563b0 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9cc05cc46ce95864ad3e87c922bd7d6e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9cc7d48622827c73b5c2800b90da6c71 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9cfbca439e6352c64927f83dfba56d33 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9d0f8c2d99f91d53540c981a669307b0 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9d585af32abcbce5c19f7dc88013b509 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9d95d6e5b127e827982ebd70a7780ac5 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9defc0b6e6f8e6a06175fd732c55f9f4 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9e0ac6eb04ae0415245380aff8a7fe18 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9e48c6c6d01f1fd30c8ebe200c54ae74 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9ea742913c11d08c3a459e7cbdc48ea7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9eaed2cf9bc04a09871029820a0febdf create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9f24d1e11f4da71f33ba80b598bf4635 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9f669749877accc7939a55a65470df73 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9fd8760c7cb04dc202cb2fcbd65fa171 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9fddcfde761802254047b04993e2653a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9fee4660d995f9fd6a5586af67f086ea create mode 100644 www/fahrschultermin.de/storage/sessions/sess_9ff67287131509f45cc8628249181573 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a04f945480a6c46840307cfd5cb133c7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a080edfb79e15193b1a986d2bd1c8fa4 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a0cf773a2425baa5763a3c5151c1e6a3 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a0ece2f1244996e6289cf22fbe69523b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a14487d530470e659ef4624121d3c2c1 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a267492beef8994b2bea7a841d1a0cd8 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a285d45c3472af1ffc36a684c856ec99 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a35bdd52888e241d85684a6d02a68bde create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a3a3b332d752eebbfaa33f846f2011e8 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a3d22e835cba5db3a924784879c04be4 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a407d22c03234511a8692574b1afdb55 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a40a10b60dfb97885a13a60cd56ab03d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a41afc440e071af84d3aa05ac5272fc3 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a4599efbaa95f71a3a07ef11cbdb2982 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a50197cb3ff817a6e011f450bfc33d48 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a50e65f017e7c264acc21fba0639a32a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a559140c7253e91fcd6a8f6f19e05bff create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a594961a601d02783f91faf40e98312c create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a5e3f86f5e484f1b76040bba1e5e14a8 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a6ad3eb821ff648a929de3ed3197bcb2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a7cb6058536c2c10eb132b9ea2273034 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a7fa2f402ec1a091b24e4dbe172fb7ce create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a80186e2242223f495ecfdcca4153e08 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a81980865d0ec43ef0c5583e07965859 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a87aa73edef2fa7a6931806265cea0c9 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a894ac582bee396f28b4a5b6d76632d1 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a8eed80b4e6c374bce30583a334fd1be create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a8f1dd35649e9f13895d193df5dcb5ba create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a9196e31df6affc38cd0927354660e20 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a95b3114ad98063dd3ba2e3ff6ee4089 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a9641cbe82cd2fba425d8bf3f1fa632b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_a9dc429cc9b5c625c3b61d1647124d00 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_aa0e7054e4831c8e95c9d968a25ca3c4 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_aa6859d5e79333e0b7fbf8152c956007 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_aa8f8b163bab75cba3311559cd7cc687 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ab4e2933297bec79168036dc7f3d1c4c create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ab893deedb9409a65c6b4c2e1fd21135 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ab89791d24d1aaadefc456a1086a2560 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_abb03b95526f02aafe816672af681ebc create mode 100644 www/fahrschultermin.de/storage/sessions/sess_abb7a002d65efea430cb0af30b78a078 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ac0e42e33c97dd59d436c667a5b3eead create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ac4027a844896a8a22c308c293a15f65 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_acab22928f8fdc15eb012b3494e41797 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ad5c7d07120b55ddac12237c4bab0e2f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ada72e41e49e00d5f656e5da43eb6bc6 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ada8c2049e1c9327b3a50dc425f6bfbe create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ae1d8a22efd12912343d561876fdfdc5 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ae234126326f18ee35485c944b7e5848 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ae34a72b4fa6668803ea46d383ea6f24 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ae52f8b68722f536aa70b7590035f9e3 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ae64540044c09c7b795ced78505c9b4d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ae70ef607969820ee6fa532c7c516c0b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_aeafd646edb77412e928721505f984c7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_aeb687357e6f692b2199e9e800d56673 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_aefb95455125a664bb95a1c2ef06f8ad create mode 100644 www/fahrschultermin.de/storage/sessions/sess_af4792ba9e683db497c8912f32609da9 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_afab05d39b52ab8998daa2eda66b6aee create mode 100644 www/fahrschultermin.de/storage/sessions/sess_afeb67ef6a5ce209e756766ec3ffe2ce create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b024fc7d8e3356fcadfeeeb2d14b1815 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b0c1002f2db893b0a6af43e0b577dfe3 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b11cbd54af2f7c7e43bcc82b0279e10b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b17c2592ffc464b4ba0b679af70ded2b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b1929d13ac18473aeea275a695808d5c create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b1da207d6c31fc9946ba3621f021ae80 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b237e22685289229a1db2608891cd30c create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b2a31fa222e71d201828baf1ecf324d2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b2eaddc565cbb2b2e87616e79caeeffa create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b359782f5ccdab7e806fbeddf150dcbc create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b36f9ea866728a5fd33680170aa21c86 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b3a6ab8bfc108f99d05e83799ba0c606 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b3b8e7ee9034bc9159f2b56875fd7cc8 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b41c95ac09de774cf44fb1e10f5e904d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b44275f8a8435fbf5025d135126479b1 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b491790afd14563692ea36be5588eabb create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b49f8d76aa16a1f4aa34d99f4cc1e1b4 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b4dbf3766611612508d08f46c9b190f2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b4ea676cb3216a36a1693463ef5b7c82 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b529a32f36544fee5e632be00fac097c create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b53d30d81ffaadae86b647f47f6a1160 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b53d83093d2c2083ed7132b123982264 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b5510f652a52854c7651f6918feb977e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b57a0d89a948fe34f8dacda9cb43bae4 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b58721ac9be6edd4764bf6f8780cefc1 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b5a60f9daa55f9734f493d6ac0458573 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b6ec78ab609d0df2fd508e9163c253e7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b6f50ca4efe83c023d25e4ebaf6487db create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b79d07314aef27e7f198bbb8b140a36a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b820236352b2730de43cf836422e206e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b84c49ccaa5176f3f60d430f254f4009 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b908569ab6c58bd1746f5f4fd534f934 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b91a01b1e5d207e45e16934c8ac97ee8 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_b9c58c1055898aec49f2b876bee0989a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_bac0d94744e5d5ef89296100c18f0090 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_bac2cfc9597d42d210a3862582579fa8 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_bb4a64bd891cc061c3adefcb44477af4 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_bb87f20b1e8a384d46d56453cad1e43c create mode 100644 www/fahrschultermin.de/storage/sessions/sess_bc2eeb565a25074bbb1662c9574c76e6 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_bc471f7dacb30aaac0280f97b8b2a40a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_bc6a3acc6bd9424ccd4f53381763a765 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_bc9a6fcb99473c1d425a69fc74e2816b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_bcba56fadcb6d5fa33aecbc0accdf024 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_bd4a221108dfce794cd7f70d523dd168 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_bdb7dda4a0ef116d409e5e69c1ac3fad create mode 100644 www/fahrschultermin.de/storage/sessions/sess_bdec0ef240e9a5bb1a666d8984758976 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_be317991eb08458fb8e9abcf374bed36 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_bf428a4caf8a7d4c47d26f32e16fe043 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_bfe6ee4113b5571017455a38ee1e2317 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c018a7218b523ecc2ade22f7aedd38f1 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c064150f27b1dab4111a60ccc6f9d2fa create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c0ed531b0f4e24c41d874a91cba8c32e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c1f34ca67059e96e6bcd732eea2446bf create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c1fe8bbe901ba4706855bd9eb723ba70 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c21b23debb7c016fc702f87a0294f3e8 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c2426b9d31ca734eabd4e282665bcb9d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c26296dc097c2f01694ee80ae77eb6e9 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c2ee1217613c2a80e949b8716990ca88 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c38c51006dab56733498c50180b3fabb create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c3a730d7b6a59d913d50e31df3e4c3a5 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c40e44707ce35c5ef27e8802aa39e47f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c441f7bf34ccd6ae14805365c2ab89c2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c45b5e61c6d053c4f763d91407f3c91a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c475fb1bab33463d842d886993c1571f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c47945969b61a07537f19f946a384741 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c48155c30b39c8362f86aef75c221163 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c542af71129f343b6408eade9270bd54 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c54748085a5be98912b747457ee83e87 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c54891068be346717c001cd9135f2f1a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c57f6f9decec3156c65ae42ec51d39d2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c621bb0f553660ec429aec2ca0f43023 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c6429f27fce95541dab9e8ed4952d4bf create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c6bf258aae59585a0e4d3baa156de525 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c6cc348dc8a6b160339d4efeb4ad5a73 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c70b845bd7ace83384142056d6460d23 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c721d5b6b93058bc391ed27a94c9b40f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c75f86e937db953edf0dc710594a1a34 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c78586d22fded1470a8af498186b8e7a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c845a8b285996a7f190591807a2e5da3 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c84f8233f589f47a1d1f3c95256a6d54 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_c8c9fc84e7bd6bb28854c759734a4aaf create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ca26d03afac9fb59c49d2459b766b0e2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_cb10ef5334885e0f4bb07e60b95e4bea create mode 100644 www/fahrschultermin.de/storage/sessions/sess_cb810d18223327abb72bbbce488255a2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_cc6ca9b9be103498b3b3f9bebbfcca8e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_cc7cc7d4d0e038443bc7f215fcf60584 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ccbfa7c487f33d217038668dba1c7f98 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_cd426fff8fdfa8c52522fb101ed1c200 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_cd6936d236e56a0958f326724caa9f66 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_cd9a052a6c053712db213bcbd569b448 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_cdb2d2c089e55ec49e126ffaee9da500 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_cde83bbf5f7883231a8bcefdf4b5749f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_cdf9116287630763b1acbe585ed5e0a2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ce5934a087e64591d7f2d218c00ea365 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ce8a8426d076db8a3d564204d4e29b24 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_cea4f34bc0b31c76efbc1ff1a6dd88bb create mode 100644 www/fahrschultermin.de/storage/sessions/sess_cf7075fc4f5727c449d2b7fa06303609 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d0054e42522dc3082a38d55702832585 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d053186c866c8aaafb3c49f5fed373fb create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d082df7384cf4c3855e911af2e4a6d68 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d1b6c144193e51c481f30adaa6f5ddb3 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d1d621d535d1444562ca2d1b7a43400a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d256ce4f2ded89a00bcf19cbcb1bed64 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d27d2227ad5637ccbac3579310e60b6d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d311a3d90755123ef2e6e86c68eefcf3 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d35e6e95a5fdda69f32a5a8bdf8964ac create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d36cf7d1753dc1ef650e4ad031194f3b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d37c9bba5a42b3760ac4fedfe02002ce create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d438212c8192544d26843d87bfda1e5b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d44df14d2838d90e63d1c5ffeee18b63 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d48edd5255a24ae738e3a13c31f7bdb2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d51b539ad255419ea6fd83ba3e9caa39 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d5ee3033b6819cfd67d2ef4d8feadeae create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d6173d6126d730e93fa9cd6774f53be7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d655d61f4acc04d61814576220dda1a4 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d761d9849d639d0449a853e9965d2558 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d764f7b5069d52949a1249384c2bff40 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d82cdb26dd412905a1b72ed027a14fe7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d866f688079ed7f6798f6b2c37d479f9 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d9079a969cbbecca3cbeab898a7b3d3a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d936dba3104d21be80694fa09c3f2e76 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d9507094e57544f8c58778996fe93796 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_d9de9a32167fd3c3625cc28150d3dc59 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_da17ec2d008cbcf1fccdade5739c1aa3 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_da4be22b9206de27363e8a6a0e53ed6f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_da63331a5ba2e04a7f539fa874066438 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_da8ca3466e87e6afa9199bbf808a6e99 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_db1dc99bb1ae9f864d590d8d4951ea24 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_db3413134a45f8caafce26c4f5bf31c9 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_db5cdce7ad0e207e29c025a404926a56 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_db6ae4770e9b9d1af93a1aa4823f8b56 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_db815a56ecd54cd9683ab4a1e5fe00d7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_dbe10898134fde8c830ce6125d63547d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_dc7ca58b2438ace110816de0b89071cb create mode 100644 www/fahrschultermin.de/storage/sessions/sess_dcd8063123fb5dc4da53f99e3be8a2f6 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_dce185e29b874f97b24d0246486f3634 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_dcf331017d5be4b352f8f7234fbf6e50 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_dcf395666a298642aa662bc593439f57 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_dd395de0ebabc53b439f4fc9e32d60fc create mode 100644 www/fahrschultermin.de/storage/sessions/sess_de195005e2ff63bd109984d154a398a3 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_de495cd1a69c28af3b9398e92e94d21e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_dfcf80bc29fce6abfed5ac34bebf7c5c create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e02cf9db82152079377afac29dee9f7e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e0d389d5ada1be1011461856fe6026d7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e0fc214acacaab4e1609c3b68e4a04ee create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e10dbe6740069aee59b04e33d58750cc create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e13a7684ee74435983312a287efe4c85 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e180f60c178c65bb2e9d312b4530a437 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e214cc8e60797ecf4e3cbc8dad77b351 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e2463cceb2b637358d505ca930d980e5 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e28398cafa24c8e0e56db5aed7749dc7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e2c69c8fb699f8ccdd772818cc7a843b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e30bb2069903f00d5b2b403e63af7538 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e32f151f178fc3473d264598c4cdb3db create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e3cd81ee2a416b06478fbb48384a4536 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e3df5ff0de2d27045da872d3673acd8f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e454faf4a50341fd4fe7d3d7a4a8043a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e4e5ec23b16a1f4fa28b55e9fabb367d create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e50445bf32d3a170f42828268268b95f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e511429ccbb1b5bf87fd2654d2c93dfb create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e512cfa96becba816ff098acd58d67d9 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e595133187ee681f4d193ad6d3d98bf7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e62417d00dfab625e2e867bc95c0e6b3 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e7294a484f93c841c720dfddaad2fa75 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e78b800b29f053b098e046e43e2b5e32 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e79da2fabd48f5e436d0a2ae92858814 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e7a0cb5ac086583a7c4f433f5566dfe0 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e7bf81464f2a282d2b5c37384544d992 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e7e0d10f34c130abe39787681ecc7c54 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e87478078e020e24d0779351c0f969b6 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_e8a7bca6e77dfea8a08a9d85ea373b3e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ea261e39262abaa69f59218d58152e24 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_eaca2607ee393620327eea92f80f61dc create mode 100644 www/fahrschultermin.de/storage/sessions/sess_eafee1426b1ca0cba1fef01042da24af create mode 100644 www/fahrschultermin.de/storage/sessions/sess_eb0b3aa7fb842573df159a3cbfbc118c create mode 100644 www/fahrschultermin.de/storage/sessions/sess_eb1423e9f00c7c286a8de731475317ed create mode 100644 www/fahrschultermin.de/storage/sessions/sess_eb5dc235fb964cb087b0581dc5470dcb create mode 100644 www/fahrschultermin.de/storage/sessions/sess_eb632c7b379753ce9e0678cc6e1fc4b7 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_eba6bb7a7e9f84453ff471f0de9c858a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ec5dd9dcf14935e06480e595c5378408 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ece749dc3266e76f800a73634c290360 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ed3e4c977232325dc987613963520ae2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ed5a34fc673a1877970f68358b1c8a2e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ed720542b7344966b8a96736178ecaf8 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_edae8a6f1230dc09b4cb5a54d21be367 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ede9c689e49df248407a15a84fc3ab6e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ef7f937ea6a6f62a765a435db96aca63 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_effb84e824295ad73fbe605ac6fe2f88 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f0e668329a30681f9bfbc3cb07c4f5cd create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f0f092cee32c29d95e9bd7c7c30e20f2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f194bc98e8e4096a6b45e7fb11d139b2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f1adc2c0c56dc66a742546fd7f916722 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f1b44223c5587e630f814a8dccd57813 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f1b6dc29404814c5b7b518731d3ad515 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f1ed9645499bb8f575b5dc55017f6482 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f1f8d1c3218c6362dcf0711b6e569ec6 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f21a3956f18d80fdec12b29b03404111 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f2b140d9118c0885e76b7bb1a5b232d0 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f2c30586d756f874aa3952a62be05345 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f341b63a04b60a2b5b7df9ad6450e9f2 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f349b233d747db9b4973592d72ba103e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f36a1f4d2ff411d7442cfccbeff9c92f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f4a545daab3a7bbc90c7313ce16cd200 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f4a6ba717bff574a5108716d3c1b76e8 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f54673683192fda4d1c05bc417825a4c create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f57e67551a8adca075901267474c5e02 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f7434453f5c9fefe400762eea3329b9f create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f86b00b21a16ae362e3e55e5cbbb49ad create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f88b647727d942b3ff67893febfa83ec create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f9047caa910fe889f68f301f00706b3b create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f9288a644472ee1e11539a71da2c16ac create mode 100644 www/fahrschultermin.de/storage/sessions/sess_f953c927cd6f250afb7d4bc8791a2506 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_fa32595e8f8e23ab874e9247eb036ec1 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_fabbd025bed38d86b6c790c3e2ae219e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_facf7bf4f85611dce0bc1077747eb283 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_fbd736474197b68a63d2aa66da52e238 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_fbf559b7fcb34e39382b7beb9e3a3052 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_fc283dec0f73891e83fc082f138c358e create mode 100644 www/fahrschultermin.de/storage/sessions/sess_fd3e050332ff331670681adaa7f3a04a create mode 100644 www/fahrschultermin.de/storage/sessions/sess_fe0b40307095880975721f2e56e653cb create mode 100644 www/fahrschultermin.de/storage/sessions/sess_fe92bea57dd460a4bde6b5a3b5b9f844 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_fedd27c97c990386b6b30a35d8da84dc create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ff4c8da7fd10a870809494bfdefa4cb9 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ffb15114fcaa6705fce00c84bef2acf8 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ffd5b0789f866679e1b3167ed8323759 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ffddcc106e6d3e46d1caa3c2dfddf4da create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ffdef08140c48ce0375a0a3d6313da12 create mode 100644 www/fahrschultermin.de/storage/sessions/sess_ffe009470987847df4966990ac35d661 diff --git a/api/README.md b/api/README.md new file mode 100644 index 0000000..0985a4b --- /dev/null +++ b/api/README.md @@ -0,0 +1,44 @@ +# DriveTime Planner API (Slim 4) + +## Setup + +```bash +cd api +composer install +``` + +## Run + +```bash +php -S localhost:8080 -t public/ +``` + +## Routes + +| Method | Path | Description | +|--------|------|-------------| +| POST | /api/v1/auth/login | Login (returns JWT) | +| POST | /api/v1/auth/logout | Logout | +| GET | /api/v1/auth/me | Current user | +| GET | /api/v1/bootstrap | Full app data | +| GET | /api/v1/students | List students | +| POST | /api/v1/students | Create student | +| PATCH | /api/v1/students/{id} | Update student | +| DELETE | /api/v1/students/{id} | Delete student | +| GET | /api/v1/instructors | List instructors | +| POST | /api/v1/instructors | Create instructor | +| PATCH | /api/v1/instructors/{id} | Update instructor | +| GET | /api/v1/appointments | List appointments | +| POST | /api/v1/appointments | Create appointment | +| PATCH | /api/v1/appointments/{id} | Update appointment | +| DELETE | /api/v1/appointments/{id} | Delete appointment | +| GET | /api/v1/lesson-types | List lesson types | +| GET | /api/v1/license-class-templates | List templates | + +## Auth + +JWT Bearer token in Authorization header. + +## Database + +PostgreSQL (configured in src/Config/Container.php) \ No newline at end of file diff --git a/api/composer.json b/api/composer.json new file mode 100644 index 0000000..5f27abb --- /dev/null +++ b/api/composer.json @@ -0,0 +1,29 @@ +{ + "name": "drivetimeplaner/api", + "description": "DriveTime Planner API - Slim 4", + "type": "project", + "require": { + "php": "^8.2", + "slim/slim": "^4.12", + "slim/psr7": "^1.6", + "php-di/php-di": "^7.0", + "illuminate/database": "^11.0", + "firebase/php-jwt": "^7.0", + "respect/validation": "^2.2", + "robmorgan/phinx": "^0.14", + "tuupola/cors-middleware": "^1.2", + "symfony/dotenv": "^7.0" + }, + "autoload": { + "psr-4": { + "App\\": "src/" + } + }, + "scripts": { + "migrate": "phinx migrate", + "migrate:rollback": "phinx rollback" + }, + "config": { + "optimize-autoloader": true + } +} \ No newline at end of file diff --git a/api/public/index.php b/api/public/index.php new file mode 100644 index 0000000..56c9351 --- /dev/null +++ b/api/public/index.php @@ -0,0 +1,36 @@ +load($envFile); +} + +// Boot Eloquent BEFORE creating the app +Container::boot(); + +$container = Container::build(); +\Slim\Factory\AppFactory::setContainer($container); + +$app = \Slim\Factory\AppFactory::create(); + +// CORS Middleware +$app->add(new \Tuupola\Middleware\CorsMiddleware([ + 'origin' => ['https://fahrschultermin.de', 'https://www.fahrschultermin.de'], + 'methods' => ['GET', 'POST', 'PATCH', 'DELETE', 'OPTIONS'], + 'headers.allow' => ['Content-Type', 'Authorization', 'X-Requested-With'], + 'credentials' => true, + 'cache.maxage' => 86400, +])); + +// Routes +(require __DIR__ . '/../src/Routes/api.php')($app); + +$app->run(); \ No newline at end of file diff --git a/api/reset_pass.php b/api/reset_pass.php new file mode 100644 index 0000000..96c33a8 --- /dev/null +++ b/api/reset_pass.php @@ -0,0 +1,9 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +$hash = password_hash('Tanja82', PASSWORD_BCRYPT, ['cost' => 12]); +echo "New hash: $hash\n"; +$stmt = $pdo->prepare("UPDATE users SET password_hash = :hash WHERE email = :email"); +$stmt->execute([':hash' => $hash, ':email' => 'admin@nordring.test']); +echo "Updated: " . $stmt->rowCount() . " rows\n"; \ No newline at end of file diff --git a/api/src/Config/Container.php b/api/src/Config/Container.php new file mode 100644 index 0000000..081815a --- /dev/null +++ b/api/src/Config/Container.php @@ -0,0 +1,56 @@ +addConnection([ + 'driver' => 'pgsql', + 'host' => '127.0.0.1', + 'port' => '5433', + 'database' => 'fahrschultermin', + 'username' => 'fahrschultermin_api', + 'password' => 'X9wL3pN7kRtHvMbZs4cGfYu2Dj6qAe8t', + 'charset' => 'utf8', + 'prefix' => '', + 'schema' => 'public', + 'sslmode' => 'prefer', + ]); + + self::$capsule->setAsGlobal(); + self::$capsule->bootEloquent(); + } + + public static function build(): ContainerInterface + { + self::boot(); + + $builder = new \DI\ContainerBuilder(); + + $builder->addDefinitions([ + 'jwt.secret' => 'your-256-bit-secret-change-in-production', + 'config' => [ + 'frontend_url' => 'https://fahrschultermin.de', + 'api_base_url' => 'https://api.fahrschultermin.de', + 'session_name' => 'drive_time_session', + ], + ]); + + return $builder->build(); + } +} \ No newline at end of file diff --git a/api/src/Controllers/AppointmentsController.php b/api/src/Controllers/AppointmentsController.php new file mode 100644 index 0000000..1d7d462 --- /dev/null +++ b/api/src/Controllers/AppointmentsController.php @@ -0,0 +1,81 @@ +getAttribute('tenant_id'); + $from = $request->getQueryParam('from'); + $to = $request->getQueryParam('to'); + + $query = Appointment::where('tenant_id', $tenantId)->with(['student', 'instructor', 'lessonType']); + if ($from && $to) { + $query->whereBetween('start_at', [$from, $to]); + } + return $this->json($response, $query->get()->toArray()); + } + + public function store(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface + { + $tenantId = $request->getAttribute('tenant_id'); + $user = $request->getAttribute('user'); + $data = json_decode($request->getBody()->getContents(), true); + + $appointment = Appointment::create([ + 'tenant_id' => $tenantId, + 'student_id' => $data['student_id'] ?? null, + 'instructor_id' => $data['instructor_id'], + 'lesson_type_id' => $data['lesson_type_id'], + 'title' => $data['title'] ?? '', + 'category' => $data['category'] ?? 'student', + 'start_at' => $data['start_at'], + 'end_at' => $data['end_at'], + 'units' => $data['units'] ?? 1, + 'status' => 'planned', + 'notes' => $data['notes'] ?? '', + 'created_by' => $user['id'], + 'updated_by' => $user['id'], + ]); + + return $this->json($response, $appointment->toArray(), 201); + } + + public function update(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface + { + $tenantId = $request->getAttribute('tenant_id'); + $user = $request->getAttribute('user'); + $appointment = Appointment::where('id', $args['id'])->where('tenant_id', $tenantId)->first(); + if (!$appointment) return $this->json($response, ['message' => 'Not found'], 404); + + $data = json_decode($request->getBody()->getContents(), true); + $data['updated_by'] = $user['id']; + $appointment->update(array_intersect_key($data, array_flip([ + 'student_id', 'instructor_id', 'lesson_type_id', 'title', 'start_at', 'end_at', 'units', 'status', 'notes', 'warning_acknowledged', 'updated_by' + ]))); + + return $this->json($response, $appointment->fresh()->toArray()); + } + + public function destroy(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface + { + $tenantId = $request->getAttribute('tenant_id'); + $appointment = Appointment::where('id', $args['id'])->where('tenant_id', $tenantId)->first(); + if (!$appointment) return $this->json($response, ['message' => 'Not found'], 404); + $appointment->delete(); + return $this->json($response, ['message' => 'Deleted']); + } + + private function json(ResponseInterface $response, array $data, int $status = 200): ResponseInterface + { + $response->getBody()->write(json_encode($data)); + return $response->withStatus($status)->withHeader('Content-Type', 'application/json'); + } +} \ No newline at end of file diff --git a/api/src/Controllers/AuthController.php b/api/src/Controllers/AuthController.php new file mode 100644 index 0000000..04c09ad --- /dev/null +++ b/api/src/Controllers/AuthController.php @@ -0,0 +1,80 @@ +getBody()->getContents(), true); + $email = $body['email'] ?? ''; + $password = $body['password'] ?? ''; + + if (empty($email) || empty($password)) { + return $this->json($response, ['message' => 'Email and password required'], 400); + } + + $user = User::where('email', $email)->first(); + + if (!$user || !password_verify($password, $user->password_hash)) { + return $this->json($response, ['message' => 'Invalid credentials'], 401); + } + + if (!$user->is_active) { + return $this->json($response, ['message' => 'Account disabled'], 403); + } + + // Generate JWT + $now = time(); + $payload = [ + 'iss' => 'api.fahrschultermin.de', + 'sub' => $user->id, + 'tenant_id' => $user->tenant_id, + 'role' => $user->role, + 'iat' => $now, + 'exp' => $now + (8 * 60 * 60), // 8 hours + ]; + + $jwt = JWT::encode($payload, $_ENV['JWT_SECRET'] ?? 'this-is-a-32-character-secret-key-for-testing', 'HS256'); + + $userData = $user->toArray(); + unset($userData['password_hash']); + + return $this->json($response, [ + 'token' => $jwt, + 'user' => $userData, + ]); + } + + public function me(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface + { + $user = $request->getAttribute('user'); + + if (!$user) { + return $this->json($response, ['message' => 'Unauthenticated'], 401); + } + + return $this->json($response, ['user' => $user]); + } + + public function logout(ResponseInterface $response): ResponseInterface + { + // JWT is stateless, logout is client-side (discard token) + return $this->json($response, ['message' => 'Logged out']); + } + + private function json(ResponseInterface $response, array $data, int $status = 200): ResponseInterface + { + $response->getBody()->write(json_encode($data)); + return $response + ->withStatus($status) + ->withHeader('Content-Type', 'application/json'); + } +} \ No newline at end of file diff --git a/api/src/Controllers/BootstrapController.php b/api/src/Controllers/BootstrapController.php new file mode 100644 index 0000000..fb1e3e4 --- /dev/null +++ b/api/src/Controllers/BootstrapController.php @@ -0,0 +1,67 @@ +getAttribute('user'); + $tenantId = (int) $user['tenant_id']; + + // Load tenant + $tenant = Tenant::find($tenantId); + if (!$tenant) { + return $this->json($response, ['message' => 'Tenant not found'], 404); + } + + // Load all related data + $students = Student::where('tenant_id', $tenantId)->with('template')->get(); + $instructors = Instructor::where('tenant_id', $tenantId)->get(); + $lessonTypes = LessonType::where('tenant_id', $tenantId)->orderBy('sort_order')->get(); + $templates = LicenseClassTemplate::where('tenant_id', $tenantId)->with('requirements')->get(); + + // Date range from query params + $params = $request->getQueryParams(); + $from = $params['from'] ?? gmdate('Y-m-d\TH:i:s\Z', strtotime('monday this week')); + $to = $params['to'] ?? gmdate('Y-m-d\TH:i:s\Z', strtotime('+7 day', strtotime($from))); + + // Load appointments + $appointments = Appointment::where('tenant_id', $tenantId) + ->whereBetween('start_at', [$from, $to]) + ->with(['student', 'instructor', 'lessonType']) + ->get(); + + $payload = [ + 'session' => ['user' => $user], + 'tenant' => $tenant->toArray(), + 'students' => $students->toArray(), + 'instructors' => $instructors->toArray(), + 'lessonTypes' => $lessonTypes->toArray(), + 'templates' => $templates->toArray(), + 'appointments' => $appointments->toArray(), + ]; + + return $this->json($response, $payload); + } + + private function json(ResponseInterface $response, array $data, int $status = 200): ResponseInterface + { + $response->getBody()->write(json_encode($data)); + return $response + ->withStatus($status) + ->withHeader('Content-Type', 'application/json'); + } +} \ No newline at end of file diff --git a/api/src/Controllers/InstructorsController.php b/api/src/Controllers/InstructorsController.php new file mode 100644 index 0000000..52ed3b4 --- /dev/null +++ b/api/src/Controllers/InstructorsController.php @@ -0,0 +1,55 @@ +getAttribute('tenant_id'); + $instructors = Instructor::where('tenant_id', $tenantId)->get(); + return $this->json($response, $instructors->toArray()); + } + + public function store(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface + { + $tenantId = $request->getAttribute('tenant_id'); + $data = json_decode($request->getBody()->getContents(), true); + + $instructor = Instructor::create([ + 'tenant_id' => $tenantId, + 'first_name' => $data['first_name'] ?? '', + 'last_name' => $data['last_name'] ?? '', + 'color' => $data['color'] ?? '#0e5d80', + 'notes' => $data['notes'] ?? '', + 'is_active' => 1, + 'pre_start_buffer_minutes' => $data['pre_start_buffer_minutes'] ?? 60, + ]); + + return $this->json($response, $instructor->toArray(), 201); + } + + public function update(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface + { + $tenantId = $request->getAttribute('tenant_id'); + $instructor = Instructor::where('id', $args['id'])->where('tenant_id', $tenantId)->first(); + if (!$instructor) return $this->json($response, ['message' => 'Not found'], 404); + + $data = json_decode($request->getBody()->getContents(), true); + $instructor->update(array_intersect_key($data, array_flip(['first_name', 'last_name', 'color', 'notes', 'is_active', 'pre_start_buffer_minutes']))); + + return $this->json($response, $instructor->fresh()->toArray()); + } + + private function json(ResponseInterface $response, array $data, int $status = 200): ResponseInterface + { + $response->getBody()->write(json_encode($data)); + return $response->withStatus($status)->withHeader('Content-Type', 'application/json'); + } +} \ No newline at end of file diff --git a/api/src/Controllers/LessonTypesController.php b/api/src/Controllers/LessonTypesController.php new file mode 100644 index 0000000..808a146 --- /dev/null +++ b/api/src/Controllers/LessonTypesController.php @@ -0,0 +1,25 @@ +getAttribute('tenant_id'); + $types = LessonType::where('tenant_id', $tenantId)->orderBy('sort_order')->get(); + return $this->json($response, $types->toArray()); + } + + private function json(ResponseInterface $response, array $data, int $status = 200): ResponseInterface + { + $response->getBody()->write(json_encode($data)); + return $response->withStatus($status)->withHeader('Content-Type', 'application/json'); + } +} \ No newline at end of file diff --git a/api/src/Controllers/StudentsController.php b/api/src/Controllers/StudentsController.php new file mode 100644 index 0000000..6d7f37f --- /dev/null +++ b/api/src/Controllers/StudentsController.php @@ -0,0 +1,75 @@ +getAttribute('tenant_id'); + $students = Student::where('tenant_id', $tenantId)->with('template')->get(); + return $this->json($response, $students->toArray()); + } + + public function store(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface + { + $tenantId = $request->getAttribute('tenant_id'); + $data = json_decode($request->getBody()->getContents(), true); + + $student = Student::create([ + 'tenant_id' => $tenantId, + 'template_id' => $data['template_id'] ?? null, + 'first_name' => $data['first_name'] ?? '', + 'last_name' => $data['last_name'] ?? '', + 'email' => $data['email'] ?? '', + 'phone' => $data['phone'] ?? '', + 'needs_glasses' => $data['needs_glasses'] ?? 0, + 'status' => $data['status'] ?? 'active', + 'notes' => $data['notes'] ?? '', + ]); + + return $this->json($response, $student->toArray(), 201); + } + + public function update(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface + { + $tenantId = $request->getAttribute('tenant_id'); + $student = Student::where('id', $args['id'])->where('tenant_id', $tenantId)->first(); + + if (!$student) { + return $this->json($response, ['message' => 'Student not found'], 404); + } + + $data = json_decode($request->getBody()->getContents(), true); + $student->update(array_intersect_key($data, array_flip([ + 'first_name', 'last_name', 'email', 'phone', 'needs_glasses', 'status', 'notes', 'template_id' + ]))); + + return $this->json($response, $student->fresh()->toArray()); + } + + public function destroy(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface + { + $tenantId = $request->getAttribute('tenant_id'); + $student = Student::where('id', $args['id'])->where('tenant_id', $tenantId)->first(); + + if (!$student) { + return $this->json($response, ['message' => 'Student not found'], 404); + } + + $student->delete(); + return $this->json($response, ['message' => 'Deleted']); + } + + private function json(ResponseInterface $response, array $data, int $status = 200): ResponseInterface + { + $response->getBody()->write(json_encode($data)); + return $response->withStatus($status)->withHeader('Content-Type', 'application/json'); + } +} \ No newline at end of file diff --git a/api/src/Controllers/TemplatesController.php b/api/src/Controllers/TemplatesController.php new file mode 100644 index 0000000..5d34b44 --- /dev/null +++ b/api/src/Controllers/TemplatesController.php @@ -0,0 +1,27 @@ +getAttribute('tenant_id'); + $templates = LicenseClassTemplate::where('tenant_id', $tenantId) + ->with('requirements') + ->get(); + return $this->json($response, $templates->toArray()); + } + + private function json(ResponseInterface $response, array $data, int $status = 200): ResponseInterface + { + $response->getBody()->write(json_encode($data)); + return $response->withStatus($status)->withHeader('Content-Type', 'application/json'); + } +} \ No newline at end of file diff --git a/api/src/Middleware/JwtMiddleware.php b/api/src/Middleware/JwtMiddleware.php new file mode 100644 index 0000000..c320126 --- /dev/null +++ b/api/src/Middleware/JwtMiddleware.php @@ -0,0 +1,62 @@ +getHeaderLine('Authorization') ?? ''; + + if (!str_starts_with($authHeader, 'Bearer ')) { + $body = json_encode(['message' => 'Missing or invalid Authorization header']); + return new \Slim\Psr7\Response(401) + ->withHeader('Content-Type', 'application/json') + ->withBody((new \Slim\Psr7\Factory\StreamFactory())->createStream($body)); + } + + $token = substr($authHeader, 7); + + try { + $secret = $_ENV['JWT_SECRET'] ?? 'this-is-a-32-character-secret-key-for-testing'; + $decoded = JWT::decode($token, new Key($secret, 'HS256')); + } catch (\Throwable $e) { + $body = json_encode(['message' => 'Invalid or expired token']); + return new \Slim\Psr7\Response(401) + ->withHeader('Content-Type', 'application/json') + ->withBody((new \Slim\Psr7\Factory\StreamFactory())->createStream($body)); + } + + // Load user from DB + $user = User::find($decoded->sub); + + if (!$user) { + $body = json_encode(['message' => 'User not found']); + return new \Slim\Psr7\Response(401) + ->withHeader('Content-Type', 'application/json') + ->withBody((new \Slim\Psr7\Factory\StreamFactory())->createStream($body)); + } + + if (!$user->is_active) { + $body = json_encode(['message' => 'Account disabled']); + return new \Slim\Psr7\Response(401) + ->withHeader('Content-Type', 'application/json') + ->withBody((new \Slim\Psr7\Factory\StreamFactory())->createStream($body)); + } + + // Attach user and tenant to request + $request = $request->withAttribute('user', $user->toArray()); + $request = $request->withAttribute('tenant_id', $user->tenant_id); + + return $handler->handle($request); + } +} \ No newline at end of file diff --git a/api/src/Models/Appointment.php b/api/src/Models/Appointment.php new file mode 100644 index 0000000..d03c281 --- /dev/null +++ b/api/src/Models/Appointment.php @@ -0,0 +1,28 @@ + 'integer', 'instructor_id' => 'integer', + 'lesson_type_id' => 'integer', 'units' => 'integer', + 'warning_acknowledged' => 'integer', 'created_by' => 'integer', 'updated_by' => 'integer', + ]; + + public function tenant(): BelongsTo { return $this->belongsTo(Tenant::class, 'tenant_id'); } + public function student(): BelongsTo { return $this->belongsTo(Student::class, 'student_id'); } + public function instructor(): BelongsTo { return $this->belongsTo(Instructor::class, 'instructor_id'); } + public function lessonType(): BelongsTo { return $this->belongsTo(LessonType::class, 'lesson_type_id'); } +} \ No newline at end of file diff --git a/api/src/Models/Instructor.php b/api/src/Models/Instructor.php new file mode 100644 index 0000000..9c4c924 --- /dev/null +++ b/api/src/Models/Instructor.php @@ -0,0 +1,28 @@ + 'integer', 'tenant_id' => 'integer']; + + public function tenant(): BelongsTo + { + return $this->belongsTo(Tenant::class, 'tenant_id'); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class, 'user_id'); + } +} \ No newline at end of file diff --git a/api/src/Models/LessonType.php b/api/src/Models/LessonType.php new file mode 100644 index 0000000..688caaa --- /dev/null +++ b/api/src/Models/LessonType.php @@ -0,0 +1,27 @@ + 'integer', 'is_counted' => 'integer', + 'is_rest_relevant' => 'integer', 'fixed_duration' => 'integer', + ]; + + public function tenant(): BelongsTo + { + return $this->belongsTo(Tenant::class, 'tenant_id'); + } +} \ No newline at end of file diff --git a/api/src/Models/LessonTypeTemplateVisibility.php b/api/src/Models/LessonTypeTemplateVisibility.php new file mode 100644 index 0000000..0dd407d --- /dev/null +++ b/api/src/Models/LessonTypeTemplateVisibility.php @@ -0,0 +1,14 @@ + 'integer']; + + public function tenant(): BelongsTo + { + return $this->belongsTo(Tenant::class, 'tenant_id'); + } + + public function requirements(): HasMany + { + return $this->hasMany(LicenseTemplateRequirement::class, 'template_id'); + } + + public function students(): HasMany + { + return $this->hasMany(Student::class, 'template_id'); + } +} \ No newline at end of file diff --git a/api/src/Models/LicenseTemplateRequirement.php b/api/src/Models/LicenseTemplateRequirement.php new file mode 100644 index 0000000..6f56fcc --- /dev/null +++ b/api/src/Models/LicenseTemplateRequirement.php @@ -0,0 +1,17 @@ + 'integer', 'sort_order' => 'integer']; +} \ No newline at end of file diff --git a/api/src/Models/Student.php b/api/src/Models/Student.php new file mode 100644 index 0000000..ae60fc5 --- /dev/null +++ b/api/src/Models/Student.php @@ -0,0 +1,28 @@ + 'integer', 'tenant_id' => 'integer', 'template_id' => 'integer']; + + public function tenant(): BelongsTo + { + return $this->belongsTo(Tenant::class, 'tenant_id'); + } + + public function template(): BelongsTo + { + return $this->belongsTo(LicenseClassTemplate::class, 'template_id'); + } +} \ No newline at end of file diff --git a/api/src/Models/Tenant.php b/api/src/Models/Tenant.php new file mode 100644 index 0000000..9b6623f --- /dev/null +++ b/api/src/Models/Tenant.php @@ -0,0 +1,37 @@ + 'integer', + 'latitude' => 'float', + 'longitude' => 'float', + ]; + + public function users(): HasMany + { + return $this->hasMany(User::class, 'tenant_id'); + } + + public function students(): HasMany + { + return $this->hasMany(Student::class, 'tenant_id'); + } + + public function instructors(): HasMany + { + return $this->hasMany(Instructor::class, 'tenant_id'); + } +} \ No newline at end of file diff --git a/api/src/Models/User.php b/api/src/Models/User.php new file mode 100644 index 0000000..8bf788d --- /dev/null +++ b/api/src/Models/User.php @@ -0,0 +1,29 @@ + 'integer']; + protected $hidden = ['password_hash']; + + public function tenant(): BelongsTo + { + return $this->belongsTo(Tenant::class, 'tenant_id'); + } + + public function instructor() + { + return $this->hasOne(Instructor::class, 'user_id'); + } +} \ No newline at end of file diff --git a/api/src/Routes/api.php b/api/src/Routes/api.php new file mode 100644 index 0000000..337811f --- /dev/null +++ b/api/src/Routes/api.php @@ -0,0 +1,62 @@ +post('/api/v1/auth/login', [AuthController::class, 'login']); + $app->post('/api/v1/auth/logout', [AuthController::class, 'logout']); + + // Protected routes + $app->get('/api/v1/auth/me', [AuthController::class, 'me']) + ->add(JwtMiddleware::class); + + $app->get('/api/v1/bootstrap', [BootstrapController::class, 'handle']) + ->add(JwtMiddleware::class); + + // CRUD routes + $app->get('/api/v1/students', [StudentsController::class, 'index']) + ->add(JwtMiddleware::class); + $app->post('/api/v1/students', [StudentsController::class, 'store']) + ->add(JwtMiddleware::class); + $app->patch('/api/v1/students/{id}', [StudentsController::class, 'update']) + ->add(JwtMiddleware::class); + $app->delete('/api/v1/students/{id}', [StudentsController::class, 'destroy']) + ->add(JwtMiddleware::class); + + $app->get('/api/v1/instructors', [InstructorsController::class, 'index']) + ->add(JwtMiddleware::class); + $app->post('/api/v1/instructors', [InstructorsController::class, 'store']) + ->add(JwtMiddleware::class); + $app->patch('/api/v1/instructors/{id}', [InstructorsController::class, 'update']) + ->add(JwtMiddleware::class); + + $app->get('/api/v1/appointments', [AppointmentsController::class, 'index']) + ->add(JwtMiddleware::class); + $app->post('/api/v1/appointments', [AppointmentsController::class, 'store']) + ->add(JwtMiddleware::class); + $app->patch('/api/v1/appointments/{id}', [AppointmentsController::class, 'update']) + ->add(JwtMiddleware::class); + $app->delete('/api/v1/appointments/{id}', [AppointmentsController::class, 'destroy']) + ->add(JwtMiddleware::class); + + // Reference data + $app->get('/api/v1/lesson-types', [LessonTypesController::class, 'index']) + ->add(JwtMiddleware::class); + $app->get('/api/v1/license-class-templates', [TemplatesController::class, 'index']) + ->add(JwtMiddleware::class); + + // Fallback 404 + $app->any('/api/v1/{path}', function ($request, $response) { + return $this->get('response')->withStatus(404)->withJson(['message' => 'Not Found']); + }); +}; \ No newline at end of file diff --git a/api/test_db.php b/api/test_db.php new file mode 100644 index 0000000..e0b8bb1 --- /dev/null +++ b/api/test_db.php @@ -0,0 +1,9 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $stmt = $pdo->prepare("SELECT id, email, password_hash FROM users LIMIT 1"); + $stmt->execute(); + print_r($stmt->fetch(PDO::FETCH_ASSOC)); +} catch (Exception $e) { echo $e->getMessage(); } \ No newline at end of file diff --git a/api/test_login.php b/api/test_login.php new file mode 100644 index 0000000..e79e86b --- /dev/null +++ b/api/test_login.php @@ -0,0 +1,12 @@ +first(); +if ($user) { + echo "User found: " . $user->email . "\n"; + echo "Hash: " . $user->password_hash . "\n"; + echo "Verify: " . (password_verify("Tanja82", $user->password_hash) ? "OK" : "FAIL") . "\n"; +} else { + echo "User not found"; +} \ No newline at end of file diff --git a/backup/fahrschultermin_20260518_223313/docs/README.txt b/backup/fahrschultermin_20260518_223313/docs/README.txt new file mode 100644 index 0000000..5149803 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/docs/README.txt @@ -0,0 +1 @@ +No docs directory on server - placeholder created diff --git a/backup/fahrschultermin_20260518_223313/www/api.fahrschultermin.de/public/index.php b/backup/fahrschultermin_20260518_223313/www/api.fahrschultermin.de/public/index.php new file mode 100644 index 0000000..c898f81 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/api.fahrschultermin.de/public/index.php @@ -0,0 +1,2 @@ +query('from'); + $to = (string) $request->query('to'); + $instructorId = null; + if ($user['role'] === 'instructor') { + $instructor = (new InstructorRepository())->findByUserId((int) $user['tenant_id'], (int) $user['id']); + $instructorId = $instructor ? (int) $instructor['id'] : null; + } + $data = (new AppointmentRepository())->listForTenant( + (int) $user['tenant_id'], + $from, + $to, + $instructorId + ); + Response::json(['data' => $data]); + } + + public function validate(Request $request): void + { + $user = Auth::requireUser(); + $payload = $this->normalizePayload((int) $user['tenant_id'], $request->input()); + $warnings = (new CalendarService())->validate((int) $user['tenant_id'], $payload); + Response::json(['warnings' => $warnings, 'payload' => $payload]); + } + + public function store(Request $request): void + { + $user = Auth::requireRole(['tenant_admin', 'dispatcher', 'instructor']); + $payload = $this->normalizePayload((int) $user['tenant_id'], $request->input()); + $warnings = (new CalendarService())->validate((int) $user['tenant_id'], $payload); + + if ($this->hasWarnings($warnings) && empty($payload['warning_acknowledged'])) { + Response::json(['message' => 'Warnings require acknowledgement', 'warnings' => $warnings], 409); + } + + $record = (new AppointmentRepository())->create((int) $user['tenant_id'], $payload, (int) $user['id']); + Response::json([ + 'data' => $record, + 'warnings' => $warnings, + 'progress' => $payload['student_id'] ? (new RequirementService())->progressForStudent((int) $payload['student_id']) : [], + ], 201); + } + + public function update(Request $request, array $params): void + { + $user = Auth::requireRole(['tenant_admin', 'dispatcher', 'instructor']); + $payload = $this->normalizePayload((int) $user['tenant_id'], $request->input()); + $warnings = (new CalendarService())->validate((int) $user['tenant_id'], $payload, (int) $params['id']); + + if ($this->hasWarnings($warnings) && empty($payload['warning_acknowledged'])) { + Response::json(['message' => 'Warnings require acknowledgement', 'warnings' => $warnings], 409); + } + + $record = (new AppointmentRepository())->update((int) $user['tenant_id'], (int) $params['id'], $payload, (int) $user['id']); + Response::json([ + 'data' => $record, + 'warnings' => $warnings, + 'progress' => $payload['student_id'] ? (new RequirementService())->progressForStudent((int) $payload['student_id']) : [], + ]); + } + + public function destroy(Request $request, array $params): void + { + $user = Auth::requireRole(['tenant_admin', 'dispatcher']); + (new AppointmentRepository())->delete((int) $user['tenant_id'], (int) $params['id']); + Response::noContent(); + } + + private function normalizePayload(int $tenantId, array $payload): array + { + $referenceRepository = new ReferenceDataRepository(); + $lessonType = $referenceRepository->findLessonType($tenantId, (int) $payload['lesson_type_id']); + if ($lessonType === null) { + Response::json(['message' => 'Unbekannte Stundenart'], 422); + } + + $student = null; + if (!empty($payload['student_id'])) { + $student = (new StudentRepository())->find($tenantId, (int) $payload['student_id']); + if ($student === null) { + Response::json(['message' => 'Unbekannter Fahrschueler'], 422); + } + + if ( + $lessonType['category'] === 'student' + && !$referenceRepository->isLessonTypeVisibleForTemplate($tenantId, (int) $lessonType['id'], (int) $student['template_id']) + ) { + Response::json(['message' => 'Diese Stundenart ist fuer die Fuehrerscheinklasse nicht freigegeben'], 422); + } + } + + $startAt = new \DateTimeImmutable((string) $payload['start_at']); + $baseDuration = max(1, (int) $lessonType['default_duration']); + $providedEndAt = !empty($payload['end_at']) ? new \DateTimeImmutable((string) $payload['end_at']) : null; + + if ($providedEndAt instanceof \DateTimeImmutable && $providedEndAt > $startAt) { + $endAt = $providedEndAt; + $duration = max(1, (int) round(($endAt->getTimestamp() - $startAt->getTimestamp()) / 60)); + $units = (int) $lessonType['fixed_duration'] === 1 + ? 1 + : max(1, (int) round($duration / $baseDuration)); + } else { + $units = max(1, (int) ($payload['units'] ?? 1)); + $duration = (int) $lessonType['fixed_duration'] === 1 + ? $baseDuration + : $baseDuration * $units; + $endAt = $startAt->modify(sprintf('+%d minutes', $duration)); + } + + $title = $payload['title'] ?? $lessonType['name'] ?? 'Termin'; + $unitsBreakdown = []; + + if ($lessonType && (int) $lessonType['is_counted'] === 1 && !empty($payload['student_id'])) { + $requirementKey = $lessonType['effective_requirement_key'] ?? $lessonType['requirement_key']; + if ($student !== null && (int) ($student['template_is_combination'] ?? 0) === 1) { + $requirementKey = $this->requirementKeyForPhase($student['requirements'] ?? [], $requirementKey, (string) ($payload['requirement_phase'] ?? '')); + } + + $unitsBreakdown[] = [ + 'requirement_key' => $requirementKey, + 'label' => $lessonType['name'], + 'units_counted' => $units, + ]; + } + + return [ + 'student_id' => $payload['student_id'] ?? null, + 'instructor_id' => (int) $payload['instructor_id'], + 'lesson_type_id' => (int) $payload['lesson_type_id'], + 'title' => $title, + 'category' => $lessonType['category'] ?? ($payload['category'] ?? 'student'), + 'start_at' => $startAt->format(DATE_ATOM), + 'end_at' => $endAt->format(DATE_ATOM), + 'units' => $units, + 'status' => $payload['status'] ?? 'planned', + 'notes' => $payload['notes'] ?? '', + 'warning_acknowledged' => !empty($payload['warning_acknowledged']), + 'units_breakdown' => $unitsBreakdown, + ]; + } + + private function hasWarnings(array $warnings): bool + { + return !empty($warnings['conflicts']) || !empty($warnings['rest_period_warnings']); + } + + private function requirementKeyForPhase(array $requirements, string $baseRequirementKey, string $phaseLabel): string + { + $matchingRows = array_values(array_filter( + $requirements, + static fn (array $row): bool => str_ends_with((string) $row['requirement_key'], '::' . $baseRequirementKey) + || (string) $row['requirement_key'] === $baseRequirementKey + )); + + if ($matchingRows === []) { + return $baseRequirementKey; + } + + foreach ($matchingRows as $row) { + if ((string) ($row['phase_label'] ?? '') === $phaseLabel) { + return (string) $row['requirement_key']; + } + } + + return (string) $matchingRows[0]['requirement_key']; + } +} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/AuthController.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/AuthController.php new file mode 100644 index 0000000..a71b131 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/AuthController.php @@ -0,0 +1,49 @@ +input(); + $user = (new UserRepository())->findByEmail(trim((string) ($payload['email'] ?? ''))); + + if ($user === null || !password_verify((string) ($payload['password'] ?? ''), $user['password_hash'])) { + Response::json(['message' => 'Ungueltige Zugangsdaten'], 422); + } + + if (!(int) $user['is_active'] || ((int) ($user['tenant_is_active'] ?? 1) !== 1 && $user['role'] !== 'superadmin')) { + Response::json(['message' => 'Benutzer oder Mandant ist deaktiviert'], 403); + } + + $_SESSION['user_id'] = (int) $user['id']; + + Response::json(['user' => $this->sanitizeUser($user)]); + } + + public function logout(): void + { + session_destroy(); + Response::noContent(); + } + + public function me(): void + { + $user = Auth::user(); + Response::json(['user' => $user ? $this->sanitizeUser($user) : null]); + } + + private function sanitizeUser(array $user): array + { + unset($user['password_hash']); + return $user; + } +} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/BootstrapController.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/BootstrapController.php new file mode 100644 index 0000000..0a9bf53 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/BootstrapController.php @@ -0,0 +1,76 @@ + ['user' => $user]]; + + if ($user['role'] === 'superadmin') { + $payload['tenants'] = (new TenantRepository())->all(); + $payload['users'] = (new UserRepository())->allUsers(); + Response::json($payload); + } + + $tenantId = (int) $user['tenant_id']; + $studentRepository = new StudentRepository(); + $students = $studentRepository->allForTenant($tenantId); + $requirementService = new RequirementService(); + + foreach ($students as &$student) { + $student['progress'] = $requirementService->progressForStudent((int) $student['id']); + } + + $payload['tenant'] = (new TenantRepository())->find($tenantId); + $payload['students'] = $students; + $payload['templates'] = (new ReferenceDataRepository())->templates($tenantId); + $payload['lessonTypes'] = (new ReferenceDataRepository())->lessonTypes($tenantId); + $payload['instructors'] = (new InstructorRepository())->allForTenant($tenantId); + $payload['users'] = $user['role'] === 'tenant_admin' + ? (new UserRepository())->allForTenant($tenantId) + : []; + $instructorId = null; + if ($user['role'] === 'instructor') { + $instructor = (new InstructorRepository())->findByUserId($tenantId, (int) $user['id']); + $instructorId = $instructor ? (int) $instructor['id'] : null; + } + + $from = $request->query('from', gmdate('Y-m-d\T00:00:00\Z', strtotime('monday this week'))); + $to = $request->query('to', gmdate('Y-m-d\T00:00:00\Z', strtotime('+7 day', strtotime($from)))); + $payload['sunTimes'] = (new SunTimesService())->forRange($payload['tenant'], (string) $from, (string) $to); + $payload['dayMeta'] = (new HolidayService())->forRange($payload['tenant'], (string) $from, (string) $to); + $payload['appointments'] = (new AppointmentRepository())->listForTenant( + $tenantId, + (string) $from, + (string) $to, + $instructorId + ); + $payload['nextAppointment'] = (new AppointmentRepository())->nextForTenant( + $tenantId, + gmdate(DATE_ATOM), + $instructorId + ); + + Response::json($payload); + } +} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/InstructorsController.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/InstructorsController.php new file mode 100644 index 0000000..b72c60a --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/InstructorsController.php @@ -0,0 +1,33 @@ + (new InstructorRepository())->allForTenant((int) $user['tenant_id'])]); + } + + public function store(Request $request): void + { + $user = Auth::requireRole('tenant_admin'); + $record = (new InstructorRepository())->create((int) $user['tenant_id'], $request->input()); + Response::json(['data' => $record], 201); + } + + public function update(Request $request, array $params): void + { + $user = Auth::requireRole('tenant_admin'); + $record = (new InstructorRepository())->update((int) $user['tenant_id'], (int) $params['id'], $request->input()); + Response::json(['data' => $record]); + } +} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/ReferenceDataController.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/ReferenceDataController.php new file mode 100644 index 0000000..71b091c --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/ReferenceDataController.php @@ -0,0 +1,90 @@ + (new ReferenceDataRepository())->lessonTypes((int) $user['tenant_id'])]); + } + + public function storeLessonType(Request $request): void + { + $user = Auth::requireRole('tenant_admin'); + $record = (new ReferenceDataRepository())->createLessonType((int) $user['tenant_id'], $request->input()); + Response::json(['data' => $record], 201); + } + + public function updateLessonType(Request $request, array $params): void + { + $user = Auth::requireRole('tenant_admin'); + $record = (new ReferenceDataRepository())->updateLessonType((int) $user['tenant_id'], (int) $params['id'], $request->input()); + Response::json(['data' => $record]); + } + + public function destroyLessonType(Request $request, array $params): void + { + $user = Auth::requireRole('tenant_admin'); + + try { + $deleted = (new ReferenceDataRepository())->deleteLessonType((int) $user['tenant_id'], (int) $params['id']); + } catch (\RuntimeException $exception) { + Response::json(['message' => $exception->getMessage()], 409); + } + + if (!$deleted) { + Response::json(['message' => 'Not found'], 404); + } + + Response::noContent(); + } + + public function reorderLessonTypes(Request $request): void + { + $user = Auth::requireRole('tenant_admin'); + $records = (new ReferenceDataRepository())->reorderLessonTypes( + (int) $user['tenant_id'], + (array) ($request->input()['lesson_type_ids'] ?? []) + ); + Response::json(['data' => $records]); + } + + public function updateLessonTypeMatrix(Request $request): void + { + $user = Auth::requireRole('tenant_admin'); + $records = (new ReferenceDataRepository())->updateLessonTypeMatrix( + (int) $user['tenant_id'], + (array) ($request->input()['rows'] ?? []) + ); + Response::json(['data' => $records]); + } + + public function templates(): void + { + $user = Auth::requireUser(); + Response::json(['data' => (new ReferenceDataRepository())->templates((int) $user['tenant_id'])]); + } + + public function storeTemplate(Request $request): void + { + $user = Auth::requireRole('tenant_admin'); + $record = (new ReferenceDataRepository())->createTemplate((int) $user['tenant_id'], $request->input()); + Response::json(['data' => $record], 201); + } + + public function updateTemplate(Request $request, array $params): void + { + $user = Auth::requireRole('tenant_admin'); + $record = (new ReferenceDataRepository())->updateTemplate((int) $user['tenant_id'], (int) $params['id'], $request->input()); + Response::json(['data' => $record]); + } +} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/StudentsController.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/StudentsController.php new file mode 100644 index 0000000..8323549 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/StudentsController.php @@ -0,0 +1,78 @@ +allForTenant((int) $user['tenant_id']); + $requirements = new RequirementService(); + + foreach ($students as &$student) { + $student['progress'] = $requirements->progressForStudent((int) $student['id']); + } + + Response::json(['data' => $students]); + } + + public function store(Request $request): void + { + $user = Auth::requireRole(['tenant_admin', 'dispatcher']); + $student = (new StudentRepository())->create((int) $user['tenant_id'], $request->input()); + $student['progress'] = (new RequirementService())->progressForStudent((int) $student['id']); + + Response::json(['data' => $student], 201); + } + + public function update(Request $request, array $params): void + { + $user = Auth::requireRole(['tenant_admin', 'dispatcher']); + $student = (new StudentRepository())->update((int) $user['tenant_id'], (int) $params['id'], $request->input()); + + if ($student === null) { + Response::json(['message' => 'Not found'], 404); + } + + $student['progress'] = (new RequirementService())->progressForStudent((int) $student['id']); + Response::json(['data' => $student]); + } + + public function updateTraining(Request $request, array $params): void + { + $user = Auth::requireRole(['tenant_admin', 'dispatcher']); + $student = (new StudentRepository())->updatePriorCompletedUnits( + (int) $user['tenant_id'], + (int) $params['id'], + $request->input()['rows'] ?? [] + ); + + if ($student === null) { + Response::json(['message' => 'Not found'], 404); + } + + $student['progress'] = (new RequirementService())->progressForStudent((int) $student['id']); + Response::json(['data' => $student]); + } + + public function destroy(Request $request, array $params): void + { + $user = Auth::requireRole(['tenant_admin', 'dispatcher']); + $deleted = (new StudentRepository())->delete((int) $user['tenant_id'], (int) $params['id']); + + if (!$deleted) { + Response::json(['message' => 'Not found'], 404); + } + + Response::noContent(); + } +} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/TenantsController.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/TenantsController.php new file mode 100644 index 0000000..cf238c6 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/TenantsController.php @@ -0,0 +1,40 @@ + (new TenantRepository())->all()]); + } + + public function store(Request $request): void + { + Auth::requireRole('superadmin'); + $tenant = (new TenantRepository())->create($request->input()); + Response::json(['data' => $tenant], 201); + } + + public function update(Request $request, array $params): void + { + Auth::requireRole('superadmin'); + $tenant = (new TenantRepository())->update((int) $params['id'], $request->input()); + Response::json(['data' => $tenant]); + } + + public function updateOwn(Request $request): void + { + $user = Auth::requireRole('tenant_admin'); + $tenant = (new TenantRepository())->updateForTenantAdmin((int) $user['tenant_id'], $request->input()); + Response::json(['data' => $tenant]); + } +} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/UsersController.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/UsersController.php new file mode 100644 index 0000000..364acc5 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Http/Controllers/UsersController.php @@ -0,0 +1,51 @@ + (new UserRepository())->allUsers()]); + } + + Response::json(['data' => (new UserRepository())->allForTenant((int) $user['tenant_id'])]); + } + + public function store(Request $request): void + { + $user = Auth::requireRole(['tenant_admin', 'superadmin']); + $payload = $request->input(); + if ($user['role'] !== 'superadmin') { + $payload['tenant_id'] = (int) $user['tenant_id']; + } + $record = (new UserRepository())->create($payload); + unset($record['password_hash']); + Response::json(['data' => $record], 201); + } + + public function update(Request $request, array $params): void + { + $user = Auth::requireRole(['tenant_admin', 'superadmin']); + $payload = $request->input(); + if ($user['role'] !== 'superadmin') { + $payload['tenant_id'] = (int) $user['tenant_id']; + } + $record = (new UserRepository())->update((int) $params['id'], $payload); + if ($record === null) { + Response::json(['message' => 'Not found'], 404); + } + unset($record['password_hash']); + Response::json(['data' => $record]); + } +} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/AppointmentRepository.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/AppointmentRepository.php new file mode 100644 index 0000000..e2f15aa --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/AppointmentRepository.php @@ -0,0 +1,290 @@ + :from'; + $params = [ + 'tenant_id' => $tenantId, + 'from' => $from, + 'to' => $to, + ]; + + if ($instructorId !== null) { + $sql .= ' AND a.instructor_id = :instructor_id'; + $params['instructor_id'] = $instructorId; + } + + $sql .= ' ORDER BY a.start_at'; + $statement = $this->db->prepare($sql); + $statement->execute($params); + + return $statement->fetchAll(); + } + + public function find(int $tenantId, int $id): ?array + { + $statement = $this->db->prepare('SELECT * FROM appointments WHERE tenant_id = :tenant_id AND id = :id'); + $statement->execute(['tenant_id' => $tenantId, 'id' => $id]); + $appointment = $statement->fetch() ?: null; + + if ($appointment !== null) { + $appointment['units_breakdown'] = $this->units((int) $appointment['id']); + } + + return $appointment; + } + + public function create(int $tenantId, array $data, int $actorId): array + { + $timestamp = $this->now(); + $statement = $this->db->prepare( + 'INSERT INTO appointments + (tenant_id, student_id, instructor_id, lesson_type_id, title, category, start_at, end_at, units, status, notes, warning_acknowledged, created_by, updated_by, created_at, updated_at) + VALUES + (:tenant_id, :student_id, :instructor_id, :lesson_type_id, :title, :category, :start_at, :end_at, :units, :status, :notes, :warning_acknowledged, :created_by, :updated_by, :created_at, :updated_at)' + ); + $statement->execute([ + 'tenant_id' => $tenantId, + 'student_id' => $data['student_id'] ?: null, + 'instructor_id' => $data['instructor_id'], + 'lesson_type_id' => $data['lesson_type_id'], + 'title' => $data['title'], + 'category' => $data['category'], + 'start_at' => $data['start_at'], + 'end_at' => $data['end_at'], + 'units' => (int) $data['units'], + 'status' => $data['status'], + 'notes' => $data['notes'] ?? '', + 'warning_acknowledged' => !empty($data['warning_acknowledged']) ? 1 : 0, + 'created_by' => $actorId, + 'updated_by' => $actorId, + 'created_at' => $timestamp, + 'updated_at' => $timestamp, + ]); + + $appointmentId = (int) $this->db->lastInsertId(); + $this->replaceUnits($appointmentId, $data['units_breakdown'] ?? []); + + return $this->find($tenantId, $appointmentId); + } + + public function update(int $tenantId, int $id, array $data, int $actorId): ?array + { + $statement = $this->db->prepare( + 'UPDATE appointments + SET student_id = :student_id, instructor_id = :instructor_id, lesson_type_id = :lesson_type_id, + title = :title, category = :category, start_at = :start_at, end_at = :end_at, units = :units, + status = :status, notes = :notes, warning_acknowledged = :warning_acknowledged, + updated_by = :updated_by, updated_at = :updated_at + WHERE tenant_id = :tenant_id AND id = :id' + ); + $statement->execute([ + 'tenant_id' => $tenantId, + 'id' => $id, + 'student_id' => $data['student_id'] ?: null, + 'instructor_id' => $data['instructor_id'], + 'lesson_type_id' => $data['lesson_type_id'], + 'title' => $data['title'], + 'category' => $data['category'], + 'start_at' => $data['start_at'], + 'end_at' => $data['end_at'], + 'units' => (int) $data['units'], + 'status' => $data['status'], + 'notes' => $data['notes'] ?? '', + 'warning_acknowledged' => !empty($data['warning_acknowledged']) ? 1 : 0, + 'updated_by' => $actorId, + 'updated_at' => $this->now(), + ]); + + $this->replaceUnits($id, $data['units_breakdown'] ?? []); + + return $this->find($tenantId, $id); + } + + public function delete(int $tenantId, int $id): void + { + $statement = $this->db->prepare('DELETE FROM appointments WHERE tenant_id = :tenant_id AND id = :id'); + $statement->execute(['tenant_id' => $tenantId, 'id' => $id]); + } + + public function units(int $appointmentId): array + { + $statement = $this->db->prepare('SELECT * FROM appointment_units WHERE appointment_id = :appointment_id'); + $statement->execute(['appointment_id' => $appointmentId]); + + return $statement->fetchAll(); + } + + public function overlappingForInstructor(int $tenantId, int $instructorId, string $from, string $to, ?int $ignoreId = null): array + { + $sql = 'SELECT * FROM appointments + WHERE tenant_id = :tenant_id AND instructor_id = :instructor_id + AND start_at < :to AND end_at > :from'; + $params = [ + 'tenant_id' => $tenantId, + 'instructor_id' => $instructorId, + 'from' => $from, + 'to' => $to, + ]; + + if ($ignoreId !== null) { + $sql .= ' AND id != :ignore_id'; + $params['ignore_id'] = $ignoreId; + } + + $statement = $this->db->prepare($sql); + $statement->execute($params); + + return $statement->fetchAll(); + } + + public function overlappingForStudent(int $tenantId, int $studentId, string $from, string $to, ?int $ignoreId = null): array + { + $sql = 'SELECT * FROM appointments + WHERE tenant_id = :tenant_id AND student_id = :student_id + AND start_at < :to AND end_at > :from'; + $params = [ + 'tenant_id' => $tenantId, + 'student_id' => $studentId, + 'from' => $from, + 'to' => $to, + ]; + + if ($ignoreId !== null) { + $sql .= ' AND id != :ignore_id'; + $params['ignore_id'] = $ignoreId; + } + + $statement = $this->db->prepare($sql); + $statement->execute($params); + + return $statement->fetchAll(); + } + + public function lastRestRelevantForInstructor(int $tenantId, int $instructorId, string $beforeStart, ?int $ignoreId = null): ?array + { + $sql = 'SELECT a.*, lt.name AS lesson_type_name + FROM appointments a + JOIN lesson_types lt ON lt.id = a.lesson_type_id + WHERE a.tenant_id = :tenant_id + AND a.instructor_id = :instructor_id + AND lt.is_rest_relevant = 1 + AND a.end_at <= :before_start'; + $params = [ + 'tenant_id' => $tenantId, + 'instructor_id' => $instructorId, + 'before_start' => $beforeStart, + ]; + + if ($ignoreId !== null) { + $sql .= ' AND a.id != :ignore_id'; + $params['ignore_id'] = $ignoreId; + } + + $sql .= ' ORDER BY a.end_at DESC LIMIT 1'; + $statement = $this->db->prepare($sql); + $statement->execute($params); + + return $statement->fetch() ?: null; + } + + public function hasRestRelevantAfterStartOnDay(int $tenantId, int $instructorId, string $dayStart, string $dayEnd, string $startAt): bool + { + $statement = $this->db->prepare( + 'SELECT 1 + FROM appointments a + JOIN lesson_types lt ON lt.id = a.lesson_type_id + WHERE a.tenant_id = :tenant_id + AND a.instructor_id = :instructor_id + AND lt.is_rest_relevant = 1 + AND a.start_at < :day_end + AND a.end_at > :day_start + AND a.end_at > :start_at + LIMIT 1' + ); + $statement->execute([ + 'tenant_id' => $tenantId, + 'instructor_id' => $instructorId, + 'day_start' => $dayStart, + 'day_end' => $dayEnd, + 'start_at' => $startAt, + ]); + + return (bool) $statement->fetchColumn(); + } + + public function nextForTenant(int $tenantId, string $from, ?int $instructorId = null): ?array + { + $sql = 'SELECT a.*, s.first_name AS student_first_name, s.last_name AS student_last_name, + i.first_name AS instructor_first_name, i.last_name AS instructor_last_name, + lt.name AS lesson_type_name + FROM appointments a + JOIN instructors i ON i.id = a.instructor_id + LEFT JOIN students s ON s.id = a.student_id + JOIN lesson_types lt ON lt.id = a.lesson_type_id + WHERE a.tenant_id = :tenant_id + AND a.start_at >= :from'; + $params = [ + 'tenant_id' => $tenantId, + 'from' => $from, + ]; + + if ($instructorId !== null) { + $sql .= ' AND a.instructor_id = :instructor_id'; + $params['instructor_id'] = $instructorId; + } + + $sql .= ' ORDER BY a.start_at ASC LIMIT 1'; + $statement = $this->db->prepare($sql); + $statement->execute($params); + + return $statement->fetch() ?: null; + } + + private function replaceUnits(int $appointmentId, array $rows): void + { + $delete = $this->db->prepare('DELETE FROM appointment_units WHERE appointment_id = :appointment_id'); + $delete->execute(['appointment_id' => $appointmentId]); + + $insert = $this->db->prepare( + 'INSERT INTO appointment_units (appointment_id, requirement_key, label, units_counted, created_at) + VALUES (:appointment_id, :requirement_key, :label, :units_counted, :created_at)' + ); + + foreach ($rows as $row) { + if ((int) ($row['units_counted'] ?? 0) <= 0) { + continue; + } + + $insert->execute([ + 'appointment_id' => $appointmentId, + 'requirement_key' => $row['requirement_key'] ?? null, + 'label' => $row['label'] ?? null, + 'units_counted' => (int) $row['units_counted'], + 'created_at' => $this->now(), + ]); + } + } +} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/BaseRepository.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/BaseRepository.php new file mode 100644 index 0000000..6d46ea8 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/BaseRepository.php @@ -0,0 +1,23 @@ +db = Database::connection(); + } + + protected function now(): string + { + return gmdate('c'); + } +} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/InstructorRepository.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/InstructorRepository.php new file mode 100644 index 0000000..7b13a06 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/InstructorRepository.php @@ -0,0 +1,82 @@ +db->prepare('SELECT * FROM instructors WHERE tenant_id = :tenant_id ORDER BY last_name, first_name'); + $statement->execute(['tenant_id' => $tenantId]); + + return $statement->fetchAll(); + } + + public function findByUserId(int $tenantId, int $userId): ?array + { + $statement = $this->db->prepare( + 'SELECT * FROM instructors WHERE tenant_id = :tenant_id AND user_id = :user_id LIMIT 1' + ); + $statement->execute(['tenant_id' => $tenantId, 'user_id' => $userId]); + + return $statement->fetch() ?: null; + } + + public function create(int $tenantId, array $data): array + { + $timestamp = $this->now(); + $statement = $this->db->prepare( + 'INSERT INTO instructors + (tenant_id, user_id, first_name, last_name, color, notes, is_active, pre_start_buffer_minutes, created_at, updated_at) + VALUES (:tenant_id, :user_id, :first_name, :last_name, :color, :notes, :is_active, :pre_start_buffer_minutes, :created_at, :updated_at)' + ); + $statement->execute([ + 'tenant_id' => $tenantId, + 'user_id' => $data['user_id'] ?? null, + 'first_name' => $data['first_name'], + 'last_name' => $data['last_name'], + 'color' => $data['color'], + 'notes' => $data['notes'] ?? '', + 'is_active' => !empty($data['is_active']) ? 1 : 0, + 'pre_start_buffer_minutes' => max(0, (int) ($data['pre_start_buffer_minutes'] ?? 0)), + 'created_at' => $timestamp, + 'updated_at' => $timestamp, + ]); + + return $this->find($tenantId, (int) $this->db->lastInsertId()); + } + + public function update(int $tenantId, int $id, array $data): ?array + { + $statement = $this->db->prepare( + 'UPDATE instructors + SET user_id = :user_id, first_name = :first_name, last_name = :last_name, color = :color, + notes = :notes, is_active = :is_active, pre_start_buffer_minutes = :pre_start_buffer_minutes, updated_at = :updated_at + WHERE tenant_id = :tenant_id AND id = :id' + ); + $statement->execute([ + 'tenant_id' => $tenantId, + 'id' => $id, + 'user_id' => $data['user_id'] ?? null, + 'first_name' => $data['first_name'], + 'last_name' => $data['last_name'], + 'color' => $data['color'], + 'notes' => $data['notes'] ?? '', + 'is_active' => !empty($data['is_active']) ? 1 : 0, + 'pre_start_buffer_minutes' => max(0, (int) ($data['pre_start_buffer_minutes'] ?? 0)), + 'updated_at' => $this->now(), + ]); + + return $this->find($tenantId, $id); + } + + public function find(int $tenantId, int $id): ?array + { + $statement = $this->db->prepare('SELECT * FROM instructors WHERE tenant_id = :tenant_id AND id = :id'); + $statement->execute(['tenant_id' => $tenantId, 'id' => $id]); + + return $statement->fetch() ?: null; + } +} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/ReferenceDataRepository.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/ReferenceDataRepository.php new file mode 100644 index 0000000..e1d72f0 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/ReferenceDataRepository.php @@ -0,0 +1,515 @@ + 'private_block', 'name' => 'Privat', 'color' => '#6B7280', 'default_duration' => 60, 'category' => 'private', 'sort_order' => 900], + ['system_key' => 'work_vehicle_care', 'name' => 'Fahrzeugpflege', 'color' => '#0E5D80', 'default_duration' => 60, 'category' => 'work', 'sort_order' => 910], + ['system_key' => 'work_office', 'name' => 'Buero', 'color' => '#8B5A2B', 'default_duration' => 60, 'category' => 'work', 'sort_order' => 920], + ['system_key' => 'work_transfer_drive', 'name' => 'Wechselfahrt', 'color' => '#3B7A57', 'default_duration' => 45, 'category' => 'work', 'sort_order' => 930], + ['system_key' => 'work_other', 'name' => 'Sonstige Taetigkeit', 'color' => '#7C3AED', 'default_duration' => 60, 'category' => 'work', 'sort_order' => 940], + ['system_key' => 'theory_a', 'name' => 'Theorie A', 'color' => '#C2410C', 'default_duration' => 90, 'category' => 'theory', 'sort_order' => 950], + ['system_key' => 'theory_b', 'name' => 'Theorie B', 'color' => '#A74826', 'default_duration' => 90, 'category' => 'theory', 'sort_order' => 960], + ['system_key' => 'theory_c', 'name' => 'Theorie C', 'color' => '#B45309', 'default_duration' => 90, 'category' => 'theory', 'sort_order' => 970], + ['system_key' => 'theory_d', 'name' => 'Theorie D', 'color' => '#BE123C', 'default_duration' => 90, 'category' => 'theory', 'sort_order' => 980], + ['system_key' => 'theory_t', 'name' => 'Theorie T', 'color' => '#047857', 'default_duration' => 90, 'category' => 'theory', 'sort_order' => 990], + ['system_key' => 'theory_bkf', 'name' => 'Theorie BKF', 'color' => '#1D4ED8', 'default_duration' => 90, 'category' => 'theory', 'sort_order' => 1000], + ]; + + public function requirementKeyForLessonType(array $lessonType): string + { + $key = trim((string) ($lessonType['requirement_key'] ?? '')); + return $key !== '' ? $key : 'lesson_type_' . (int) $lessonType['id']; + } + + public function lessonTypes(int $tenantId): array + { + $this->ensureDefaultPlanningTypes($tenantId); + $statement = $this->db->prepare('SELECT * FROM lesson_types WHERE tenant_id = :tenant_id ORDER BY sort_order, name'); + $statement->execute(['tenant_id' => $tenantId]); + $lessonTypes = $statement->fetchAll(); + $visibilityMap = $this->lessonTypeVisibilityMap($tenantId); + + foreach ($lessonTypes as &$lessonType) { + $lessonType['visible_template_ids'] = $visibilityMap[(int) $lessonType['id']] ?? []; + $lessonType['effective_requirement_key'] = $this->requirementKeyForLessonType($lessonType); + } + + return $lessonTypes; + } + + public function createLessonType(int $tenantId, array $data): array + { + $timestamp = $this->now(); + $statement = $this->db->prepare( + 'INSERT INTO lesson_types + (tenant_id, name, color, default_duration, category, requirement_key, is_billable, is_counted, is_rest_relevant, fixed_duration, sort_order, created_at, updated_at) + VALUES + (:tenant_id, :name, :color, :default_duration, :category, :requirement_key, :is_billable, :is_counted, :is_rest_relevant, :fixed_duration, :sort_order, :created_at, :updated_at)' + ); + $statement->execute([ + 'tenant_id' => $tenantId, + 'name' => $data['name'], + 'color' => $data['color'], + 'default_duration' => (int) $data['default_duration'], + 'category' => $data['category'], + 'requirement_key' => $data['requirement_key'] ?: null, + 'is_billable' => !empty($data['is_billable']) ? 1 : 0, + 'is_counted' => !empty($data['is_counted']) ? 1 : 0, + 'is_rest_relevant' => !empty($data['is_rest_relevant']) ? 1 : 0, + 'fixed_duration' => !empty($data['fixed_duration']) ? 1 : 0, + 'sort_order' => (int) ($data['sort_order'] ?? 0), + 'created_at' => $timestamp, + 'updated_at' => $timestamp, + ]); + $lessonTypeId = (int) $this->db->lastInsertId(); + $this->replaceLessonTypeVisibility($tenantId, $lessonTypeId, $data['visible_template_ids'] ?? null); + + return $this->findLessonType($tenantId, $lessonTypeId); + } + + public function updateLessonType(int $tenantId, int $id, array $data): ?array + { + $statement = $this->db->prepare( + 'UPDATE lesson_types + SET name = :name, color = :color, default_duration = :default_duration, category = :category, + requirement_key = :requirement_key, is_billable = :is_billable, is_counted = :is_counted, + is_rest_relevant = :is_rest_relevant, fixed_duration = :fixed_duration, sort_order = :sort_order, + updated_at = :updated_at + WHERE id = :id AND tenant_id = :tenant_id' + ); + $statement->execute([ + 'id' => $id, + 'tenant_id' => $tenantId, + 'name' => $data['name'], + 'color' => $data['color'], + 'default_duration' => (int) $data['default_duration'], + 'category' => $data['category'], + 'requirement_key' => $data['requirement_key'] ?: null, + 'is_billable' => !empty($data['is_billable']) ? 1 : 0, + 'is_counted' => !empty($data['is_counted']) ? 1 : 0, + 'is_rest_relevant' => !empty($data['is_rest_relevant']) ? 1 : 0, + 'fixed_duration' => !empty($data['fixed_duration']) ? 1 : 0, + 'sort_order' => (int) ($data['sort_order'] ?? 0), + 'updated_at' => $this->now(), + ]); + + $this->replaceLessonTypeVisibility($tenantId, $id, $data['visible_template_ids'] ?? null); + + return $this->findLessonType($tenantId, $id); + } + + public function deleteLessonType(int $tenantId, int $id): bool + { + $lessonType = $this->findLessonTypeRecord($tenantId, $id); + if ($lessonType === null) { + return false; + } + + if (($lessonType['system_key'] ?? null) === 'private_block') { + throw new \RuntimeException('Der feste Privat-Baustein kann nicht geloescht werden.'); + } + + $usage = $this->db->prepare('SELECT COUNT(*) AS count FROM appointments WHERE tenant_id = :tenant_id AND lesson_type_id = :lesson_type_id'); + $usage->execute(['tenant_id' => $tenantId, 'lesson_type_id' => $id]); + if ((int) ($usage->fetch()['count'] ?? 0) > 0) { + throw new \RuntimeException('Diese Stundenart wird bereits in Terminen verwendet und kann nicht geloescht werden.'); + } + + $this->db->beginTransaction(); + try { + $visibility = $this->db->prepare('DELETE FROM lesson_type_template_visibility WHERE lesson_type_id = :lesson_type_id'); + $visibility->execute(['lesson_type_id' => $id]); + + $delete = $this->db->prepare('DELETE FROM lesson_types WHERE tenant_id = :tenant_id AND id = :id'); + $delete->execute(['tenant_id' => $tenantId, 'id' => $id]); + + $this->db->commit(); + return true; + } catch (\Throwable $exception) { + $this->db->rollBack(); + throw $exception; + } + } + + public function reorderLessonTypes(int $tenantId, array $lessonTypeIds): array + { + $allowedIds = array_map( + static fn (array $lessonType): int => (int) $lessonType['id'], + $this->lessonTypes($tenantId) + ); + + $statement = $this->db->prepare( + 'UPDATE lesson_types + SET sort_order = :sort_order, updated_at = :updated_at + WHERE tenant_id = :tenant_id AND id = :id' + ); + + $sortOrder = 1; + foreach ($lessonTypeIds as $lessonTypeId) { + $lessonTypeId = (int) $lessonTypeId; + if (!in_array($lessonTypeId, $allowedIds, true)) { + continue; + } + + $statement->execute([ + 'sort_order' => $sortOrder, + 'updated_at' => $this->now(), + 'tenant_id' => $tenantId, + 'id' => $lessonTypeId, + ]); + $sortOrder++; + } + + return $this->lessonTypes($tenantId); + } + + public function findLessonType(int $tenantId, int $id): ?array + { + $statement = $this->db->prepare('SELECT * FROM lesson_types WHERE tenant_id = :tenant_id AND id = :id'); + $statement->execute(['tenant_id' => $tenantId, 'id' => $id]); + $lessonType = $statement->fetch() ?: null; + if ($lessonType === null) { + return null; + } + + $lessonType['visible_template_ids'] = $this->visibleTemplateIdsForLessonType($tenantId, $id); + $lessonType['effective_requirement_key'] = $this->requirementKeyForLessonType($lessonType); + + return $lessonType; + } + + public function templates(int $tenantId): array + { + $statement = $this->db->prepare( + 'SELECT * FROM license_class_templates WHERE tenant_id = :tenant_id ORDER BY name' + ); + $statement->execute(['tenant_id' => $tenantId]); + $templates = $statement->fetchAll(); + + foreach ($templates as &$template) { + $template['requirements'] = $this->templateRequirements((int) $template['id']); + $template['lesson_type_ids'] = $this->lessonTypeIdsForTemplate((int) $template['id']); + } + + return $templates; + } + + public function templateRequirements(int $templateId): array + { + $statement = $this->db->prepare( + 'SELECT * FROM license_template_requirements WHERE template_id = :template_id ORDER BY sort_order, phase_label, label' + ); + $statement->execute(['template_id' => $templateId]); + + return $statement->fetchAll(); + } + + public function createTemplate(int $tenantId, array $data): array + { + $timestamp = $this->now(); + $statement = $this->db->prepare( + 'INSERT INTO license_class_templates + (tenant_id, code, name, is_combination, created_at, updated_at) + VALUES (:tenant_id, :code, :name, :is_combination, :created_at, :updated_at)' + ); + $statement->execute([ + 'tenant_id' => $tenantId, + 'code' => $data['code'], + 'name' => $data['name'], + 'is_combination' => !empty($data['is_combination']) ? 1 : 0, + 'created_at' => $timestamp, + 'updated_at' => $timestamp, + ]); + $templateId = (int) $this->db->lastInsertId(); + + $this->replaceTemplateRequirements($templateId, $data['requirements'] ?? []); + $this->attachTemplateToExistingStudentLessonTypes($tenantId, $templateId); + + return $this->findTemplate($tenantId, $templateId); + } + + public function updateTemplate(int $tenantId, int $id, array $data): ?array + { + $statement = $this->db->prepare( + 'UPDATE license_class_templates + SET code = :code, name = :name, is_combination = :is_combination, updated_at = :updated_at + WHERE id = :id AND tenant_id = :tenant_id' + ); + $statement->execute([ + 'id' => $id, + 'tenant_id' => $tenantId, + 'code' => $data['code'], + 'name' => $data['name'], + 'is_combination' => !empty($data['is_combination']) ? 1 : 0, + 'updated_at' => $this->now(), + ]); + + $this->replaceTemplateRequirements($id, $data['requirements'] ?? []); + + return $this->findTemplate($tenantId, $id); + } + + public function updateLessonTypeMatrix(int $tenantId, array $rows): array + { + $templates = $this->templates($tenantId); + $templateIds = array_map(static fn (array $template): int => (int) $template['id'], $templates); + $allowedLessonTypeIds = array_map( + static fn (array $lessonType): int => (int) $lessonType['id'], + array_filter($this->lessonTypes($tenantId), static fn (array $lessonType): bool => $lessonType['category'] === 'student') + ); + + $delete = $this->db->prepare( + 'DELETE FROM lesson_type_template_visibility + WHERE template_id = :template_id + AND lesson_type_id IN ( + SELECT id FROM lesson_types WHERE tenant_id = :tenant_id AND category = \'student\' + )' + ); + $insert = $this->db->prepare( + 'INSERT OR IGNORE INTO lesson_type_template_visibility (lesson_type_id, template_id) + VALUES (:lesson_type_id, :template_id)' + ); + + foreach ($rows as $row) { + $templateId = (int) ($row['template_id'] ?? 0); + if (!in_array($templateId, $templateIds, true)) { + continue; + } + + $delete->execute([ + 'template_id' => $templateId, + 'tenant_id' => $tenantId, + ]); + + foreach ((array) ($row['lesson_type_ids'] ?? []) as $lessonTypeId) { + $lessonTypeId = (int) $lessonTypeId; + if (!in_array($lessonTypeId, $allowedLessonTypeIds, true)) { + continue; + } + + $insert->execute([ + 'lesson_type_id' => $lessonTypeId, + 'template_id' => $templateId, + ]); + } + } + + return $this->templates($tenantId); + } + + public function isLessonTypeVisibleForTemplate(int $tenantId, int $lessonTypeId, int $templateId): bool + { + $lessonType = $this->findLessonType($tenantId, $lessonTypeId); + if ($lessonType === null) { + return false; + } + + if ($lessonType['category'] !== 'student') { + return true; + } + + return in_array($templateId, array_map('intval', $lessonType['visible_template_ids'] ?? []), true); + } + + private function replaceTemplateRequirements(int $templateId, array $requirements): void + { + $delete = $this->db->prepare('DELETE FROM license_template_requirements WHERE template_id = :template_id'); + $delete->execute(['template_id' => $templateId]); + + $insert = $this->db->prepare( + 'INSERT INTO license_template_requirements + (template_id, requirement_key, label, required_units, sort_order, phase_label) + VALUES (:template_id, :requirement_key, :label, :required_units, :sort_order, :phase_label)' + ); + + foreach ($requirements as $index => $requirement) { + if (($requirement['label'] ?? '') === '') { + continue; + } + + $insert->execute([ + 'template_id' => $templateId, + 'requirement_key' => $requirement['requirement_key'], + 'label' => $requirement['label'], + 'required_units' => (int) $requirement['required_units'], + 'sort_order' => (int) ($requirement['sort_order'] ?? $index), + 'phase_label' => trim((string) ($requirement['phase_label'] ?? '')), + ]); + } + } + + public function findTemplate(int $tenantId, int $id): ?array + { + $statement = $this->db->prepare( + 'SELECT * FROM license_class_templates WHERE tenant_id = :tenant_id AND id = :id' + ); + $statement->execute(['tenant_id' => $tenantId, 'id' => $id]); + $template = $statement->fetch() ?: null; + + if ($template !== null) { + $template['requirements'] = $this->templateRequirements($id); + $template['lesson_type_ids'] = $this->lessonTypeIdsForTemplate($id); + } + + return $template; + } + + private function replaceLessonTypeVisibility(int $tenantId, int $lessonTypeId, ?array $visibleTemplateIds): void + { + $lessonType = $this->findLessonTypeRecord($tenantId, $lessonTypeId); + if ($lessonType === null) { + return; + } + + $delete = $this->db->prepare('DELETE FROM lesson_type_template_visibility WHERE lesson_type_id = :lesson_type_id'); + $delete->execute(['lesson_type_id' => $lessonTypeId]); + + if ($lessonType['category'] !== 'student') { + return; + } + + $templateIds = $visibleTemplateIds ?? $this->templateIdsForTenant($tenantId); + $templateIds = array_values(array_unique(array_map('intval', $templateIds))); + if ($templateIds === []) { + return; + } + + $allowedTemplateIds = $this->templateIdsForTenant($tenantId); + $insert = $this->db->prepare( + 'INSERT OR IGNORE INTO lesson_type_template_visibility (lesson_type_id, template_id) + VALUES (:lesson_type_id, :template_id)' + ); + + foreach ($templateIds as $templateId) { + if (!in_array($templateId, $allowedTemplateIds, true)) { + continue; + } + + $insert->execute([ + 'lesson_type_id' => $lessonTypeId, + 'template_id' => $templateId, + ]); + } + } + + private function lessonTypeVisibilityMap(int $tenantId): array + { + $statement = $this->db->prepare( + 'SELECT lt.id AS lesson_type_id, vis.template_id + FROM lesson_types lt + LEFT JOIN lesson_type_template_visibility vis ON vis.lesson_type_id = lt.id + WHERE lt.tenant_id = :tenant_id AND lt.category = \'student\' + ORDER BY vis.template_id' + ); + $statement->execute(['tenant_id' => $tenantId]); + + $map = []; + foreach ($statement->fetchAll() as $row) { + $lessonTypeId = (int) $row['lesson_type_id']; + $map[$lessonTypeId] ??= []; + if ($row['template_id'] !== null) { + $map[$lessonTypeId][] = (int) $row['template_id']; + } + } + + return $map; + } + + private function visibleTemplateIdsForLessonType(int $tenantId, int $lessonTypeId): array + { + $lessonType = $this->findLessonTypeRecord($tenantId, $lessonTypeId); + if ($lessonType === null || $lessonType['category'] !== 'student') { + return []; + } + + $statement = $this->db->prepare( + 'SELECT template_id FROM lesson_type_template_visibility + WHERE lesson_type_id = :lesson_type_id + ORDER BY template_id' + ); + $statement->execute(['lesson_type_id' => $lessonTypeId]); + + return array_map( + static fn (array $row): int => (int) $row['template_id'], + $statement->fetchAll() + ); + } + + private function lessonTypeIdsForTemplate(int $templateId): array + { + $statement = $this->db->prepare( + 'SELECT lesson_type_id FROM lesson_type_template_visibility + WHERE template_id = :template_id + ORDER BY lesson_type_id' + ); + $statement->execute(['template_id' => $templateId]); + + return array_map( + static fn (array $row): int => (int) $row['lesson_type_id'], + $statement->fetchAll() + ); + } + + private function templateIdsForTenant(int $tenantId): array + { + $statement = $this->db->prepare('SELECT id FROM license_class_templates WHERE tenant_id = :tenant_id ORDER BY id'); + $statement->execute(['tenant_id' => $tenantId]); + + return array_map( + static fn (array $row): int => (int) $row['id'], + $statement->fetchAll() + ); + } + + private function attachTemplateToExistingStudentLessonTypes(int $tenantId, int $templateId): void + { + $statement = $this->db->prepare( + 'INSERT OR IGNORE INTO lesson_type_template_visibility (lesson_type_id, template_id) + SELECT id, :template_id FROM lesson_types + WHERE tenant_id = :tenant_id AND category = \'student\'' + ); + $statement->execute([ + 'template_id' => $templateId, + 'tenant_id' => $tenantId, + ]); + } + + private function findLessonTypeRecord(int $tenantId, int $id): ?array + { + $statement = $this->db->prepare('SELECT * FROM lesson_types WHERE tenant_id = :tenant_id AND id = :id'); + $statement->execute(['tenant_id' => $tenantId, 'id' => $id]); + + return $statement->fetch() ?: null; + } + + private function ensureDefaultPlanningTypes(int $tenantId): void + { + $statement = $this->db->prepare( + 'INSERT OR IGNORE INTO lesson_types + (tenant_id, name, color, default_duration, category, requirement_key, is_billable, is_counted, is_rest_relevant, fixed_duration, sort_order, system_key, created_at, updated_at) + VALUES + (:tenant_id, :name, :color, :default_duration, :category, NULL, 0, 0, :is_rest_relevant, 0, :sort_order, :system_key, :created_at, :updated_at)' + ); + + $timestamp = $this->now(); + foreach (self::DEFAULT_PLANNING_TYPES as $row) { + $statement->execute([ + 'tenant_id' => $tenantId, + 'name' => $row['name'], + 'color' => $row['color'], + 'default_duration' => $row['default_duration'], + 'category' => $row['category'], + 'is_rest_relevant' => $row['category'] === 'private' ? 0 : 1, + 'sort_order' => $row['sort_order'], + 'system_key' => $row['system_key'], + 'created_at' => $timestamp, + 'updated_at' => $timestamp, + ]); + } + } +} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/StudentRepository.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/StudentRepository.php new file mode 100644 index 0000000..4ad7894 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/StudentRepository.php @@ -0,0 +1,294 @@ +db->prepare( + 'SELECT s.*, t.code AS template_code, t.name AS template_name, t.is_combination AS template_is_combination + FROM students s + JOIN license_class_templates t ON t.id = s.template_id + WHERE s.tenant_id = :tenant_id + ORDER BY s.last_name, s.first_name' + ); + $statement->execute(['tenant_id' => $tenantId]); + + return $statement->fetchAll(); + } + + public function find(int $tenantId, int $id): ?array + { + $statement = $this->db->prepare( + 'SELECT s.*, t.code AS template_code, t.name AS template_name, t.is_combination AS template_is_combination + FROM students s + JOIN license_class_templates t ON t.id = s.template_id + WHERE s.tenant_id = :tenant_id AND s.id = :id' + ); + $statement->execute(['tenant_id' => $tenantId, 'id' => $id]); + $student = $statement->fetch() ?: null; + + if ($student !== null) { + $student['requirements'] = $this->requirements($id); + } + + return $student; + } + + public function create(int $tenantId, array $data): array + { + $timestamp = $this->now(); + $statement = $this->db->prepare( + 'INSERT INTO students + (tenant_id, template_id, first_name, last_name, phone, email, needs_glasses, notes, status, created_at, updated_at) + VALUES (:tenant_id, :template_id, :first_name, :last_name, :phone, :email, :needs_glasses, :notes, :status, :created_at, :updated_at)' + ); + $statement->execute([ + 'tenant_id' => $tenantId, + 'template_id' => $data['template_id'], + 'first_name' => $data['first_name'], + 'last_name' => $data['last_name'], + 'phone' => $data['phone'] ?? '', + 'email' => $data['email'] ?? '', + 'needs_glasses' => !empty($data['needs_glasses']) ? 1 : 0, + 'notes' => $data['notes'] ?? '', + 'status' => $data['status'] ?? 'active', + 'created_at' => $timestamp, + 'updated_at' => $timestamp, + ]); + + $studentId = (int) $this->db->lastInsertId(); + $this->snapshotRequirements($studentId, (int) $data['template_id']); + + return $this->find($tenantId, $studentId); + } + + public function update(int $tenantId, int $id, array $data): ?array + { + $statement = $this->db->prepare( + 'UPDATE students + SET template_id = :template_id, first_name = :first_name, last_name = :last_name, phone = :phone, email = :email, needs_glasses = :needs_glasses, + notes = :notes, status = :status, updated_at = :updated_at + WHERE tenant_id = :tenant_id AND id = :id' + ); + $statement->execute([ + 'tenant_id' => $tenantId, + 'id' => $id, + 'template_id' => $data['template_id'], + 'first_name' => $data['first_name'], + 'last_name' => $data['last_name'], + 'phone' => $data['phone'] ?? '', + 'email' => $data['email'] ?? '', + 'needs_glasses' => !empty($data['needs_glasses']) ? 1 : 0, + 'notes' => $data['notes'] ?? '', + 'status' => $data['status'] ?? 'active', + 'updated_at' => $this->now(), + ]); + + return $this->find($tenantId, $id); + } + + public function delete(int $tenantId, int $id): bool + { + $student = $this->find($tenantId, $id); + if ($student === null) { + return false; + } + + $this->db->beginTransaction(); + try { + $updateAppointments = $this->db->prepare( + 'UPDATE appointments SET student_id = NULL, updated_at = :updated_at WHERE tenant_id = :tenant_id AND student_id = :student_id' + ); + $updateAppointments->execute([ + 'tenant_id' => $tenantId, + 'student_id' => $id, + 'updated_at' => $this->now(), + ]); + + $deleteSnapshots = $this->db->prepare('DELETE FROM student_requirement_snapshots WHERE student_id = :student_id'); + $deleteSnapshots->execute(['student_id' => $id]); + + $deleteStudent = $this->db->prepare('DELETE FROM students WHERE tenant_id = :tenant_id AND id = :id'); + $deleteStudent->execute(['tenant_id' => $tenantId, 'id' => $id]); + + $this->db->commit(); + return true; + } catch (\Throwable $exception) { + $this->db->rollBack(); + throw $exception; + } + } + + public function requirements(int $studentId): array + { + $this->syncRequirementSnapshots($studentId); + + $statement = $this->db->prepare( + 'SELECT * FROM student_requirement_snapshots WHERE student_id = :student_id ORDER BY sort_order, phase_label, label' + ); + $statement->execute(['student_id' => $studentId]); + + return $statement->fetchAll(); + } + + public function updatePriorCompletedUnits(int $tenantId, int $studentId, array $rows): ?array + { + $student = $this->find($tenantId, $studentId); + if ($student === null) { + return null; + } + + $statement = $this->db->prepare( + 'UPDATE student_requirement_snapshots + SET prior_completed_units = :prior_completed_units + WHERE student_id = :student_id AND requirement_key = :requirement_key' + ); + + foreach ($rows as $row) { + $statement->execute([ + 'student_id' => $studentId, + 'requirement_key' => (string) ($row['requirement_key'] ?? ''), + 'prior_completed_units' => max(0, (int) ($row['prior_completed_units'] ?? 0)), + ]); + } + + return $this->find($tenantId, $studentId); + } + + private function snapshotRequirements(int $studentId, int $templateId): void + { + $this->syncRequirementSnapshots($studentId, $templateId); + } + + private function syncRequirementSnapshots(int $studentId, ?int $templateId = null): void + { + if ($templateId === null) { + $student = $this->db->prepare('SELECT template_id FROM students WHERE id = :student_id'); + $student->execute(['student_id' => $studentId]); + $templateId = (int) ($student->fetch()['template_id'] ?? 0); + } + + if ($templateId <= 0) { + return; + } + + $requirements = $this->db->prepare( + 'SELECT requirement_key, label, required_units, sort_order, phase_label + FROM license_template_requirements WHERE template_id = :template_id' + ); + $requirements->execute(['template_id' => $templateId]); + $rows = []; + + foreach ($requirements->fetchAll() as $row) { + $rows[$row['requirement_key']] = $row; + } + $hasPhasedRequirements = array_reduce( + $rows, + static fn (bool $carry, array $row): bool => $carry || trim((string) ($row['phase_label'] ?? '')) !== '', + false + ); + + $lessonTypes = $this->db->prepare( + 'SELECT COALESCE(NULLIF(TRIM(lt.requirement_key), \'\'), \'lesson_type_\' || lt.id) AS requirement_key, + lt.name AS label, + lt.sort_order + FROM lesson_types lt + JOIN lesson_type_template_visibility vis ON vis.lesson_type_id = lt.id + WHERE vis.template_id = :template_id + AND lt.category = \'student\' + AND lt.is_counted = 1 + ORDER BY lt.sort_order, lt.name' + ); + $lessonTypes->execute(['template_id' => $templateId]); + + foreach ($lessonTypes->fetchAll() as $row) { + if (isset($rows[$row['requirement_key']])) { + continue; + } + + if ($hasPhasedRequirements) { + $hasMatchingPhasedRequirement = false; + foreach ($rows as $existingRow) { + $phaseLabel = trim((string) ($existingRow['phase_label'] ?? '')); + if ($phaseLabel === '') { + continue; + } + + if (str_ends_with((string) $existingRow['requirement_key'], '::' . $row['requirement_key'])) { + $hasMatchingPhasedRequirement = true; + break; + } + } + + if ($hasMatchingPhasedRequirement) { + continue; + } + } + + $rows[$row['requirement_key']] = [ + 'requirement_key' => $row['requirement_key'], + 'label' => $row['label'], + 'required_units' => 0, + 'sort_order' => $row['sort_order'], + ]; + } + + $insert = $this->db->prepare( + 'INSERT INTO student_requirement_snapshots + (student_id, requirement_key, label, required_units, prior_completed_units, sort_order, phase_label) + SELECT :student_id, :requirement_key, :label, :required_units, 0, :sort_order, :phase_label + WHERE NOT EXISTS ( + SELECT 1 FROM student_requirement_snapshots + WHERE student_id = :student_id AND requirement_key = :requirement_key + )' + ); + $update = $this->db->prepare( + 'UPDATE student_requirement_snapshots + SET label = :label, + required_units = :required_units, + sort_order = :sort_order, + phase_label = :phase_label + WHERE student_id = :student_id AND requirement_key = :requirement_key' + ); + + foreach ($rows as $row) { + $insert->execute([ + 'student_id' => $studentId, + 'requirement_key' => $row['requirement_key'], + 'label' => $row['label'], + 'required_units' => $row['required_units'], + 'sort_order' => $row['sort_order'], + 'phase_label' => $row['phase_label'] ?? '', + ]); + $update->execute([ + 'student_id' => $studentId, + 'requirement_key' => $row['requirement_key'], + 'label' => $row['label'], + 'required_units' => $row['required_units'], + 'sort_order' => $row['sort_order'], + 'phase_label' => $row['phase_label'] ?? '', + ]); + } + + if ($rows !== []) { + $placeholders = implode(', ', array_fill(0, count($rows), '?')); + $delete = $this->db->prepare( + "DELETE FROM student_requirement_snapshots + WHERE student_id = ? + AND requirement_key NOT IN ($placeholders) + AND prior_completed_units = 0 + AND NOT EXISTS ( + SELECT 1 FROM appointment_units au + JOIN appointments a ON a.id = au.appointment_id + WHERE a.student_id = student_requirement_snapshots.student_id + AND au.requirement_key = student_requirement_snapshots.requirement_key + )" + ); + $delete->execute(array_merge([$studentId], array_keys($rows))); + } + } +} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/TenantRepository.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/TenantRepository.php new file mode 100644 index 0000000..3c71a02 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/TenantRepository.php @@ -0,0 +1,86 @@ +db->query('SELECT * FROM tenants ORDER BY name')->fetchAll(); + } + + public function find(int $id): ?array + { + $statement = $this->db->prepare('SELECT * FROM tenants WHERE id = :id'); + $statement->execute(['id' => $id]); + + return $statement->fetch() ?: null; + } + + public function create(array $data): array + { + $timestamp = $this->now(); + $statement = $this->db->prepare( + 'INSERT INTO tenants (name, timezone, federal_state, location_label, latitude, longitude, is_active, created_at, updated_at) + VALUES (:name, :timezone, :federal_state, :location_label, :latitude, :longitude, :is_active, :created_at, :updated_at)' + ); + $statement->execute([ + 'name' => $data['name'], + 'timezone' => $data['timezone'] ?? 'Europe/Berlin', + 'federal_state' => $data['federal_state'] ?? 'BE', + 'location_label' => $data['location_label'] ?? '', + 'latitude' => $data['latitude'] !== '' ? $data['latitude'] : null, + 'longitude' => $data['longitude'] !== '' ? $data['longitude'] : null, + 'is_active' => !empty($data['is_active']) ? 1 : 0, + 'created_at' => $timestamp, + 'updated_at' => $timestamp, + ]); + + return $this->find((int) $this->db->lastInsertId()); + } + + public function update(int $id, array $data): ?array + { + $statement = $this->db->prepare( + 'UPDATE tenants + SET name = :name, timezone = :timezone, federal_state = :federal_state, location_label = :location_label, + latitude = :latitude, longitude = :longitude, is_active = :is_active, updated_at = :updated_at + WHERE id = :id' + ); + $statement->execute([ + 'id' => $id, + 'name' => $data['name'], + 'timezone' => $data['timezone'], + 'federal_state' => $data['federal_state'] ?? 'BE', + 'location_label' => $data['location_label'] ?? '', + 'latitude' => $data['latitude'] !== '' ? $data['latitude'] : null, + 'longitude' => $data['longitude'] !== '' ? $data['longitude'] : null, + 'is_active' => !empty($data['is_active']) ? 1 : 0, + 'updated_at' => $this->now(), + ]); + + return $this->find($id); + } + + public function updateForTenantAdmin(int $id, array $data): ?array + { + $current = $this->find($id); + if ($current === null) { + return null; + } + + return $this->update($id, [ + 'name' => $data['name'] ?? $current['name'], + 'timezone' => $data['timezone'] ?? $current['timezone'], + 'federal_state' => $data['federal_state'] ?? ($current['federal_state'] ?? 'BE'), + 'location_label' => $data['location_label'] ?? ($current['location_label'] ?? ''), + 'latitude' => array_key_exists('latitude', $data) ? $data['latitude'] : ($current['latitude'] ?? null), + 'longitude' => array_key_exists('longitude', $data) ? $data['longitude'] : ($current['longitude'] ?? null), + 'is_active' => $current['is_active'], + ]); + } +} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/UserRepository.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/UserRepository.php new file mode 100644 index 0000000..9e0664d --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Repositories/UserRepository.php @@ -0,0 +1,112 @@ +db->prepare( + 'SELECT id, tenant_id, role, email, first_name, last_name, is_active, created_at, updated_at + FROM users WHERE tenant_id = :tenant_id ORDER BY role, last_name, first_name' + ); + $statement->execute(['tenant_id' => $tenantId]); + + return $statement->fetchAll(); + } + + public function allUsers(): array + { + return $this->db->query( + 'SELECT u.id, u.tenant_id, u.role, u.email, u.first_name, u.last_name, u.is_active, + u.created_at, u.updated_at, t.name AS tenant_name + FROM users u + LEFT JOIN tenants t ON t.id = u.tenant_id + ORDER BY COALESCE(t.name, "System"), u.role, u.last_name, u.first_name' + )->fetchAll(); + } + + public function findByEmail(string $email): ?array + { + $statement = $this->db->prepare( + 'SELECT u.*, t.name AS tenant_name, t.timezone AS tenant_timezone, t.is_active AS tenant_is_active + FROM users u + LEFT JOIN tenants t ON t.id = u.tenant_id + WHERE u.email = :email' + ); + $statement->execute(['email' => $email]); + + return $statement->fetch() ?: null; + } + + public function findById(int $id): ?array + { + $statement = $this->db->prepare( + 'SELECT u.*, t.name AS tenant_name, t.timezone AS tenant_timezone, t.is_active AS tenant_is_active + FROM users u + LEFT JOIN tenants t ON t.id = u.tenant_id + WHERE u.id = :id' + ); + $statement->execute(['id' => $id]); + + return $statement->fetch() ?: null; + } + + public function create(array $data): array + { + $timestamp = $this->now(); + $statement = $this->db->prepare( + 'INSERT INTO users + (tenant_id, role, email, password_hash, first_name, last_name, is_active, created_at, updated_at) + VALUES (:tenant_id, :role, :email, :password_hash, :first_name, :last_name, :is_active, :created_at, :updated_at)' + ); + $statement->execute([ + 'tenant_id' => $data['tenant_id'], + 'role' => $data['role'], + 'email' => $data['email'], + 'password_hash' => password_hash($data['password'], PASSWORD_DEFAULT), + 'first_name' => $data['first_name'], + 'last_name' => $data['last_name'], + 'is_active' => !empty($data['is_active']) ? 1 : 0, + 'created_at' => $timestamp, + 'updated_at' => $timestamp, + ]); + + return $this->findById((int) $this->db->lastInsertId()); + } + + public function update(int $id, array $data): ?array + { + $current = $this->findById($id); + if ($current === null) { + return null; + } + + $passwordHash = $current['password_hash']; + if (!empty($data['password'])) { + $passwordHash = password_hash($data['password'], PASSWORD_DEFAULT); + } + + $statement = $this->db->prepare( + 'UPDATE users + SET tenant_id = :tenant_id, role = :role, email = :email, password_hash = :password_hash, + first_name = :first_name, last_name = :last_name, is_active = :is_active, updated_at = :updated_at + WHERE id = :id' + ); + $statement->execute([ + 'id' => $id, + 'tenant_id' => $data['tenant_id'], + 'role' => $data['role'], + 'email' => $data['email'], + 'password_hash' => $passwordHash, + 'first_name' => $data['first_name'], + 'last_name' => $data['last_name'], + 'is_active' => !empty($data['is_active']) ? 1 : 0, + 'updated_at' => $this->now(), + ]); + + return $this->findById($id); + } +} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Services/CalendarService.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Services/CalendarService.php new file mode 100644 index 0000000..76fcbd0 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Services/CalendarService.php @@ -0,0 +1,93 @@ +findLessonType($tenantId, (int) $payload['lesson_type_id']); + $warnings = [ + 'conflicts' => [], + 'rest_period_warnings' => [], + ]; + + $conflicts = $appointmentRepository->overlappingForInstructor( + $tenantId, + (int) $payload['instructor_id'], + $payload['start_at'], + $payload['end_at'], + $appointmentId + ); + + foreach ($conflicts as $conflict) { + $warnings['conflicts'][] = [ + 'type' => 'instructor_overlap', + 'message' => 'Fahrlehrer ist in diesem Zeitraum bereits verplant.', + 'appointment_id' => (int) $conflict['id'], + ]; + } + + if (!empty($payload['student_id'])) { + $studentConflicts = $appointmentRepository->overlappingForStudent( + $tenantId, + (int) $payload['student_id'], + $payload['start_at'], + $payload['end_at'], + $appointmentId + ); + foreach ($studentConflicts as $conflict) { + $warnings['conflicts'][] = [ + 'type' => 'student_overlap', + 'message' => 'Fahrschueler ist in diesem Zeitraum bereits verplant.', + 'appointment_id' => (int) $conflict['id'], + ]; + } + } + + if ($lessonType !== null && (int) $lessonType['is_rest_relevant'] === 1) { + $newStart = new DateTimeImmutable($payload['start_at']); + $dayStart = $newStart->setTime(0, 0, 0); + $dayEnd = $dayStart->modify('+1 day'); + $hasLaterRelevantOnDay = $appointmentRepository->hasRestRelevantAfterStartOnDay( + $tenantId, + (int) $payload['instructor_id'], + $dayStart->format(DATE_ATOM), + $dayEnd->format(DATE_ATOM), + $payload['start_at'] + ); + + if ($hasLaterRelevantOnDay) { + return $warnings; + } + + $lastRelevant = $appointmentRepository->lastRestRelevantForInstructor( + $tenantId, + (int) $payload['instructor_id'], + $payload['start_at'], + $appointmentId + ); + if ($lastRelevant !== null) { + $restEnds = (new DateTimeImmutable($lastRelevant['end_at']))->add(new DateInterval('PT11H')); + if ($newStart < $restEnds) { + $warnings['rest_period_warnings'][] = [ + 'type' => 'rest_period', + 'message' => 'Achtung: Dieser Termin liegt innerhalb der 11-Stunden-Ruhezeit.', + 'rest_until' => $restEnds->format(DATE_ATOM), + 'blocking_appointment_id' => (int) $lastRelevant['id'], + ]; + } + } + } + + return $warnings; + } +} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Services/HolidayService.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Services/HolidayService.php new file mode 100644 index 0000000..1b6d66b --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Services/HolidayService.php @@ -0,0 +1,109 @@ +setTimezone($timezone)->setTime(0, 0); + $end = (new DateTimeImmutable($to, new DateTimeZone('UTC')))->setTimezone($timezone)->setTime(0, 0); + + $years = []; + $day = $start; + while ($day <= $end) { + $years[(int) $day->format('Y')] = true; + $day = $day->add(new DateInterval('P1D')); + } + + $holidaysByDate = []; + foreach (array_keys($years) as $year) { + foreach ($this->holidaysForYear((int) $year, $state, $timezone) as $date => $name) { + $holidaysByDate[$date] = $name; + } + } + + $result = []; + $day = $start; + while ($day < $end) { + $key = $day->format('Y-m-d'); + $result[$key] = [ + 'isSunday' => (int) $day->format('N') === 7, + 'holidayName' => $holidaysByDate[$key] ?? null, + ]; + $day = $day->add(new DateInterval('P1D')); + } + + return $result; + } + + private function holidaysForYear(int $year, string $state, DateTimeZone $timezone): array + { + $easter = (new DateTimeImmutable('@' . (string) easter_date($year)))->setTimezone($timezone)->setTime(0, 0); + $holidays = [ + $this->dateKey($year, 1, 1, $timezone) => 'Neujahr', + $easter->sub(new DateInterval('P2D'))->format('Y-m-d') => 'Karfreitag', + $easter->add(new DateInterval('P1D'))->format('Y-m-d') => 'Ostermontag', + $this->dateKey($year, 5, 1, $timezone) => 'Tag der Arbeit', + $easter->add(new DateInterval('P39D'))->format('Y-m-d') => 'Christi Himmelfahrt', + $easter->add(new DateInterval('P50D'))->format('Y-m-d') => 'Pfingstmontag', + $this->dateKey($year, 10, 3, $timezone) => 'Tag der Deutschen Einheit', + $this->dateKey($year, 12, 25, $timezone) => '1. Weihnachtstag', + $this->dateKey($year, 12, 26, $timezone) => '2. Weihnachtstag', + ]; + + if (in_array($state, self::STATES_WITH_EPIPHANY, true)) { + $holidays[$this->dateKey($year, 1, 6, $timezone)] = 'Heilige Drei Koenige'; + } + if (in_array($state, self::STATES_WITH_WOMENS_DAY, true)) { + $holidays[$this->dateKey($year, 3, 8, $timezone)] = 'Internationaler Frauentag'; + } + if (in_array($state, self::STATES_WITH_CORPUS_CHRISTI, true)) { + $holidays[$easter->add(new DateInterval('P60D'))->format('Y-m-d')] = 'Fronleichnam'; + } + if (in_array($state, self::STATES_WITH_ASSUMPTION, true)) { + $holidays[$this->dateKey($year, 8, 15, $timezone)] = 'Mariae Himmelfahrt'; + } + if (in_array($state, self::STATES_WITH_REFORMATION, true)) { + $holidays[$this->dateKey($year, 10, 31, $timezone)] = 'Reformationstag'; + } + if (in_array($state, self::STATES_WITH_ALL_SAINTS, true)) { + $holidays[$this->dateKey($year, 11, 1, $timezone)] = 'Allerheiligen'; + } + if (in_array($state, self::STATES_WITH_REPENTANCE, true)) { + $holidays[$this->repentanceDay($year, $timezone)->format('Y-m-d')] = 'Buss- und Bettag'; + } + + return $holidays; + } + + private function dateKey(int $year, int $month, int $day, DateTimeZone $timezone): string + { + return (new DateTimeImmutable(sprintf('%04d-%02d-%02d', $year, $month, $day), $timezone))->format('Y-m-d'); + } + + private function repentanceDay(int $year, DateTimeZone $timezone): DateTimeImmutable + { + $day = new DateTimeImmutable(sprintf('%04d-11-23', $year), $timezone); + while ((int) $day->format('N') !== 3) { + $day = $day->sub(new DateInterval('P1D')); + } + return $day; + } +} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Services/RequirementService.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Services/RequirementService.php new file mode 100644 index 0000000..fa646fc --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Services/RequirementService.php @@ -0,0 +1,56 @@ +requirements($studentId); + + $statement = $db->prepare( + 'SELECT au.requirement_key, + SUM(CASE WHEN a.status = "planned" THEN au.units_counted ELSE 0 END) AS planned_units, + SUM(CASE WHEN a.status = "completed" THEN au.units_counted ELSE 0 END) AS completed_units + FROM appointment_units au + JOIN appointments a ON a.id = au.appointment_id + WHERE a.student_id = :student_id + GROUP BY au.requirement_key' + ); + $statement->execute(['student_id' => $studentId]); + $totals = []; + foreach ($statement->fetchAll() as $row) { + $totals[$row['requirement_key']] = [ + 'planned' => (int) $row['planned_units'], + 'completed' => (int) $row['completed_units'], + ]; + } + + return array_map(static function (array $snapshot) use ($totals): array { + $row = $totals[$snapshot['requirement_key']] ?? ['planned' => 0, 'completed' => 0]; + $priorCompleted = (int) ($snapshot['prior_completed_units'] ?? 0); + $appointmentCompleted = (int) $row['completed']; + $completed = $priorCompleted + $appointmentCompleted; + $open = (int) $snapshot['required_units'] - $row['planned'] - $completed; + + return [ + 'requirement_key' => $snapshot['requirement_key'], + 'phase_label' => $snapshot['phase_label'] ?? '', + 'label' => $snapshot['label'], + 'required_units' => (int) $snapshot['required_units'], + 'planned_units' => $row['planned'], + 'completed_units' => $completed, + 'prior_completed_units' => $priorCompleted, + 'appointment_completed_units' => $appointmentCompleted, + 'open_units' => $open, + ]; + }, $snapshots); + } +} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Services/SunTimesService.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Services/SunTimesService.php new file mode 100644 index 0000000..041fef0 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Services/SunTimesService.php @@ -0,0 +1,55 @@ +setTimezone($timezone)->setTime(0, 0); + $lastDay = $end->setTimezone($timezone)->setTime(0, 0); + + $result = []; + while ($day < $lastDay) { + $solar = date_sun_info($day->setTime(12, 0)->getTimestamp(), $latitude, $longitude); + $key = $day->format('Y-m-d'); + + $result[$key] = [ + 'sunrise' => $this->formatSolarTimestamp($solar['sunrise'] ?? false, $timezone), + 'sunset' => $this->formatSolarTimestamp($solar['sunset'] ?? false, $timezone), + ]; + + $day = $day->add(new DateInterval('P1D')); + } + + return $result; + } + + private function formatSolarTimestamp(int|float|bool $timestamp, DateTimeZone $timezone): ?string + { + if (!is_int($timestamp) && !is_float($timestamp)) { + return null; + } + + return (new DateTimeImmutable('@' . (string) (int) $timestamp)) + ->setTimezone($timezone) + ->format('H:i'); + } +} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Support/Auth.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Support/Auth.php new file mode 100644 index 0000000..488c54f --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Support/Auth.php @@ -0,0 +1,42 @@ +findById((int) $userId); + } + + public static function requireUser(): array + { + $user = self::user(); + if ($user === null) { + Response::json(['message' => 'Unauthenticated'], 401); + } + + return $user; + } + + public static function requireRole(array|string $roles): array + { + $user = self::requireUser(); + $roles = (array) $roles; + + if (!in_array($user['role'], $roles, true)) { + Response::json(['message' => 'Forbidden'], 403); + } + + return $user; + } +} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Support/Database.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Support/Database.php new file mode 100644 index 0000000..c895c52 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Support/Database.php @@ -0,0 +1,37 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + self::$connection->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + self::$connection->exec('PRAGMA foreign_keys = ON'); + + return self::$connection; + } +} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Support/Request.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Support/Request.php new file mode 100644 index 0000000..ca66565 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/Support/Request.php @@ -0,0 +1,40 @@ +map('GET', $path, $handler); + } + + public function post(string $path, callable $handler): void + { + $this->map('POST', $path, $handler); + } + + public function patch(string $path, callable $handler): void + { + $this->map('PATCH', $path, $handler); + } + + public function delete(string $path, callable $handler): void + { + $this->map('DELETE', $path, $handler); + } + + public function dispatch(): void + { + $method = $this->request->method(); + $path = $this->request->path(); + + foreach ($this->routes[$method] ?? [] as $route) { + $pattern = preg_replace('#\{([a-zA-Z_][a-zA-Z0-9_]*)\}#', '(?P<$1>[^/]+)', $route['path']); + $pattern = '#^' . $pattern . '$#'; + + if (!preg_match($pattern, $path, $matches)) { + continue; + } + + $params = array_filter($matches, static fn (string|int $key): bool => is_string($key), ARRAY_FILTER_USE_KEY); + $route['handler']($this->request, $params); + return; + } + + Response::json(['message' => 'Route not found', 'path' => $path], 404); + } + + private function map(string $method, string $path, callable $handler): void + { + $this->routes[$method][] = [ + 'path' => rtrim($path, '/') ?: '/', + 'handler' => $handler, + ]; + } +} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/bootstrap.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/bootstrap.php new file mode 100644 index 0000000..044e7f1 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/app/bootstrap.php @@ -0,0 +1,44 @@ + $config, + 'router' => new Router(new Request()), +]; diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/bin/migrate.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/bin/migrate.php new file mode 100644 index 0000000..33bc9d5 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/bin/migrate.php @@ -0,0 +1,27 @@ +query('PRAGMA table_info(' . $matches[1] . ')')->fetchAll(); + $columnNames = array_map(static fn (array $column): string => strtolower((string) $column['name']), $columns); + if (in_array(strtolower($matches[2]), $columnNames, true)) { + continue; + } + } + + $db->exec($statement); + } + fwrite(STDOUT, "Applied: " . basename($migration) . PHP_EOL); +} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/bin/seed.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/bin/seed.php new file mode 100644 index 0000000..38babcb --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/bin/seed.php @@ -0,0 +1,208 @@ +query('SELECT COUNT(*) AS count FROM users')->fetch(); +if ((int) $existing['count'] > 0) { + fwrite(STDOUT, "Seed skipped: data already exists." . PHP_EOL); + exit(0); +} + +$tenant = $tenantRepository->create([ + 'name' => 'Fahrschule Nordring', + 'timezone' => 'Europe/Berlin', + 'is_active' => true, +]); + +$db->prepare('INSERT INTO settings (tenant_id, key_name, value) VALUES (:tenant_id, :key_name, :value)') + ->execute([ + 'tenant_id' => $tenant['id'], + 'key_name' => 'app.theme', + 'value' => 'signal', + ]); + +$superadmin = $userRepository->create([ + 'tenant_id' => null, + 'role' => 'superadmin', + 'email' => 'superadmin@example.com', + 'password' => 'secret123', + 'first_name' => 'System', + 'last_name' => 'Admin', + 'is_active' => true, +]); + +$tenantAdmin = $userRepository->create([ + 'tenant_id' => $tenant['id'], + 'role' => 'tenant_admin', + 'email' => 'admin@nordring.test', + 'password' => 'secret123', + 'first_name' => 'Jana', + 'last_name' => 'Koester', + 'is_active' => true, +]); + +$dispatcher = $userRepository->create([ + 'tenant_id' => $tenant['id'], + 'role' => 'dispatcher', + 'email' => 'planung@nordring.test', + 'password' => 'secret123', + 'first_name' => 'Mara', + 'last_name' => 'Dispo', + 'is_active' => true, +]); + +$instructorUser = $userRepository->create([ + 'tenant_id' => $tenant['id'], + 'role' => 'instructor', + 'email' => 'fahrlehrer@nordring.test', + 'password' => 'secret123', + 'first_name' => 'Tobias', + 'last_name' => 'Fahr', + 'is_active' => true, +]); + +$instructors = [ + ['user_id' => $instructorUser['id'], 'first_name' => 'Tobias', 'last_name' => 'Fahr', 'color' => '#0E5D80'], + ['user_id' => null, 'first_name' => 'Leonie', 'last_name' => 'Kraft', 'color' => '#A74826'], +]; + +foreach ($instructors as $instructor) { + $instructorRepository->create($tenant['id'], $instructor + ['notes' => '', 'is_active' => true]); +} + +$lessonTypes = [ + ['name' => 'Einweisung', 'color' => '#0E5D80', 'default_duration' => 45, 'category' => 'student', 'requirement_key' => 'intro', 'is_billable' => 1, 'is_counted' => 1, 'is_rest_relevant' => 1, 'fixed_duration' => 0, 'sort_order' => 1], + ['name' => 'Uebungsstunde', 'color' => '#2C7A4B', 'default_duration' => 45, 'category' => 'student', 'requirement_key' => 'practice', 'is_billable' => 1, 'is_counted' => 1, 'is_rest_relevant' => 1, 'fixed_duration' => 0, 'sort_order' => 2], + ['name' => 'Bundes- / Landstrasse', 'color' => '#D99A1A', 'default_duration' => 45, 'category' => 'student', 'requirement_key' => 'rural', 'is_billable' => 1, 'is_counted' => 1, 'is_rest_relevant' => 1, 'fixed_duration' => 0, 'sort_order' => 3], + ['name' => 'Autobahn', 'color' => '#C86C1E', 'default_duration' => 45, 'category' => 'student', 'requirement_key' => 'highway', 'is_billable' => 1, 'is_counted' => 1, 'is_rest_relevant' => 1, 'fixed_duration' => 0, 'sort_order' => 4], + ['name' => 'Daemmerung / Dunkelheit', 'color' => '#4B356B', 'default_duration' => 45, 'category' => 'student', 'requirement_key' => 'night', 'is_billable' => 1, 'is_counted' => 1, 'is_rest_relevant' => 1, 'fixed_duration' => 0, 'sort_order' => 5], + ['name' => 'Pruefungsfahrt', 'color' => '#A83333', 'default_duration' => 70, 'category' => 'student', 'requirement_key' => null, 'is_billable' => 1, 'is_counted' => 0, 'is_rest_relevant' => 1, 'fixed_duration' => 1, 'sort_order' => 6], + ['name' => 'Theorieunterricht', 'color' => '#2B8793', 'default_duration' => 90, 'category' => 'other', 'requirement_key' => null, 'is_billable' => 0, 'is_counted' => 0, 'is_rest_relevant' => 0, 'fixed_duration' => 1, 'sort_order' => 7], + ['name' => 'Sonstige Arbeiten', 'color' => '#70747C', 'default_duration' => 60, 'category' => 'other', 'requirement_key' => null, 'is_billable' => 0, 'is_counted' => 0, 'is_rest_relevant' => 0, 'fixed_duration' => 1, 'sort_order' => 8], + ['name' => 'Privater Termin', 'color' => '#44444A', 'default_duration' => 60, 'category' => 'private', 'requirement_key' => null, 'is_billable' => 0, 'is_counted' => 0, 'is_rest_relevant' => 0, 'fixed_duration' => 1, 'sort_order' => 9], + ['name' => 'Sperrzeit / Blockierung', 'color' => '#111111', 'default_duration' => 60, 'category' => 'blocked', 'requirement_key' => null, 'is_billable' => 0, 'is_counted' => 0, 'is_rest_relevant' => 0, 'fixed_duration' => 1, 'sort_order' => 10], +]; + +foreach ($lessonTypes as $lessonType) { + $referenceRepository->createLessonType($tenant['id'], $lessonType); +} + +$templates = [ + [ + 'code' => 'C', + 'name' => 'Klasse C', + 'is_combination' => false, + 'requirements' => [ + ['requirement_key' => 'practice', 'label' => 'Uebungsstunden', 'required_units' => 8], + ['requirement_key' => 'rural', 'label' => 'Ueberland', 'required_units' => 5], + ['requirement_key' => 'highway', 'label' => 'Autobahn', 'required_units' => 4], + ['requirement_key' => 'night', 'label' => 'Nachtfahrt', 'required_units' => 3], + ], + ], + [ + 'code' => 'CE', + 'name' => 'Klasse CE', + 'is_combination' => false, + 'requirements' => [ + ['requirement_key' => 'practice', 'label' => 'Uebungsstunden', 'required_units' => 10], + ['requirement_key' => 'rural', 'label' => 'Ueberland', 'required_units' => 5], + ['requirement_key' => 'highway', 'label' => 'Autobahn', 'required_units' => 5], + ['requirement_key' => 'night', 'label' => 'Nachtfahrt', 'required_units' => 3], + ], + ], + [ + 'code' => 'C+CE', + 'name' => 'Klasse C + CE', + 'is_combination' => true, + 'requirements' => [ + ['requirement_key' => 'C::practice', 'phase_label' => 'C', 'label' => 'Uebungsstunden', 'required_units' => 8], + ['requirement_key' => 'C::rural', 'phase_label' => 'C', 'label' => 'Ueberland', 'required_units' => 5], + ['requirement_key' => 'C::highway', 'phase_label' => 'C', 'label' => 'Autobahn', 'required_units' => 4], + ['requirement_key' => 'C::night', 'phase_label' => 'C', 'label' => 'Nachtfahrt', 'required_units' => 3], + ['requirement_key' => 'CE::practice', 'phase_label' => 'CE', 'label' => 'Uebungsstunden', 'required_units' => 6], + ['requirement_key' => 'CE::rural', 'phase_label' => 'CE', 'label' => 'Ueberland', 'required_units' => 3], + ['requirement_key' => 'CE::highway', 'phase_label' => 'CE', 'label' => 'Autobahn', 'required_units' => 3], + ['requirement_key' => 'CE::night', 'phase_label' => 'CE', 'label' => 'Nachtfahrt', 'required_units' => 2], + ], + ], + [ + 'code' => 'C1', + 'name' => 'Klasse C1', + 'is_combination' => false, + 'requirements' => [ + ['requirement_key' => 'practice', 'label' => 'Uebungsstunden', 'required_units' => 6], + ['requirement_key' => 'rural', 'label' => 'Ueberland', 'required_units' => 4], + ['requirement_key' => 'highway', 'label' => 'Autobahn', 'required_units' => 3], + ['requirement_key' => 'night', 'label' => 'Nachtfahrt', 'required_units' => 2], + ], + ], + [ + 'code' => 'C1E', + 'name' => 'Klasse C1E', + 'is_combination' => false, + 'requirements' => [ + ['requirement_key' => 'practice', 'label' => 'Uebungsstunden', 'required_units' => 7], + ['requirement_key' => 'rural', 'label' => 'Ueberland', 'required_units' => 4], + ['requirement_key' => 'highway', 'label' => 'Autobahn', 'required_units' => 3], + ['requirement_key' => 'night', 'label' => 'Nachtfahrt', 'required_units' => 2], + ], + ], + [ + 'code' => 'C1+C1E', + 'name' => 'Klasse C1 + C1E', + 'is_combination' => true, + 'requirements' => [ + ['requirement_key' => 'C1::practice', 'phase_label' => 'C1', 'label' => 'Uebungsstunden', 'required_units' => 6], + ['requirement_key' => 'C1::rural', 'phase_label' => 'C1', 'label' => 'Ueberland', 'required_units' => 4], + ['requirement_key' => 'C1::highway', 'phase_label' => 'C1', 'label' => 'Autobahn', 'required_units' => 3], + ['requirement_key' => 'C1::night', 'phase_label' => 'C1', 'label' => 'Nachtfahrt', 'required_units' => 2], + ['requirement_key' => 'C1E::practice', 'phase_label' => 'C1E', 'label' => 'Uebungsstunden', 'required_units' => 4], + ['requirement_key' => 'C1E::rural', 'phase_label' => 'C1E', 'label' => 'Ueberland', 'required_units' => 2], + ['requirement_key' => 'C1E::highway', 'phase_label' => 'C1E', 'label' => 'Autobahn', 'required_units' => 2], + ['requirement_key' => 'C1E::night', 'phase_label' => 'C1E', 'label' => 'Nachtfahrt', 'required_units' => 1], + ], + ], +]; + +$templateIds = []; +foreach ($templates as $template) { + $record = $referenceRepository->createTemplate($tenant['id'], $template); + $templateIds[$record['code']] = $record['id']; +} + +$studentRepository->create($tenant['id'], [ + 'template_id' => $templateIds['C+CE'], + 'first_name' => 'Mila', + 'last_name' => 'Hansen', + 'phone' => '0170 1234567', + 'notes' => 'Startet naechste Woche mit Nachtfahrten.', + 'status' => 'active', +]); + +$studentRepository->create($tenant['id'], [ + 'template_id' => $templateIds['C1'], + 'first_name' => 'Jonas', + 'last_name' => 'Winter', + 'phone' => '0172 999000', + 'notes' => 'Braucht fruehe Termine.', + 'status' => 'active', +]); + +fwrite(STDOUT, "Seed complete." . PHP_EOL); diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/config/app.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/config/app.php new file mode 100644 index 0000000..d252d45 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/config/app.php @@ -0,0 +1,13 @@ + 'DriveTime Planner', + 'env' => 'local', + 'debug' => true, + 'db_path' => __DIR__ . '/../storage/database/app.sqlite', + 'session_path' => __DIR__ . '/../storage/sessions', + 'session_name' => 'drive_time_session', + 'frontend_entry' => __DIR__ . '/../public/assets/index.js', +]; diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/001_initial.sql b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/001_initial.sql new file mode 100644 index 0000000..c9ec8b6 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/001_initial.sql @@ -0,0 +1,145 @@ +CREATE TABLE IF NOT EXISTS tenants ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + timezone TEXT NOT NULL DEFAULT 'Europe/Berlin', + is_active INTEGER NOT NULL DEFAULT 1, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + tenant_id INTEGER NULL, + role TEXT NOT NULL, + email TEXT NOT NULL UNIQUE, + password_hash TEXT NOT NULL, + first_name TEXT NOT NULL, + last_name TEXT NOT NULL, + is_active INTEGER NOT NULL DEFAULT 1, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS instructors ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + tenant_id INTEGER NOT NULL, + first_name TEXT NOT NULL, + last_name TEXT NOT NULL, + color TEXT NOT NULL, + notes TEXT DEFAULT '', + is_active INTEGER NOT NULL DEFAULT 1, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS lesson_types ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + tenant_id INTEGER NOT NULL, + name TEXT NOT NULL, + color TEXT NOT NULL, + default_duration INTEGER NOT NULL, + category TEXT NOT NULL, + requirement_key TEXT DEFAULT NULL, + is_billable INTEGER NOT NULL DEFAULT 0, + is_counted INTEGER NOT NULL DEFAULT 0, + is_rest_relevant INTEGER NOT NULL DEFAULT 0, + fixed_duration INTEGER NOT NULL DEFAULT 0, + sort_order INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS license_class_templates ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + tenant_id INTEGER NOT NULL, + code TEXT NOT NULL, + name TEXT NOT NULL, + is_combination INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + UNIQUE (tenant_id, code), + FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS license_template_requirements ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + template_id INTEGER NOT NULL, + requirement_key TEXT NOT NULL, + label TEXT NOT NULL, + required_units INTEGER NOT NULL, + sort_order INTEGER NOT NULL DEFAULT 0, + FOREIGN KEY (template_id) REFERENCES license_class_templates (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS students ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + tenant_id INTEGER NOT NULL, + template_id INTEGER NOT NULL, + first_name TEXT NOT NULL, + last_name TEXT NOT NULL, + phone TEXT DEFAULT '', + notes TEXT DEFAULT '', + status TEXT NOT NULL DEFAULT 'active', + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE, + FOREIGN KEY (template_id) REFERENCES license_class_templates (id) +); + +CREATE TABLE IF NOT EXISTS student_requirement_snapshots ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + student_id INTEGER NOT NULL, + requirement_key TEXT NOT NULL, + label TEXT NOT NULL, + required_units INTEGER NOT NULL, + sort_order INTEGER NOT NULL DEFAULT 0, + FOREIGN KEY (student_id) REFERENCES students (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS appointments ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + tenant_id INTEGER NOT NULL, + student_id INTEGER DEFAULT NULL, + instructor_id INTEGER NOT NULL, + lesson_type_id INTEGER NOT NULL, + title TEXT NOT NULL, + category TEXT NOT NULL, + start_at TEXT NOT NULL, + end_at TEXT NOT NULL, + units INTEGER NOT NULL DEFAULT 1, + status TEXT NOT NULL DEFAULT 'planned', + notes TEXT DEFAULT '', + warning_acknowledged INTEGER NOT NULL DEFAULT 0, + created_by INTEGER NOT NULL, + updated_by INTEGER NOT NULL, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE, + FOREIGN KEY (student_id) REFERENCES students (id) ON DELETE SET NULL, + FOREIGN KEY (instructor_id) REFERENCES instructors (id), + FOREIGN KEY (lesson_type_id) REFERENCES lesson_types (id), + FOREIGN KEY (created_by) REFERENCES users (id), + FOREIGN KEY (updated_by) REFERENCES users (id) +); + +CREATE TABLE IF NOT EXISTS appointment_units ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + appointment_id INTEGER NOT NULL, + requirement_key TEXT DEFAULT NULL, + label TEXT DEFAULT NULL, + units_counted INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL, + FOREIGN KEY (appointment_id) REFERENCES appointments (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS settings ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + tenant_id INTEGER NOT NULL, + key_name TEXT NOT NULL, + value TEXT NOT NULL, + UNIQUE (tenant_id, key_name), + FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE +); diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/002_instructors_user_link.sql b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/002_instructors_user_link.sql new file mode 100644 index 0000000..d1a755e --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/002_instructors_user_link.sql @@ -0,0 +1 @@ +ALTER TABLE instructors ADD COLUMN user_id INTEGER DEFAULT NULL; diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/003_backfill_instructor_user_links.sql b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/003_backfill_instructor_user_links.sql new file mode 100644 index 0000000..c41d2fd --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/003_backfill_instructor_user_links.sql @@ -0,0 +1,11 @@ +UPDATE instructors +SET user_id = ( + SELECT u.id + FROM users u + WHERE u.tenant_id = instructors.tenant_id + AND u.role = 'instructor' + AND u.first_name = instructors.first_name + AND u.last_name = instructors.last_name + LIMIT 1 +) +WHERE user_id IS NULL; diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/004_lesson_type_template_visibility.sql b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/004_lesson_type_template_visibility.sql new file mode 100644 index 0000000..084a4f3 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/004_lesson_type_template_visibility.sql @@ -0,0 +1,13 @@ +CREATE TABLE IF NOT EXISTS lesson_type_template_visibility ( + lesson_type_id INTEGER NOT NULL, + template_id INTEGER NOT NULL, + PRIMARY KEY (lesson_type_id, template_id), + FOREIGN KEY (lesson_type_id) REFERENCES lesson_types (id) ON DELETE CASCADE, + FOREIGN KEY (template_id) REFERENCES license_class_templates (id) ON DELETE CASCADE +); + +INSERT OR IGNORE INTO lesson_type_template_visibility (lesson_type_id, template_id) +SELECT lt.id, tpl.id +FROM lesson_types lt +JOIN license_class_templates tpl ON tpl.tenant_id = lt.tenant_id +WHERE lt.category = 'student'; diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/005_tenant_location_sun.sql b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/005_tenant_location_sun.sql new file mode 100644 index 0000000..154f13a --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/005_tenant_location_sun.sql @@ -0,0 +1,3 @@ +ALTER TABLE tenants ADD COLUMN location_label TEXT DEFAULT ''; +ALTER TABLE tenants ADD COLUMN latitude REAL DEFAULT NULL; +ALTER TABLE tenants ADD COLUMN longitude REAL DEFAULT NULL; diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/006_tenant_federal_state.sql b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/006_tenant_federal_state.sql new file mode 100644 index 0000000..7af0b1b --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/006_tenant_federal_state.sql @@ -0,0 +1 @@ +ALTER TABLE tenants ADD COLUMN federal_state TEXT DEFAULT 'BE'; diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/007_student_prior_completed_units.sql b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/007_student_prior_completed_units.sql new file mode 100644 index 0000000..0d181c8 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/007_student_prior_completed_units.sql @@ -0,0 +1 @@ +ALTER TABLE student_requirement_snapshots ADD COLUMN prior_completed_units INTEGER NOT NULL DEFAULT 0; diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/008_lesson_type_system_key.sql b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/008_lesson_type_system_key.sql new file mode 100644 index 0000000..623f0ff --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/008_lesson_type_system_key.sql @@ -0,0 +1,2 @@ +ALTER TABLE lesson_types ADD COLUMN system_key TEXT DEFAULT NULL; +CREATE UNIQUE INDEX IF NOT EXISTS idx_lesson_types_tenant_system_key ON lesson_types (tenant_id, system_key) WHERE system_key IS NOT NULL; diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/009_requirement_phase_labels.sql b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/009_requirement_phase_labels.sql new file mode 100644 index 0000000..8d403ba --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/009_requirement_phase_labels.sql @@ -0,0 +1,2 @@ +ALTER TABLE license_template_requirements ADD COLUMN phase_label TEXT NOT NULL DEFAULT ''; +ALTER TABLE student_requirement_snapshots ADD COLUMN phase_label TEXT NOT NULL DEFAULT ''; diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/010_student_email.sql b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/010_student_email.sql new file mode 100644 index 0000000..64fbf40 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/010_student_email.sql @@ -0,0 +1 @@ +ALTER TABLE students ADD COLUMN email TEXT NOT NULL DEFAULT ''; diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/010_students_contact_flags.sql b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/010_students_contact_flags.sql new file mode 100644 index 0000000..1c1d76f --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/010_students_contact_flags.sql @@ -0,0 +1,2 @@ +ALTER TABLE students ADD COLUMN email TEXT DEFAULT ''; +ALTER TABLE students ADD COLUMN needs_glasses INTEGER NOT NULL DEFAULT 0; diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/011_student_needs_glasses.sql b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/011_student_needs_glasses.sql new file mode 100644 index 0000000..9e31849 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/011_student_needs_glasses.sql @@ -0,0 +1 @@ +ALTER TABLE students ADD COLUMN needs_glasses INTEGER NOT NULL DEFAULT 0; diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/012_split_combination_templates.sql b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/012_split_combination_templates.sql new file mode 100644 index 0000000..9e37494 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/012_split_combination_templates.sql @@ -0,0 +1,183 @@ +DROP TABLE IF EXISTS student_requirement_snapshots_old; +CREATE TEMP TABLE student_requirement_snapshots_old AS +SELECT * +FROM student_requirement_snapshots +WHERE student_id IN ( + SELECT s.id + FROM students s + JOIN license_class_templates t ON t.id = s.template_id + WHERE t.code IN ('C+CE', 'C1+C1E') +); + +DELETE FROM license_template_requirements +WHERE template_id IN ( + SELECT id FROM license_class_templates WHERE code IN ('C+CE', 'C1+C1E') +); + +INSERT INTO license_template_requirements (template_id, requirement_key, label, required_units, sort_order, phase_label) +SELECT id, 'C::practice', 'Uebungsstunden', 8, 0, 'C' +FROM license_class_templates +WHERE code = 'C+CE'; + +INSERT INTO license_template_requirements (template_id, requirement_key, label, required_units, sort_order, phase_label) +SELECT id, 'C::rural', 'Ueberland', 5, 1, 'C' +FROM license_class_templates +WHERE code = 'C+CE'; + +INSERT INTO license_template_requirements (template_id, requirement_key, label, required_units, sort_order, phase_label) +SELECT id, 'C::highway', 'Autobahn', 4, 2, 'C' +FROM license_class_templates +WHERE code = 'C+CE'; + +INSERT INTO license_template_requirements (template_id, requirement_key, label, required_units, sort_order, phase_label) +SELECT id, 'C::night', 'Nachtfahrt', 3, 3, 'C' +FROM license_class_templates +WHERE code = 'C+CE'; + +INSERT INTO license_template_requirements (template_id, requirement_key, label, required_units, sort_order, phase_label) +SELECT id, 'CE::practice', 'Uebungsstunden', 6, 100, 'CE' +FROM license_class_templates +WHERE code = 'C+CE'; + +INSERT INTO license_template_requirements (template_id, requirement_key, label, required_units, sort_order, phase_label) +SELECT id, 'CE::rural', 'Ueberland', 3, 101, 'CE' +FROM license_class_templates +WHERE code = 'C+CE'; + +INSERT INTO license_template_requirements (template_id, requirement_key, label, required_units, sort_order, phase_label) +SELECT id, 'CE::highway', 'Autobahn', 3, 102, 'CE' +FROM license_class_templates +WHERE code = 'C+CE'; + +INSERT INTO license_template_requirements (template_id, requirement_key, label, required_units, sort_order, phase_label) +SELECT id, 'CE::night', 'Nachtfahrt', 2, 103, 'CE' +FROM license_class_templates +WHERE code = 'C+CE'; + +INSERT INTO license_template_requirements (template_id, requirement_key, label, required_units, sort_order, phase_label) +SELECT id, 'C1::practice', 'Uebungsstunden', 6, 0, 'C1' +FROM license_class_templates +WHERE code = 'C1+C1E'; + +INSERT INTO license_template_requirements (template_id, requirement_key, label, required_units, sort_order, phase_label) +SELECT id, 'C1::rural', 'Ueberland', 4, 1, 'C1' +FROM license_class_templates +WHERE code = 'C1+C1E'; + +INSERT INTO license_template_requirements (template_id, requirement_key, label, required_units, sort_order, phase_label) +SELECT id, 'C1::highway', 'Autobahn', 3, 2, 'C1' +FROM license_class_templates +WHERE code = 'C1+C1E'; + +INSERT INTO license_template_requirements (template_id, requirement_key, label, required_units, sort_order, phase_label) +SELECT id, 'C1::night', 'Nachtfahrt', 2, 3, 'C1' +FROM license_class_templates +WHERE code = 'C1+C1E'; + +INSERT INTO license_template_requirements (template_id, requirement_key, label, required_units, sort_order, phase_label) +SELECT id, 'C1E::practice', 'Uebungsstunden', 4, 100, 'C1E' +FROM license_class_templates +WHERE code = 'C1+C1E'; + +INSERT INTO license_template_requirements (template_id, requirement_key, label, required_units, sort_order, phase_label) +SELECT id, 'C1E::rural', 'Ueberland', 2, 101, 'C1E' +FROM license_class_templates +WHERE code = 'C1+C1E'; + +INSERT INTO license_template_requirements (template_id, requirement_key, label, required_units, sort_order, phase_label) +SELECT id, 'C1E::highway', 'Autobahn', 2, 102, 'C1E' +FROM license_class_templates +WHERE code = 'C1+C1E'; + +INSERT INTO license_template_requirements (template_id, requirement_key, label, required_units, sort_order, phase_label) +SELECT id, 'C1E::night', 'Nachtfahrt', 1, 103, 'C1E' +FROM license_class_templates +WHERE code = 'C1+C1E'; + +UPDATE appointment_units +SET requirement_key = 'C::' || requirement_key +WHERE requirement_key IN ('practice', 'rural', 'highway', 'night') + AND appointment_id IN ( + SELECT a.id + FROM appointments a + JOIN students s ON s.id = a.student_id + JOIN license_class_templates t ON t.id = s.template_id + WHERE t.code = 'C+CE' + ); + +UPDATE appointment_units +SET requirement_key = 'C1::' || requirement_key +WHERE requirement_key IN ('practice', 'rural', 'highway', 'night') + AND appointment_id IN ( + SELECT a.id + FROM appointments a + JOIN students s ON s.id = a.student_id + JOIN license_class_templates t ON t.id = s.template_id + WHERE t.code = 'C1+C1E' + ); + +DELETE FROM student_requirement_snapshots +WHERE student_id IN ( + SELECT s.id + FROM students s + JOIN license_class_templates t ON t.id = s.template_id + WHERE t.code IN ('C+CE', 'C1+C1E') +); + +INSERT INTO student_requirement_snapshots (student_id, requirement_key, label, required_units, sort_order, prior_completed_units, phase_label) +SELECT + s.id, + r.requirement_key, + r.label, + r.required_units, + r.sort_order, + CASE + WHEN r.requirement_key = 'C::practice' THEN COALESCE(( + SELECT prior_completed_units + FROM student_requirement_snapshots_old old + WHERE old.student_id = s.id AND old.requirement_key = 'practice' + ), 0) + WHEN r.requirement_key = 'C::rural' THEN COALESCE(( + SELECT prior_completed_units + FROM student_requirement_snapshots_old old + WHERE old.student_id = s.id AND old.requirement_key = 'rural' + ), 0) + WHEN r.requirement_key = 'C::highway' THEN COALESCE(( + SELECT prior_completed_units + FROM student_requirement_snapshots_old old + WHERE old.student_id = s.id AND old.requirement_key = 'highway' + ), 0) + WHEN r.requirement_key = 'C::night' THEN COALESCE(( + SELECT prior_completed_units + FROM student_requirement_snapshots_old old + WHERE old.student_id = s.id AND old.requirement_key = 'night' + ), 0) + WHEN r.requirement_key = 'C1::practice' THEN COALESCE(( + SELECT prior_completed_units + FROM student_requirement_snapshots_old old + WHERE old.student_id = s.id AND old.requirement_key = 'practice' + ), 0) + WHEN r.requirement_key = 'C1::rural' THEN COALESCE(( + SELECT prior_completed_units + FROM student_requirement_snapshots_old old + WHERE old.student_id = s.id AND old.requirement_key = 'rural' + ), 0) + WHEN r.requirement_key = 'C1::highway' THEN COALESCE(( + SELECT prior_completed_units + FROM student_requirement_snapshots_old old + WHERE old.student_id = s.id AND old.requirement_key = 'highway' + ), 0) + WHEN r.requirement_key = 'C1::night' THEN COALESCE(( + SELECT prior_completed_units + FROM student_requirement_snapshots_old old + WHERE old.student_id = s.id AND old.requirement_key = 'night' + ), 0) + ELSE 0 + END, + r.phase_label +FROM students s +JOIN license_class_templates t ON t.id = s.template_id +JOIN license_template_requirements r ON r.template_id = t.id +WHERE t.code IN ('C+CE', 'C1+C1E'); + +DROP TABLE IF EXISTS student_requirement_snapshots_old; diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/013_combo_generic_key_cleanup.sql b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/013_combo_generic_key_cleanup.sql new file mode 100644 index 0000000..2cc654a --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/013_combo_generic_key_cleanup.sql @@ -0,0 +1,42 @@ +UPDATE appointment_units +SET requirement_key = COALESCE( + ( + SELECT r.requirement_key + FROM appointments a + JOIN students s ON s.id = a.student_id + JOIN license_class_templates t ON t.id = s.template_id + JOIN license_template_requirements r ON r.template_id = t.id + WHERE a.id = appointment_units.appointment_id + AND t.is_combination = 1 + AND COALESCE(r.phase_label, '') <> '' + AND substr(r.requirement_key, instr(r.requirement_key, '::') + 2) = appointment_units.requirement_key + ORDER BY r.sort_order + LIMIT 1 + ), + requirement_key +) +WHERE requirement_key NOT LIKE '%::%' + AND appointment_id IN ( + SELECT a.id + FROM appointments a + JOIN students s ON s.id = a.student_id + JOIN license_class_templates t ON t.id = s.template_id + WHERE t.is_combination = 1 + ); + +DELETE FROM student_requirement_snapshots +WHERE requirement_key NOT LIKE '%::%' + AND student_id IN ( + SELECT s.id + FROM students s + JOIN license_class_templates t ON t.id = s.template_id + WHERE t.is_combination = 1 + ) + AND EXISTS ( + SELECT 1 + FROM students s + JOIN license_template_requirements r ON r.template_id = s.template_id + WHERE s.id = student_requirement_snapshots.student_id + AND COALESCE(r.phase_label, '') <> '' + AND substr(r.requirement_key, instr(r.requirement_key, '::') + 2) = student_requirement_snapshots.requirement_key + ); diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/014_legacy_student_key_aliases_to_lesson_type_keys.sql b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/014_legacy_student_key_aliases_to_lesson_type_keys.sql new file mode 100644 index 0000000..94a52ef --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/014_legacy_student_key_aliases_to_lesson_type_keys.sql @@ -0,0 +1,170 @@ +UPDATE appointment_units +SET requirement_key = COALESCE(( + SELECT 'lesson_type_' || lt.id + FROM appointments a + JOIN students s ON s.id = a.student_id + JOIN lesson_type_template_visibility vis ON vis.template_id = s.template_id + JOIN lesson_types lt ON lt.id = vis.lesson_type_id + WHERE a.id = appointment_units.appointment_id + AND lt.category = 'student' + AND lt.is_counted = 1 + AND ( + (appointment_units.requirement_key = 'intro' AND lt.name = 'Einweisung') + OR (appointment_units.requirement_key = 'practice' AND lt.name IN ('Uebungsstunde', 'Übungsstunde', 'Uebungsstunden', 'Übungsstunden')) + OR (appointment_units.requirement_key = 'rural' AND lt.name IN ('Ueberlandfahrt', 'Überlandfahrt', 'Bundes- / Landstrasse')) + OR (appointment_units.requirement_key = 'highway' AND lt.name IN ('Autobahn', 'Autobahnfahrt')) + OR (appointment_units.requirement_key = 'night' AND lt.name IN ('Nachtfahrt', 'Nachtfahrten', 'Daemmerung / Dunkelheit')) + ) + ORDER BY lt.sort_order, lt.id + LIMIT 1 +), requirement_key) +WHERE requirement_key IN ('intro', 'practice', 'rural', 'highway', 'night'); + +UPDATE student_requirement_snapshots AS target +SET prior_completed_units = prior_completed_units + COALESCE(( + SELECT SUM(src.prior_completed_units) + FROM student_requirement_snapshots src + WHERE src.student_id = target.student_id + AND src.requirement_key = 'intro' +), 0) +WHERE target.requirement_key = COALESCE(( + SELECT 'lesson_type_' || lt.id + FROM students s + JOIN lesson_type_template_visibility vis ON vis.template_id = s.template_id + JOIN lesson_types lt ON lt.id = vis.lesson_type_id + WHERE s.id = target.student_id + AND lt.category = 'student' + AND lt.is_counted = 1 + AND lt.name = 'Einweisung' + ORDER BY lt.sort_order, lt.id + LIMIT 1 +), target.requirement_key) + AND EXISTS ( + SELECT 1 + FROM student_requirement_snapshots src + WHERE src.student_id = target.student_id + AND src.requirement_key = 'intro' + ); + +UPDATE student_requirement_snapshots AS target +SET prior_completed_units = prior_completed_units + COALESCE(( + SELECT SUM(src.prior_completed_units) + FROM student_requirement_snapshots src + WHERE src.student_id = target.student_id + AND src.requirement_key = 'practice' +), 0) +WHERE target.requirement_key = COALESCE(( + SELECT 'lesson_type_' || lt.id + FROM students s + JOIN lesson_type_template_visibility vis ON vis.template_id = s.template_id + JOIN lesson_types lt ON lt.id = vis.lesson_type_id + WHERE s.id = target.student_id + AND lt.category = 'student' + AND lt.is_counted = 1 + AND lt.name IN ('Uebungsstunde', 'Übungsstunde', 'Uebungsstunden', 'Übungsstunden') + ORDER BY lt.sort_order, lt.id + LIMIT 1 +), target.requirement_key) + AND EXISTS ( + SELECT 1 + FROM student_requirement_snapshots src + WHERE src.student_id = target.student_id + AND src.requirement_key = 'practice' + ); + +UPDATE student_requirement_snapshots AS target +SET prior_completed_units = prior_completed_units + COALESCE(( + SELECT SUM(src.prior_completed_units) + FROM student_requirement_snapshots src + WHERE src.student_id = target.student_id + AND src.requirement_key = 'rural' +), 0) +WHERE target.requirement_key = COALESCE(( + SELECT 'lesson_type_' || lt.id + FROM students s + JOIN lesson_type_template_visibility vis ON vis.template_id = s.template_id + JOIN lesson_types lt ON lt.id = vis.lesson_type_id + WHERE s.id = target.student_id + AND lt.category = 'student' + AND lt.is_counted = 1 + AND lt.name IN ('Ueberlandfahrt', 'Überlandfahrt', 'Bundes- / Landstrasse') + ORDER BY lt.sort_order, lt.id + LIMIT 1 +), target.requirement_key) + AND EXISTS ( + SELECT 1 + FROM student_requirement_snapshots src + WHERE src.student_id = target.student_id + AND src.requirement_key = 'rural' + ); + +UPDATE student_requirement_snapshots AS target +SET prior_completed_units = prior_completed_units + COALESCE(( + SELECT SUM(src.prior_completed_units) + FROM student_requirement_snapshots src + WHERE src.student_id = target.student_id + AND src.requirement_key = 'highway' +), 0) +WHERE target.requirement_key = COALESCE(( + SELECT 'lesson_type_' || lt.id + FROM students s + JOIN lesson_type_template_visibility vis ON vis.template_id = s.template_id + JOIN lesson_types lt ON lt.id = vis.lesson_type_id + WHERE s.id = target.student_id + AND lt.category = 'student' + AND lt.is_counted = 1 + AND lt.name IN ('Autobahn', 'Autobahnfahrt') + ORDER BY lt.sort_order, lt.id + LIMIT 1 +), target.requirement_key) + AND EXISTS ( + SELECT 1 + FROM student_requirement_snapshots src + WHERE src.student_id = target.student_id + AND src.requirement_key = 'highway' + ); + +UPDATE student_requirement_snapshots AS target +SET prior_completed_units = prior_completed_units + COALESCE(( + SELECT SUM(src.prior_completed_units) + FROM student_requirement_snapshots src + WHERE src.student_id = target.student_id + AND src.requirement_key = 'night' +), 0) +WHERE target.requirement_key = COALESCE(( + SELECT 'lesson_type_' || lt.id + FROM students s + JOIN lesson_type_template_visibility vis ON vis.template_id = s.template_id + JOIN lesson_types lt ON lt.id = vis.lesson_type_id + WHERE s.id = target.student_id + AND lt.category = 'student' + AND lt.is_counted = 1 + AND lt.name IN ('Nachtfahrt', 'Nachtfahrten', 'Daemmerung / Dunkelheit') + ORDER BY lt.sort_order, lt.id + LIMIT 1 +), target.requirement_key) + AND EXISTS ( + SELECT 1 + FROM student_requirement_snapshots src + WHERE src.student_id = target.student_id + AND src.requirement_key = 'night' + ); + +DELETE FROM student_requirement_snapshots +WHERE requirement_key IN ('intro', 'practice', 'rural', 'highway', 'night') + AND EXISTS ( + SELECT 1 + FROM students s + JOIN lesson_type_template_visibility vis ON vis.template_id = s.template_id + JOIN lesson_types lt ON lt.id = vis.lesson_type_id + WHERE s.id = student_requirement_snapshots.student_id + AND lt.category = 'student' + AND lt.is_counted = 1 + AND ( + (student_requirement_snapshots.requirement_key = 'intro' AND lt.name = 'Einweisung') + OR (student_requirement_snapshots.requirement_key = 'practice' AND lt.name IN ('Uebungsstunde', 'Übungsstunde', 'Uebungsstunden', 'Übungsstunden')) + OR (student_requirement_snapshots.requirement_key = 'rural' AND lt.name IN ('Ueberlandfahrt', 'Überlandfahrt', 'Bundes- / Landstrasse')) + OR (student_requirement_snapshots.requirement_key = 'highway' AND lt.name IN ('Autobahn', 'Autobahnfahrt')) + OR (student_requirement_snapshots.requirement_key = 'night' AND lt.name IN ('Nachtfahrt', 'Nachtfahrten', 'Daemmerung / Dunkelheit')) + ) + ); diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/015_instructor_pre_start_buffer.sql b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/015_instructor_pre_start_buffer.sql new file mode 100644 index 0000000..60c1107 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/database/migrations/015_instructor_pre_start_buffer.sql @@ -0,0 +1 @@ +ALTER TABLE instructors ADD COLUMN pre_start_buffer_minutes INTEGER NOT NULL DEFAULT 0; diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/.htaccess b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/.htaccess new file mode 100644 index 0000000..66ef8f6 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/.htaccess @@ -0,0 +1,4 @@ +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^ index.php [QSA,L] diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/assets/.vite/manifest.json b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/assets/.vite/manifest.json new file mode 100644 index 0000000..9d7b925 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/assets/.vite/manifest.json @@ -0,0 +1,11 @@ +{ + "src/main.jsx": { + "file": "assets/main-F76uNDS1.js", + "name": "main", + "src": "src/main.jsx", + "isEntry": true, + "css": [ + "assets/main-C54NZrM5.css" + ] + } +} \ No newline at end of file diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/assets/assets/main-C54NZrM5.css b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/assets/assets/main-C54NZrM5.css new file mode 100644 index 0000000..d475cba --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/assets/assets/main-C54NZrM5.css @@ -0,0 +1 @@ +:root{--bg: #f3efe6;--panel: rgba(255, 252, 246, .94);--ink: #111217;--muted: #67625a;--line: rgba(17, 18, 23, .08);--accent: #a74826;--accent-deep: #6d2410;--teal: #0e5d80;--success: #2c7a4b;--danger: #a83333;--shadow: 0 24px 60px rgba(44, 36, 25, .14);--surface-soft: #efe6d7;--surface-time: #f7f2ea;--input-bg: rgba(255, 255, 255, .88);--ghost-bg: rgba(255, 255, 255, .7);--badge-bg: rgba(17, 18, 23, .07);font-family:Iowan Old Style,Palatino Linotype,Book Antiqua,serif;color:var(--ink);background:radial-gradient(circle at top left,rgba(167,72,38,.17),transparent 30%),radial-gradient(circle at bottom right,rgba(14,93,128,.14),transparent 26%),linear-gradient(180deg,#efe8da,#f6f3ed)}:root[data-theme=dark]{--bg: #14181d;--panel: rgba(24, 29, 36, .94);--ink: #edf0f4;--muted: #a3acb8;--line: rgba(237, 240, 244, .12);--accent: #c66945;--accent-deep: #f1b08f;--teal: #63a8c2;--success: #7bc596;--danger: #f08a8a;--shadow: 0 24px 60px rgba(0, 0, 0, .32);--surface-soft: #222933;--surface-time: #1b2129;--input-bg: rgba(35, 42, 52, .92);--ghost-bg: rgba(35, 42, 52, .82);--badge-bg: rgba(237, 240, 244, .08);background:radial-gradient(circle at top left,rgba(198,105,69,.18),transparent 28%),radial-gradient(circle at bottom right,rgba(99,168,194,.14),transparent 25%),linear-gradient(180deg,#11161c,#161c23)}*{box-sizing:border-box}body{margin:0;color:var(--ink);background:var(--bg)}button,input,select,textarea{font:inherit}.app-shell{min-height:100vh;padding:24px}.topbar,.calendar-stage,.planner-panel,.list-panel,.loading-panel{background:var(--panel);border:1px solid var(--line);box-shadow:var(--shadow);border-radius:24px}.topbar{display:flex;justify-content:space-between;gap:24px;align-items:center;padding:24px 28px}.topbar h1,.login-card h1,.section-head h2{font-family:Avenir Next Condensed,Franklin Gothic Medium,Arial Narrow,sans-serif;text-transform:uppercase;letter-spacing:.06em;margin:0}.eyebrow,.hint-line,.meta-line{color:var(--muted);margin:0}.badge,.open-pill{border-radius:999px;padding:8px 12px;font-size:.88rem;background:var(--badge-bg)}.open-pill.is-missing,.phase-state.is-missing{background:#a8333329;color:var(--danger)}.open-pill.is-complete,.phase-state.is-complete{background:#2c7a4b29;color:var(--success)}.open-pill.is-over,.phase-state.is-over{background:#d99a1a33;color:#a96b00}.tab-strip{display:flex;gap:10px;margin:16px 0;flex-wrap:wrap}.tab-button,.ghost-button,.primary-button,.danger-button{border-radius:999px;border:1px solid var(--line);padding:10px 16px;cursor:pointer}.tab-button,.ghost-button{background:var(--ghost-bg);color:var(--ink)}.tab-button.active,.primary-button{background:var(--accent);color:#fffaf4;border-color:transparent}.danger-button{background:#8f1d28;color:#fff;border-color:transparent}.content-grid{display:grid;grid-template-columns:1.5fr .9fr;gap:18px;align-items:start}.calendar-stage,.planner-panel,.list-panel{padding:20px}.section-head{display:flex;justify-content:space-between;gap:16px;align-items:start;margin-bottom:18px}.toolbar,.topbar-actions,.mode-switch,.modal-actions{display:flex;gap:10px;flex-wrap:wrap}.calendar-filter{display:grid;gap:6px;color:var(--muted);font-size:.92rem}.calendar-grid{display:grid;grid-template-columns:88px repeat(7,minmax(0,1fr));gap:0;border-radius:18px;overflow:hidden;border:1px solid rgba(17,18,23,.08)}.calendar-head{padding:10px 12px;background:var(--surface-soft);border-right:1px solid rgba(17,18,23,.08);border-bottom:1px solid rgba(17,18,23,.08);display:grid;gap:4px}.calendar-head.sunday-head{background:#f7d8d8}.calendar-head.holiday-head{background:#f1c8c8}.calendar-head.past-head{background:linear-gradient(180deg,#1112171f,#1112171f),var(--surface-soft);color:color-mix(in srgb,var(--ink) 72%,transparent)}.sun-meta{color:var(--muted);font-size:.78rem}.day-summary{display:grid;gap:3px;margin-top:2px}.day-summary-line{display:flex;align-items:baseline;gap:6px;font-size:.75rem;color:var(--muted);flex-wrap:wrap}.day-summary-line strong{color:var(--ink);font-size:.82rem}.break-line.ok,.break-line.ok strong{color:var(--success)}.break-line.warning,.break-line.warning strong{color:var(--danger)}.holiday-meta{color:#8f1d28;font-size:.78rem;font-weight:700}.time-grid,.day-grid{display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:repeat(96,28px);position:relative}.time-grid{background:var(--surface-time)}.day-grid{background:#ffffffd1;border-right:1px solid rgba(17,18,23,.08)}.day-grid.sunday-column{background:linear-gradient(180deg,#c1272d14,#c1272d14),#ffffffd1}.day-grid.holiday-column{background:linear-gradient(180deg,#8f1d281f,#8f1d281f),#ffffffd1}.day-grid.past-column{background:linear-gradient(180deg,#1112170f,#1112170f),#ffffffd1}.day-past-overlay{grid-column:1;align-self:stretch;justify-self:stretch;margin:0;background:#11121729;pointer-events:none;z-index:1}.current-time-past{grid-column:1;align-self:stretch;justify-self:stretch;margin:0;background:#1112172e;pointer-events:none;z-index:1}.current-time-line{position:absolute;left:0;right:0;height:0;border-top:2px solid color-mix(in srgb,var(--accent) 72%,white);pointer-events:none;z-index:4}.current-time-line span{position:absolute;top:-12px;right:6px;padding:2px 8px;border-radius:999px;background:color-mix(in srgb,var(--accent) 90%,white);color:#fffaf4;font-size:.66rem;font-weight:700;letter-spacing:.03em;box-shadow:0 4px 12px #11121729}.slot-cell{grid-column:1;min-height:28px;padding:0;border:0;border-top:1px solid rgba(17,18,23,.05);border-radius:0;background:transparent;position:relative;overflow:hidden;z-index:2}.slot-label{align-self:stretch;display:flex;align-items:center;padding:4px 8px;color:var(--muted);font-size:.82rem;border-top:1px solid rgba(17,18,23,.05);border-right:1px solid rgba(17,18,23,.08)}.slot-label.full-hour,.slot-cell.full-hour{border-top:1px solid rgba(17,18,23,.16)}.resource-lane,.mobile-day-stack{border-radius:14px;background:#ffffffa3;padding:8px;min-height:100px}.lane-caption{font-size:.75rem;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);margin-bottom:6px}.appointment-card,.entity-item{width:100%;text-align:left;border:0;border-radius:14px;padding:10px 12px;cursor:pointer}.appointment-card{background:linear-gradient(135deg,var(--appointment-color),color-mix(in srgb,var(--appointment-color) 45%,white));color:#fff;display:grid;gap:4px;margin-bottom:8px;font-size:.92rem}.compact-card{padding:4px 6px;border-radius:8px;gap:6px;margin-bottom:0;font-size:.7rem;line-height:1.15;min-height:calc(var(--span-blocks, 1) * 28px - 4px);height:calc(var(--span-blocks, 1) * 28px - 4px);align-content:stretch;grid-template-rows:1fr auto;position:relative;z-index:2;overflow:hidden}.appointment-top{display:grid;gap:2px;align-content:start}.appointment-title-row{display:flex;align-items:center;justify-content:space-between;gap:8px}.appointment-student{font-weight:700}.appointment-meta{opacity:.9;font-size:.66rem}.appointment-bottom{align-self:end;padding-top:4px;border-top:1px solid rgba(255,250,244,.35);font-weight:700}.vision-badge{display:inline-flex;align-items:center;justify-content:center;padding:2px 6px;border-radius:999px;background:#fffaf438;border:1px solid rgba(255,250,244,.32);font-size:.62rem;line-height:1;white-space:nowrap}.vision-inline{font-weight:600;opacity:.92}.day-appointment{grid-column:1;align-self:stretch;justify-self:stretch;margin:2px 3px;z-index:3}.rest-stripe{grid-column:1;align-self:stretch;justify-self:stretch;margin:0;z-index:0;pointer-events:auto;position:relative;cursor:pointer}.rest-stripe.warning{background:repeating-linear-gradient(-45deg,#8f1d2829,#8f1d2829 8px,#8f1d280d 8px,#8f1d280d 16px);border-left:3px solid rgba(143,29,40,.6)}.rest-stripe.ok{background:repeating-linear-gradient(-45deg,#0e5d801f,#0e5d801f 8px,#0e5d800a 8px,#0e5d800a 16px);border-left:3px solid rgba(14,93,128,.48)}.rest-stripe span{position:sticky;top:4px;display:inline-block;margin:4px 6px;padding:2px 6px;border-radius:999px;background:#fffaf4db;color:#0e5d80;font-size:.68rem;pointer-events:none}.pre-start-stripe{grid-column:1;align-self:stretch;justify-self:stretch;margin:0;z-index:1;pointer-events:none;position:relative;background:repeating-linear-gradient(-45deg,#a7482629,#a7482629 8px,#a748260d 8px,#a748260d 16px);border-left:3px solid rgba(167,72,38,.56)}.pre-start-stripe span{position:sticky;top:4px;display:inline-block;margin:4px 6px;padding:2px 6px;border-radius:999px;background:#fffaf4e6;color:var(--accent-deep);font-size:.68rem}.sun-band{grid-column:1;align-self:stretch;justify-self:stretch;margin:0;z-index:0;pointer-events:none;position:relative;background:repeating-linear-gradient(180deg,#11121714,#11121714 8px,#11121724 8px,#11121724 16px)}.sun-band.top-band{border-bottom:2px solid rgba(14,93,128,.35)}.sun-band.bottom-band{border-top:2px solid rgba(14,93,128,.35)}.sun-band span{position:sticky;display:inline-block;margin:4px 6px;padding:2px 6px;border-radius:999px;background:#fffaf4e6;color:var(--ink);font-size:.68rem;box-shadow:0 1px #11121714}.sun-band.top-band span{top:4px}.sun-band.bottom-band span{bottom:4px}.planner-panel{position:sticky;top:24px}.block-bank,.entity-list,.requirements-grid{display:grid;gap:10px}.autocomplete-shell{position:relative}.autocomplete-list{position:absolute;top:calc(100% + 8px);left:0;right:0;display:grid;gap:6px;padding:8px;border-radius:16px;border:1px solid var(--line);background:#fffaf4;box-shadow:var(--shadow);z-index:8}.autocomplete-option{width:100%;border:0;background:#1112170d;border-radius:12px;padding:10px 12px;text-align:left;display:grid;gap:2px;cursor:pointer}.autocomplete-option span{color:var(--muted);font-size:.9rem}.location-search{display:flex;gap:10px}.location-search input{flex:1 1 auto}.form-error{margin:-6px 0 0;color:#8f1d28;font-size:.92rem}.time-editor-grid{display:grid;gap:14px;grid-template-columns:repeat(3,minmax(0,1fr));align-items:start}.time-dial-field{position:relative}.time-dial-button{width:100%;border:1px solid rgba(17,18,23,.12);border-radius:16px;padding:12px 14px;background:var(--input-bg);color:var(--ink);display:flex;justify-content:space-between;gap:12px;align-items:center;cursor:pointer}.time-dial-popover{position:absolute;top:calc(100% + 8px);left:50%;transform:translate(-50%);width:min(340px,92vw);padding:14px;border-radius:20px;border:1px solid var(--line);background:#fffaf4;box-shadow:var(--shadow);display:grid;gap:12px;z-index:12}.time-dial-head{display:flex;justify-content:space-between;gap:12px;align-items:start}.time-dial-face{position:relative;width:280px;height:280px;margin:0 auto;border-radius:999px;background:radial-gradient(circle at center,rgba(17,18,23,.08) 0,rgba(17,18,23,.08) 10px,transparent 11px),radial-gradient(circle at center,transparent 0 54px,rgba(17,18,23,.06) 55px 56px,transparent 57px),linear-gradient(180deg,#ffffffb8,#efe6d7db);border:1px solid var(--line)}.time-dial-center{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-family:Avenir Next Condensed,Franklin Gothic Medium,Arial Narrow,sans-serif;font-size:1.7rem;letter-spacing:.05em}.time-dial-point{position:absolute;transform:translate(-50%,-50%);width:42px;height:42px;border-radius:999px;border:1px solid rgba(17,18,23,.08);background:#ffffffc7;color:var(--ink);cursor:pointer}.time-dial-point.inner-ring{width:38px;height:38px;font-size:.84rem;background:#efe6d7eb}.time-dial-point.outer-ring{box-shadow:0 4px 12px #11121714}.time-dial-point.active{background:var(--accent);color:#fffaf4;border-color:transparent}.duration-preset-panel,.share-panel,.quick-plan-panel{display:grid;gap:12px;padding:14px;border-radius:18px;border:1px solid var(--line);background:#ffffff6b}.compact-head{margin-bottom:0}.time-editor-grid.two-col{grid-template-columns:repeat(2,minmax(0,1fr))}.duration-preset-row,.share-actions{display:flex;gap:10px;flex-wrap:wrap}.share-button{display:inline-flex;align-items:center;gap:10px;padding:10px 14px;border-radius:999px;border:1px solid var(--line);background:var(--ghost-bg);color:var(--ink);text-decoration:none}.share-button.disabled{opacity:.45;cursor:default}.share-badge{min-width:36px;height:36px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;color:#fffaf4;font-weight:700}.share-badge.whatsapp{background:#1d8f5a}.share-badge.sms{background:#0e5d80}.share-badge.email{background:#a74826}.location-result-list{display:grid;gap:8px}.location-result{width:100%;border:1px solid var(--line);background:#ffffffc7;border-radius:14px;padding:10px 12px;text-align:left;display:grid;gap:4px;cursor:pointer}.location-result span{color:var(--muted);font-size:.88rem}.coordinate-grid{display:grid;gap:14px;grid-template-columns:repeat(2,minmax(0,1fr))}.map-preview{display:grid;gap:8px}.map-preview iframe{width:100%;min-height:260px;border:1px solid var(--line);border-radius:18px;background:#e7efe9}.location-hint{display:grid;gap:4px;padding:12px 14px;border-radius:16px;background:#0e5d8014;border:1px solid rgba(14,93,128,.14)}.requirement-title{display:inline-flex;align-items:center;gap:8px}.color-dot{width:12px;height:12px;border-radius:999px;background:var(--dot-color);box-shadow:inset 0 0 0 1px #1112171f;flex:0 0 auto}.visibility-matrix-wrap{overflow-x:auto;border:1px solid var(--line);border-radius:18px;background:#ffffff94}.visibility-matrix{width:100%;border-collapse:collapse;min-width:720px}.visibility-matrix th,.visibility-matrix td{padding:12px 14px;border-bottom:1px solid var(--line);text-align:center}.visibility-matrix thead th{position:sticky;top:0;background:#f5eee2;z-index:1;font-size:.82rem;text-transform:uppercase;letter-spacing:.06em}.visibility-matrix tbody th{text-align:left;background:#11121708;min-width:180px}.visibility-matrix tbody tr:last-child th,.visibility-matrix tbody tr:last-child td{border-bottom:0}.lesson-chip{border-radius:18px;padding:14px;background:linear-gradient(135deg,color-mix(in srgb,var(--chip-color) 78%,white),#ffffffbf),repeating-linear-gradient(-45deg,#ffffff2e,#ffffff2e 8px,#ffffff05 8px,#ffffff05 16px);color:#fff;cursor:grab;display:grid;gap:4px}.lesson-chip.active{box-shadow:inset 0 0 0 2px #fffaf4e6,0 0 0 2px color-mix(in srgb,var(--chip-color) 72%,white)}.progress-panel{margin-top:16px;padding-top:16px;border-top:1px solid var(--line)}.progress-row{display:flex;justify-content:space-between;gap:12px;padding:10px 0;border-bottom:1px solid var(--line)}.progress-phase,.training-phase,.requirement-phase{display:grid;gap:10px}.progress-phase+.progress-phase,.training-phase+.training-phase,.requirement-phase+.requirement-phase{margin-top:14px}.phase-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 10px;border-radius:12px;background:var(--badge-bg)}.phase-state{font-size:.86rem;font-weight:700;border-radius:999px;padding:6px 10px}.student-page{grid-column:1 / -1}.student-list-row{display:grid;gap:4px}.student-list-row span{color:var(--muted)}.student-detail-page{max-width:980px}.detail-tabs{margin-bottom:18px}.training-panel{display:grid;gap:12px}.training-row{display:grid;gap:10px;padding:14px;border:1px solid var(--line);border-radius:18px;background:#ffffff75}:root[data-theme=dark] .training-row{background:#ffffff0a}.training-row p{margin:4px 0 0;color:var(--muted)}.training-row-head{display:grid;grid-template-columns:minmax(0,1fr) 150px;gap:14px;align-items:end}.training-import-field{display:grid;gap:6px;color:var(--muted);font-size:.9rem}.training-meter{height:12px;overflow:hidden;border-radius:999px;background:var(--badge-bg)}.training-meter span{display:block;height:100%;border-radius:inherit;background:linear-gradient(90deg,var(--teal),var(--accent))}.requirement-phase h3{margin:0;font-size:.95rem}.entity-item{background:#1112170d}.lesson-type-list{gap:12px}.lesson-type-row{display:flex;align-items:center;gap:12px;justify-content:space-between}.lesson-type-main{display:grid;gap:2px;flex:1 1 auto}.lesson-type-actions{display:flex;gap:8px;flex:0 0 auto}.reorder-button[disabled]{opacity:.45;cursor:default}.modal-shell{position:fixed;top:0;right:0;bottom:0;left:0;background:#11121747;display:grid;align-items:start;justify-items:center;padding:24px;overflow-y:auto;z-index:50}.modal-card,.login-card{width:min(720px,100%);max-height:calc(100dvh - 48px);border-radius:24px;padding:24px;background:#fffaf4;box-shadow:var(--shadow);border:1px solid rgba(17,18,23,.08);overflow-y:auto}.modal-card .modal-actions{position:sticky;bottom:0;padding-top:12px;background:linear-gradient(180deg,#fffaf400,#fffaf4 24px)}.stack-form{display:grid;gap:14px}.stack-form label{display:grid;gap:8px}.checkbox-row{display:flex!important;align-items:center;gap:10px}.checkbox-row span{font-weight:600}input,select,textarea{width:100%;border-radius:14px;border:1px solid rgba(17,18,23,.12);padding:12px 14px;background:var(--input-bg);color:var(--ink)}input[type=checkbox]{width:auto}.login-shell{min-height:100vh;display:grid;place-items:center;padding:24px}.login-card{background:linear-gradient(180deg,color-mix(in srgb,var(--panel) 96%,white),color-mix(in srgb,var(--surface-soft) 92%,transparent)),repeating-linear-gradient(90deg,rgba(17,18,23,.04),rgba(17,18,23,.04) 1px,transparent 1px,transparent 24px)}.login-copy{max-width:38ch}.inline-error,.flash-error{background:#8f1d281f;color:#6b1820;border-radius:14px;padding:12px 14px}.flash-error{margin-bottom:14px}.loading-panel{min-height:50vh;display:grid;place-items:center}.compact{padding:8px 12px}@media (max-width: 960px){.app-shell{padding:14px}.content-grid{grid-template-columns:1fr}.modal-shell{padding:14px}.modal-card{max-height:calc(100dvh - 28px);padding:18px}.calendar-grid{grid-template-columns:72px 1fr}.time-editor-grid{grid-template-columns:1fr}.time-dial-popover{position:static;width:100%}.time-dial-face{width:min(280px,100%);height:min(280px,calc(100vw - 88px))}.planner-panel{position:static}.topbar{flex-direction:column;align-items:flex-start}.location-search,.coordinate-grid,.training-row-head{display:grid;grid-template-columns:1fr}} diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/assets/assets/main-F76uNDS1.js b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/assets/assets/main-F76uNDS1.js new file mode 100644 index 0000000..0310b69 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/assets/assets/main-F76uNDS1.js @@ -0,0 +1,44 @@ +function Zc(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Do={exports:{}},jl={},Lo={exports:{}},U={};/** + * @license React + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var hr=Symbol.for("react.element"),Jc=Symbol.for("react.portal"),ed=Symbol.for("react.fragment"),td=Symbol.for("react.strict_mode"),nd=Symbol.for("react.profiler"),rd=Symbol.for("react.provider"),ld=Symbol.for("react.context"),id=Symbol.for("react.forward_ref"),sd=Symbol.for("react.suspense"),ad=Symbol.for("react.memo"),od=Symbol.for("react.lazy"),ya=Symbol.iterator;function ud(e){return e===null||typeof e!="object"?null:(e=ya&&e[ya]||e["@@iterator"],typeof e=="function"?e:null)}var Fo={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Ro=Object.assign,$o={};function En(e,t,n){this.props=e,this.context=t,this.refs=$o,this.updater=n||Fo}En.prototype.isReactComponent={};En.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};En.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function Oo(){}Oo.prototype=En.prototype;function xs(e,t,n){this.props=e,this.context=t,this.refs=$o,this.updater=n||Fo}var ws=xs.prototype=new Oo;ws.constructor=xs;Ro(ws,En.prototype);ws.isPureReactComponent=!0;var xa=Array.isArray,Io=Object.prototype.hasOwnProperty,Ss={current:null},Bo={key:!0,ref:!0,__self:!0,__source:!0};function Ao(e,t,n){var r,l={},i=null,s=null;if(t!=null)for(r in t.ref!==void 0&&(s=t.ref),t.key!==void 0&&(i=""+t.key),t)Io.call(t,r)&&!Bo.hasOwnProperty(r)&&(l[r]=t[r]);var o=arguments.length-2;if(o===1)l.children=n;else if(1>>1,Q=P[q];if(0>>1;ql(Fe,I))_el(vt,Fe)?(P[q]=vt,P[_e]=I,q=_e):(P[q]=Fe,P[Y]=I,q=Y);else if(_el(vt,I))P[q]=vt,P[_e]=I,q=_e;else break e}}return O}function l(P,O){var I=P.sortIndex-O.sortIndex;return I!==0?I:P.id-O.id}if(typeof performance=="object"&&typeof performance.now=="function"){var i=performance;e.unstable_now=function(){return i.now()}}else{var s=Date,o=s.now();e.unstable_now=function(){return s.now()-o}}var u=[],d=[],g=1,h=null,m=3,x=!1,w=!1,v=!1,D=typeof setTimeout=="function"?setTimeout:null,f=typeof clearTimeout=="function"?clearTimeout:null,c=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function p(P){for(var O=n(d);O!==null;){if(O.callback===null)r(d);else if(O.startTime<=P)r(d),O.sortIndex=O.expirationTime,t(u,O);else break;O=n(d)}}function y(P){if(v=!1,p(P),!w)if(n(u)!==null)w=!0,V(j);else{var O=n(d);O!==null&&J(y,O.startTime-P)}}function j(P,O){w=!1,v&&(v=!1,f(T),T=-1),x=!0;var I=m;try{for(p(O),h=n(u);h!==null&&(!(h.expirationTime>O)||P&&!W());){var q=h.callback;if(typeof q=="function"){h.callback=null,m=h.priorityLevel;var Q=q(h.expirationTime<=O);O=e.unstable_now(),typeof Q=="function"?h.callback=Q:h===n(u)&&r(u),p(O)}else r(u);h=n(u)}if(h!==null)var it=!0;else{var Y=n(d);Y!==null&&J(y,Y.startTime-O),it=!1}return it}finally{h=null,m=I,x=!1}}var M=!1,S=null,T=-1,$=5,L=-1;function W(){return!(e.unstable_now()-L<$)}function C(){if(S!==null){var P=e.unstable_now();L=P;var O=!0;try{O=S(!0,P)}finally{O?R():(M=!1,S=null)}}else M=!1}var R;if(typeof c=="function")R=function(){c(C)};else if(typeof MessageChannel<"u"){var A=new MessageChannel,N=A.port2;A.port1.onmessage=C,R=function(){N.postMessage(null)}}else R=function(){D(C,0)};function V(P){S=P,M||(M=!0,R())}function J(P,O){T=D(function(){P(e.unstable_now())},O)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(P){P.callback=null},e.unstable_continueExecution=function(){w||x||(w=!0,V(j))},e.unstable_forceFrameRate=function(P){0>P||125q?(P.sortIndex=I,t(d,P),n(u)===null&&P===n(d)&&(v?(f(T),T=-1):v=!0,J(y,I-q))):(P.sortIndex=Q,t(u,P),w||x||(w=!0,V(j))),P},e.unstable_shouldYield=W,e.unstable_wrapCallback=function(P){var O=m;return function(){var I=m;m=O;try{return P.apply(this,arguments)}finally{m=I}}}})(Qo);Wo.exports=Qo;var Sd=Wo.exports;/** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var _d=F,Ie=Sd;function k(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Ni=Object.prototype.hasOwnProperty,kd=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,Sa={},_a={};function Nd(e){return Ni.call(_a,e)?!0:Ni.call(Sa,e)?!1:kd.test(e)?_a[e]=!0:(Sa[e]=!0,!1)}function jd(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function Ed(e,t,n,r){if(t===null||typeof t>"u"||jd(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function Ce(e,t,n,r,l,i,s){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=l,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=i,this.removeEmptyString=s}var pe={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){pe[e]=new Ce(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];pe[t]=new Ce(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){pe[e]=new Ce(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){pe[e]=new Ce(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){pe[e]=new Ce(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){pe[e]=new Ce(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){pe[e]=new Ce(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){pe[e]=new Ce(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){pe[e]=new Ce(e,5,!1,e.toLowerCase(),null,!1,!1)});var ks=/[\-:]([a-z])/g;function Ns(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(ks,Ns);pe[t]=new Ce(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(ks,Ns);pe[t]=new Ce(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(ks,Ns);pe[t]=new Ce(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){pe[e]=new Ce(e,1,!1,e.toLowerCase(),null,!1,!1)});pe.xlinkHref=new Ce("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){pe[e]=new Ce(e,1,!1,e.toLowerCase(),null,!0,!0)});function js(e,t,n,r){var l=pe.hasOwnProperty(t)?pe[t]:null;(l!==null?l.type!==0:r||!(2o||l[s]!==i[o]){var u=` +`+l[s].replace(" at new "," at ");return e.displayName&&u.includes("")&&(u=u.replace("",e.displayName)),u}while(1<=s&&0<=o);break}}}finally{Gl=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?On(e):""}function Cd(e){switch(e.tag){case 5:return On(e.type);case 16:return On("Lazy");case 13:return On("Suspense");case 19:return On("SuspenseList");case 0:case 2:case 15:return e=Zl(e.type,!1),e;case 11:return e=Zl(e.type.render,!1),e;case 1:return e=Zl(e.type,!0),e;default:return""}}function Mi(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case tn:return"Fragment";case en:return"Portal";case ji:return"Profiler";case Es:return"StrictMode";case Ei:return"Suspense";case Ci:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case qo:return(e.displayName||"Context")+".Consumer";case bo:return(e._context.displayName||"Context")+".Provider";case Cs:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case Ms:return t=e.displayName||null,t!==null?t:Mi(e.type)||"Memo";case yt:t=e._payload,e=e._init;try{return Mi(e(t))}catch{}}return null}function Md(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return Mi(t);case 8:return t===Es?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function Ft(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function Xo(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function Pd(e){var t=Xo(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var l=n.get,i=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(s){r=""+s,i.call(this,s)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(s){r=""+s},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function kr(e){e._valueTracker||(e._valueTracker=Pd(e))}function Go(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=Xo(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function el(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Pi(e,t){var n=t.checked;return re({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Na(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=Ft(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function Zo(e,t){t=t.checked,t!=null&&js(e,"checked",t,!1)}function Ti(e,t){Zo(e,t);var n=Ft(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?zi(e,t.type,n):t.hasOwnProperty("defaultValue")&&zi(e,t.type,Ft(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function ja(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function zi(e,t,n){(t!=="number"||el(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var In=Array.isArray;function pn(e,t,n,r){if(e=e.options,t){t={};for(var l=0;l"+t.valueOf().toString()+"",t=Nr.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Jn(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var Vn={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},Td=["Webkit","ms","Moz","O"];Object.keys(Vn).forEach(function(e){Td.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),Vn[t]=Vn[e]})});function nu(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||Vn.hasOwnProperty(e)&&Vn[e]?(""+t).trim():t+"px"}function ru(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,l=nu(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,l):e[n]=l}}var zd=re({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function Fi(e,t){if(t){if(zd[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(k(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(k(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(k(61))}if(t.style!=null&&typeof t.style!="object")throw Error(k(62))}}function Ri(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var $i=null;function Ps(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var Oi=null,mn=null,hn=null;function Ma(e){if(e=yr(e)){if(typeof Oi!="function")throw Error(k(280));var t=e.stateNode;t&&(t=Tl(t),Oi(e.stateNode,e.type,t))}}function lu(e){mn?hn?hn.push(e):hn=[e]:mn=e}function iu(){if(mn){var e=mn,t=hn;if(hn=mn=null,Ma(e),t)for(e=0;e>>=0,e===0?32:31-(Vd(e)/Hd|0)|0}var jr=64,Er=4194304;function Bn(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function ll(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,l=e.suspendedLanes,i=e.pingedLanes,s=n&268435455;if(s!==0){var o=s&~l;o!==0?r=Bn(o):(i&=s,i!==0&&(r=Bn(i)))}else s=n&~l,s!==0?r=Bn(s):i!==0&&(r=Bn(i));if(r===0)return 0;if(t!==0&&t!==r&&!(t&l)&&(l=r&-r,i=t&-t,l>=i||l===16&&(i&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function vr(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-Ge(t),e[t]=n}function bd(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=Wn),Oa=" ",Ia=!1;function ju(e,t){switch(e){case"keyup":return _f.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Eu(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var nn=!1;function Nf(e,t){switch(e){case"compositionend":return Eu(t);case"keypress":return t.which!==32?null:(Ia=!0,Oa);case"textInput":return e=t.data,e===Oa&&Ia?null:e;default:return null}}function jf(e,t){if(nn)return e==="compositionend"||!Os&&ju(e,t)?(e=ku(),Hr=Fs=kt=null,nn=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=Va(n)}}function Tu(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Tu(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function zu(){for(var e=window,t=el();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=el(e.document)}return t}function Is(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function Ff(e){var t=zu(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&Tu(n.ownerDocument.documentElement,n)){if(r!==null&&Is(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var l=n.textContent.length,i=Math.min(r.start,l);r=r.end===void 0?i:Math.min(r.end,l),!e.extend&&i>r&&(l=r,r=i,i=l),l=Ha(n,i);var s=Ha(n,r);l&&s&&(e.rangeCount!==1||e.anchorNode!==l.node||e.anchorOffset!==l.offset||e.focusNode!==s.node||e.focusOffset!==s.offset)&&(t=t.createRange(),t.setStart(l.node,l.offset),e.removeAllRanges(),i>r?(e.addRange(t),e.extend(s.node,s.offset)):(t.setEnd(s.node,s.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,rn=null,Hi=null,Kn=null,Wi=!1;function Wa(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Wi||rn==null||rn!==el(r)||(r=rn,"selectionStart"in r&&Is(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Kn&&ir(Kn,r)||(Kn=r,r=al(Hi,"onSelect"),0an||(e.current=Xi[an],Xi[an]=null,an--)}function X(e,t){an++,Xi[an]=e.current,e.current=t}var Rt={},Se=Ot(Rt),ze=Ot(!1),Kt=Rt;function wn(e,t){var n=e.type.contextTypes;if(!n)return Rt;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var l={},i;for(i in n)l[i]=t[i];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=l),l}function De(e){return e=e.childContextTypes,e!=null}function ul(){Z(ze),Z(Se)}function Ga(e,t,n){if(Se.current!==Rt)throw Error(k(168));X(Se,t),X(ze,n)}function Au(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var l in r)if(!(l in t))throw Error(k(108,Md(e)||"Unknown",l));return re({},n,r)}function cl(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||Rt,Kt=Se.current,X(Se,e),X(ze,ze.current),!0}function Za(e,t,n){var r=e.stateNode;if(!r)throw Error(k(169));n?(e=Au(e,t,Kt),r.__reactInternalMemoizedMergedChildContext=e,Z(ze),Z(Se),X(Se,e)):Z(ze),X(ze,n)}var at=null,zl=!1,fi=!1;function Uu(e){at===null?at=[e]:at.push(e)}function Kf(e){zl=!0,Uu(e)}function It(){if(!fi&&at!==null){fi=!0;var e=0,t=K;try{var n=at;for(K=1;e>=s,l-=s,ot=1<<32-Ge(t)+l|n<T?($=S,S=null):$=S.sibling;var L=m(f,S,p[T],y);if(L===null){S===null&&(S=$);break}e&&S&&L.alternate===null&&t(f,S),c=i(L,c,T),M===null?j=L:M.sibling=L,M=L,S=$}if(T===p.length)return n(f,S),ee&&Bt(f,T),j;if(S===null){for(;TT?($=S,S=null):$=S.sibling;var W=m(f,S,L.value,y);if(W===null){S===null&&(S=$);break}e&&S&&W.alternate===null&&t(f,S),c=i(W,c,T),M===null?j=W:M.sibling=W,M=W,S=$}if(L.done)return n(f,S),ee&&Bt(f,T),j;if(S===null){for(;!L.done;T++,L=p.next())L=h(f,L.value,y),L!==null&&(c=i(L,c,T),M===null?j=L:M.sibling=L,M=L);return ee&&Bt(f,T),j}for(S=r(f,S);!L.done;T++,L=p.next())L=x(S,f,T,L.value,y),L!==null&&(e&&L.alternate!==null&&S.delete(L.key===null?T:L.key),c=i(L,c,T),M===null?j=L:M.sibling=L,M=L);return e&&S.forEach(function(C){return t(f,C)}),ee&&Bt(f,T),j}function D(f,c,p,y){if(typeof p=="object"&&p!==null&&p.type===tn&&p.key===null&&(p=p.props.children),typeof p=="object"&&p!==null){switch(p.$$typeof){case _r:e:{for(var j=p.key,M=c;M!==null;){if(M.key===j){if(j=p.type,j===tn){if(M.tag===7){n(f,M.sibling),c=l(M,p.props.children),c.return=f,f=c;break e}}else if(M.elementType===j||typeof j=="object"&&j!==null&&j.$$typeof===yt&&to(j)===M.type){n(f,M.sibling),c=l(M,p.props),c.ref=Fn(f,M,p),c.return=f,f=c;break e}n(f,M);break}else t(f,M);M=M.sibling}p.type===tn?(c=Qt(p.props.children,f.mode,y,p.key),c.return=f,f=c):(y=Gr(p.type,p.key,p.props,null,f.mode,y),y.ref=Fn(f,c,p),y.return=f,f=y)}return s(f);case en:e:{for(M=p.key;c!==null;){if(c.key===M)if(c.tag===4&&c.stateNode.containerInfo===p.containerInfo&&c.stateNode.implementation===p.implementation){n(f,c.sibling),c=l(c,p.children||[]),c.return=f,f=c;break e}else{n(f,c);break}else t(f,c);c=c.sibling}c=wi(p,f.mode,y),c.return=f,f=c}return s(f);case yt:return M=p._init,D(f,c,M(p._payload),y)}if(In(p))return w(f,c,p,y);if(Pn(p))return v(f,c,p,y);Lr(f,p)}return typeof p=="string"&&p!==""||typeof p=="number"?(p=""+p,c!==null&&c.tag===6?(n(f,c.sibling),c=l(c,p),c.return=f,f=c):(n(f,c),c=xi(p,f.mode,y),c.return=f,f=c),s(f)):n(f,c)}return D}var _n=Qu(!0),Ku=Qu(!1),pl=Ot(null),ml=null,cn=null,Vs=null;function Hs(){Vs=cn=ml=null}function Ws(e){var t=pl.current;Z(pl),e._currentValue=t}function Ji(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function gn(e,t){ml=e,Vs=cn=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(Te=!0),e.firstContext=null)}function Qe(e){var t=e._currentValue;if(Vs!==e)if(e={context:e,memoizedValue:t,next:null},cn===null){if(ml===null)throw Error(k(308));cn=e,ml.dependencies={lanes:0,firstContext:e}}else cn=cn.next=e;return t}var Vt=null;function Qs(e){Vt===null?Vt=[e]:Vt.push(e)}function bu(e,t,n,r){var l=t.interleaved;return l===null?(n.next=n,Qs(t)):(n.next=l.next,l.next=n),t.interleaved=n,pt(e,r)}function pt(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var xt=!1;function Ks(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function qu(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function ct(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function Tt(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,H&2){var l=r.pending;return l===null?t.next=t:(t.next=l.next,l.next=t),r.pending=t,pt(e,n)}return l=r.interleaved,l===null?(t.next=t,Qs(r)):(t.next=l.next,l.next=t),r.interleaved=t,pt(e,n)}function Qr(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,zs(e,n)}}function no(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var l=null,i=null;if(n=n.firstBaseUpdate,n!==null){do{var s={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};i===null?l=i=s:i=i.next=s,n=n.next}while(n!==null);i===null?l=i=t:i=i.next=t}else l=i=t;n={baseState:r.baseState,firstBaseUpdate:l,lastBaseUpdate:i,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function hl(e,t,n,r){var l=e.updateQueue;xt=!1;var i=l.firstBaseUpdate,s=l.lastBaseUpdate,o=l.shared.pending;if(o!==null){l.shared.pending=null;var u=o,d=u.next;u.next=null,s===null?i=d:s.next=d,s=u;var g=e.alternate;g!==null&&(g=g.updateQueue,o=g.lastBaseUpdate,o!==s&&(o===null?g.firstBaseUpdate=d:o.next=d,g.lastBaseUpdate=u))}if(i!==null){var h=l.baseState;s=0,g=d=u=null,o=i;do{var m=o.lane,x=o.eventTime;if((r&m)===m){g!==null&&(g=g.next={eventTime:x,lane:0,tag:o.tag,payload:o.payload,callback:o.callback,next:null});e:{var w=e,v=o;switch(m=t,x=n,v.tag){case 1:if(w=v.payload,typeof w=="function"){h=w.call(x,h,m);break e}h=w;break e;case 3:w.flags=w.flags&-65537|128;case 0:if(w=v.payload,m=typeof w=="function"?w.call(x,h,m):w,m==null)break e;h=re({},h,m);break e;case 2:xt=!0}}o.callback!==null&&o.lane!==0&&(e.flags|=64,m=l.effects,m===null?l.effects=[o]:m.push(o))}else x={eventTime:x,lane:m,tag:o.tag,payload:o.payload,callback:o.callback,next:null},g===null?(d=g=x,u=h):g=g.next=x,s|=m;if(o=o.next,o===null){if(o=l.shared.pending,o===null)break;m=o,o=m.next,m.next=null,l.lastBaseUpdate=m,l.shared.pending=null}}while(!0);if(g===null&&(u=h),l.baseState=u,l.firstBaseUpdate=d,l.lastBaseUpdate=g,t=l.shared.interleaved,t!==null){l=t;do s|=l.lane,l=l.next;while(l!==t)}else i===null&&(l.shared.lanes=0);Yt|=s,e.lanes=s,e.memoizedState=h}}function ro(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=mi.transition;mi.transition={};try{e(!1),t()}finally{K=n,mi.transition=r}}function dc(){return Ke().memoizedState}function Xf(e,t,n){var r=Dt(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},fc(e))pc(t,n);else if(n=bu(e,t,n,r),n!==null){var l=je();Ze(n,e,r,l),mc(n,t,r)}}function Gf(e,t,n){var r=Dt(e),l={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(fc(e))pc(t,l);else{var i=e.alternate;if(e.lanes===0&&(i===null||i.lanes===0)&&(i=t.lastRenderedReducer,i!==null))try{var s=t.lastRenderedState,o=i(s,n);if(l.hasEagerState=!0,l.eagerState=o,Je(o,s)){var u=t.interleaved;u===null?(l.next=l,Qs(t)):(l.next=u.next,u.next=l),t.interleaved=l;return}}catch{}finally{}n=bu(e,t,l,r),n!==null&&(l=je(),Ze(n,e,r,l),mc(n,t,r))}}function fc(e){var t=e.alternate;return e===ne||t!==null&&t===ne}function pc(e,t){bn=gl=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function mc(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,zs(e,n)}}var yl={readContext:Qe,useCallback:ve,useContext:ve,useEffect:ve,useImperativeHandle:ve,useInsertionEffect:ve,useLayoutEffect:ve,useMemo:ve,useReducer:ve,useRef:ve,useState:ve,useDebugValue:ve,useDeferredValue:ve,useTransition:ve,useMutableSource:ve,useSyncExternalStore:ve,useId:ve,unstable_isNewReconciler:!1},Zf={readContext:Qe,useCallback:function(e,t){return tt().memoizedState=[e,t===void 0?null:t],e},useContext:Qe,useEffect:io,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,br(4194308,4,sc.bind(null,t,e),n)},useLayoutEffect:function(e,t){return br(4194308,4,e,t)},useInsertionEffect:function(e,t){return br(4,2,e,t)},useMemo:function(e,t){var n=tt();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=tt();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=Xf.bind(null,ne,e),[r.memoizedState,e]},useRef:function(e){var t=tt();return e={current:e},t.memoizedState=e},useState:lo,useDebugValue:ea,useDeferredValue:function(e){return tt().memoizedState=e},useTransition:function(){var e=lo(!1),t=e[0];return e=Yf.bind(null,e[1]),tt().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=ne,l=tt();if(ee){if(n===void 0)throw Error(k(407));n=n()}else{if(n=t(),ce===null)throw Error(k(349));qt&30||Zu(r,t,n)}l.memoizedState=n;var i={value:n,getSnapshot:t};return l.queue=i,io(ec.bind(null,r,i,e),[e]),r.flags|=2048,pr(9,Ju.bind(null,r,i,n,t),void 0,null),n},useId:function(){var e=tt(),t=ce.identifierPrefix;if(ee){var n=ut,r=ot;n=(r&~(1<<32-Ge(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=dr++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=s.createElement(n,{is:r.is}):(e=s.createElement(n),n==="select"&&(s=e,r.multiple?s.multiple=!0:r.size&&(s.size=r.size))):e=s.createElementNS(e,n),e[nt]=t,e[or]=r,Nc(e,t,!1,!1),t.stateNode=e;e:{switch(s=Ri(n,r),n){case"dialog":G("cancel",e),G("close",e),l=r;break;case"iframe":case"object":case"embed":G("load",e),l=r;break;case"video":case"audio":for(l=0;ljn&&(t.flags|=128,r=!0,Rn(i,!1),t.lanes=4194304)}else{if(!r)if(e=vl(s),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),Rn(i,!0),i.tail===null&&i.tailMode==="hidden"&&!s.alternate&&!ee)return ge(t),null}else 2*ie()-i.renderingStartTime>jn&&n!==1073741824&&(t.flags|=128,r=!0,Rn(i,!1),t.lanes=4194304);i.isBackwards?(s.sibling=t.child,t.child=s):(n=i.last,n!==null?n.sibling=s:t.child=s,i.last=s)}return i.tail!==null?(t=i.tail,i.rendering=t,i.tail=t.sibling,i.renderingStartTime=ie(),t.sibling=null,n=te.current,X(te,r?n&1|2:n&1),t):(ge(t),null);case 22:case 23:return sa(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?Re&1073741824&&(ge(t),t.subtreeFlags&6&&(t.flags|=8192)):ge(t),null;case 24:return null;case 25:return null}throw Error(k(156,t.tag))}function sp(e,t){switch(As(t),t.tag){case 1:return De(t.type)&&ul(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return kn(),Z(ze),Z(Se),Ys(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return qs(t),null;case 13:if(Z(te),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(k(340));Sn()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return Z(te),null;case 4:return kn(),null;case 10:return Ws(t.type._context),null;case 22:case 23:return sa(),null;case 24:return null;default:return null}}var Rr=!1,we=!1,ap=typeof WeakSet=="function"?WeakSet:Set,z=null;function dn(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){le(e,t,r)}else n.current=null}function os(e,t,n){try{n()}catch(r){le(e,t,r)}}var go=!1;function op(e,t){if(Qi=il,e=zu(),Is(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var l=r.anchorOffset,i=r.focusNode;r=r.focusOffset;try{n.nodeType,i.nodeType}catch{n=null;break e}var s=0,o=-1,u=-1,d=0,g=0,h=e,m=null;t:for(;;){for(var x;h!==n||l!==0&&h.nodeType!==3||(o=s+l),h!==i||r!==0&&h.nodeType!==3||(u=s+r),h.nodeType===3&&(s+=h.nodeValue.length),(x=h.firstChild)!==null;)m=h,h=x;for(;;){if(h===e)break t;if(m===n&&++d===l&&(o=s),m===i&&++g===r&&(u=s),(x=h.nextSibling)!==null)break;h=m,m=h.parentNode}h=x}n=o===-1||u===-1?null:{start:o,end:u}}else n=null}n=n||{start:0,end:0}}else n=null;for(Ki={focusedElem:e,selectionRange:n},il=!1,z=t;z!==null;)if(t=z,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,z=e;else for(;z!==null;){t=z;try{var w=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(w!==null){var v=w.memoizedProps,D=w.memoizedState,f=t.stateNode,c=f.getSnapshotBeforeUpdate(t.elementType===t.type?v:qe(t.type,v),D);f.__reactInternalSnapshotBeforeUpdate=c}break;case 3:var p=t.stateNode.containerInfo;p.nodeType===1?p.textContent="":p.nodeType===9&&p.documentElement&&p.removeChild(p.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(k(163))}}catch(y){le(t,t.return,y)}if(e=t.sibling,e!==null){e.return=t.return,z=e;break}z=t.return}return w=go,go=!1,w}function qn(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var l=r=r.next;do{if((l.tag&e)===e){var i=l.destroy;l.destroy=void 0,i!==void 0&&os(t,n,i)}l=l.next}while(l!==r)}}function Fl(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function us(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function Cc(e){var t=e.alternate;t!==null&&(e.alternate=null,Cc(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[nt],delete t[or],delete t[Yi],delete t[Wf],delete t[Qf])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function Mc(e){return e.tag===5||e.tag===3||e.tag===4}function yo(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||Mc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function cs(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=ol));else if(r!==4&&(e=e.child,e!==null))for(cs(e,t,n),e=e.sibling;e!==null;)cs(e,t,n),e=e.sibling}function ds(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(ds(e,t,n),e=e.sibling;e!==null;)ds(e,t,n),e=e.sibling}var de=null,Ye=!1;function gt(e,t,n){for(n=n.child;n!==null;)Pc(e,t,n),n=n.sibling}function Pc(e,t,n){if(rt&&typeof rt.onCommitFiberUnmount=="function")try{rt.onCommitFiberUnmount(El,n)}catch{}switch(n.tag){case 5:we||dn(n,t);case 6:var r=de,l=Ye;de=null,gt(e,t,n),de=r,Ye=l,de!==null&&(Ye?(e=de,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):de.removeChild(n.stateNode));break;case 18:de!==null&&(Ye?(e=de,n=n.stateNode,e.nodeType===8?di(e.parentNode,n):e.nodeType===1&&di(e,n),rr(e)):di(de,n.stateNode));break;case 4:r=de,l=Ye,de=n.stateNode.containerInfo,Ye=!0,gt(e,t,n),de=r,Ye=l;break;case 0:case 11:case 14:case 15:if(!we&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){l=r=r.next;do{var i=l,s=i.destroy;i=i.tag,s!==void 0&&(i&2||i&4)&&os(n,t,s),l=l.next}while(l!==r)}gt(e,t,n);break;case 1:if(!we&&(dn(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(o){le(n,t,o)}gt(e,t,n);break;case 21:gt(e,t,n);break;case 22:n.mode&1?(we=(r=we)||n.memoizedState!==null,gt(e,t,n),we=r):gt(e,t,n);break;default:gt(e,t,n)}}function xo(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new ap),t.forEach(function(r){var l=gp.bind(null,e,r);n.has(r)||(n.add(r),r.then(l,l))})}}function be(e,t){var n=t.deletions;if(n!==null)for(var r=0;rl&&(l=s),r&=~i}if(r=l,r=ie()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*cp(r/1960))-r,10e?16:e,Nt===null)var r=!1;else{if(e=Nt,Nt=null,Sl=0,H&6)throw Error(k(331));var l=H;for(H|=4,z=e.current;z!==null;){var i=z,s=i.child;if(z.flags&16){var o=i.deletions;if(o!==null){for(var u=0;uie()-la?Wt(e,0):ra|=n),Le(e,t)}function Oc(e,t){t===0&&(e.mode&1?(t=Er,Er<<=1,!(Er&130023424)&&(Er=4194304)):t=1);var n=je();e=pt(e,t),e!==null&&(vr(e,t,n),Le(e,n))}function vp(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Oc(e,n)}function gp(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(k(314))}r!==null&&r.delete(t),Oc(e,n)}var Ic;Ic=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||ze.current)Te=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return Te=!1,lp(e,t,n);Te=!!(e.flags&131072)}else Te=!1,ee&&t.flags&1048576&&Vu(t,fl,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;qr(e,t),e=t.pendingProps;var l=wn(t,Se.current);gn(t,n),l=Gs(null,t,r,e,l,n);var i=Zs();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,De(r)?(i=!0,cl(t)):i=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,Ks(t),l.updater=Ll,t.stateNode=l,l._reactInternals=t,ts(t,r,e,n),t=ls(null,t,r,!0,i,n)):(t.tag=0,ee&&i&&Bs(t),Ne(null,t,l,n),t=t.child),t;case 16:r=t.elementType;e:{switch(qr(e,t),e=t.pendingProps,l=r._init,r=l(r._payload),t.type=r,l=t.tag=xp(r),e=qe(r,e),l){case 0:t=rs(null,t,r,e,n);break e;case 1:t=mo(null,t,r,e,n);break e;case 11:t=fo(null,t,r,e,n);break e;case 14:t=po(null,t,r,qe(r.type,e),n);break e}throw Error(k(306,r,""))}return t;case 0:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:qe(r,l),rs(e,t,r,l,n);case 1:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:qe(r,l),mo(e,t,r,l,n);case 3:e:{if(Sc(t),e===null)throw Error(k(387));r=t.pendingProps,i=t.memoizedState,l=i.element,qu(e,t),hl(t,r,null,n);var s=t.memoizedState;if(r=s.element,i.isDehydrated)if(i={element:r,isDehydrated:!1,cache:s.cache,pendingSuspenseBoundaries:s.pendingSuspenseBoundaries,transitions:s.transitions},t.updateQueue.baseState=i,t.memoizedState=i,t.flags&256){l=Nn(Error(k(423)),t),t=ho(e,t,r,n,l);break e}else if(r!==l){l=Nn(Error(k(424)),t),t=ho(e,t,r,n,l);break e}else for($e=Pt(t.stateNode.containerInfo.firstChild),Oe=t,ee=!0,Xe=null,n=Ku(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(Sn(),r===l){t=mt(e,t,n);break e}Ne(e,t,r,n)}t=t.child}return t;case 5:return Yu(t),e===null&&Zi(t),r=t.type,l=t.pendingProps,i=e!==null?e.memoizedProps:null,s=l.children,bi(r,l)?s=null:i!==null&&bi(r,i)&&(t.flags|=32),wc(e,t),Ne(e,t,s,n),t.child;case 6:return e===null&&Zi(t),null;case 13:return _c(e,t,n);case 4:return bs(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=_n(t,null,r,n):Ne(e,t,r,n),t.child;case 11:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:qe(r,l),fo(e,t,r,l,n);case 7:return Ne(e,t,t.pendingProps,n),t.child;case 8:return Ne(e,t,t.pendingProps.children,n),t.child;case 12:return Ne(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,l=t.pendingProps,i=t.memoizedProps,s=l.value,X(pl,r._currentValue),r._currentValue=s,i!==null)if(Je(i.value,s)){if(i.children===l.children&&!ze.current){t=mt(e,t,n);break e}}else for(i=t.child,i!==null&&(i.return=t);i!==null;){var o=i.dependencies;if(o!==null){s=i.child;for(var u=o.firstContext;u!==null;){if(u.context===r){if(i.tag===1){u=ct(-1,n&-n),u.tag=2;var d=i.updateQueue;if(d!==null){d=d.shared;var g=d.pending;g===null?u.next=u:(u.next=g.next,g.next=u),d.pending=u}}i.lanes|=n,u=i.alternate,u!==null&&(u.lanes|=n),Ji(i.return,n,t),o.lanes|=n;break}u=u.next}}else if(i.tag===10)s=i.type===t.type?null:i.child;else if(i.tag===18){if(s=i.return,s===null)throw Error(k(341));s.lanes|=n,o=s.alternate,o!==null&&(o.lanes|=n),Ji(s,n,t),s=i.sibling}else s=i.child;if(s!==null)s.return=i;else for(s=i;s!==null;){if(s===t){s=null;break}if(i=s.sibling,i!==null){i.return=s.return,s=i;break}s=s.return}i=s}Ne(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,r=t.pendingProps.children,gn(t,n),l=Qe(l),r=r(l),t.flags|=1,Ne(e,t,r,n),t.child;case 14:return r=t.type,l=qe(r,t.pendingProps),l=qe(r.type,l),po(e,t,r,l,n);case 15:return yc(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:qe(r,l),qr(e,t),t.tag=1,De(r)?(e=!0,cl(t)):e=!1,gn(t,n),hc(t,r,l),ts(t,r,l,n),ls(null,t,r,!0,e,n);case 19:return kc(e,t,n);case 22:return xc(e,t,n)}throw Error(k(156,t.tag))};function Bc(e,t){return fu(e,t)}function yp(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function He(e,t,n,r){return new yp(e,t,n,r)}function oa(e){return e=e.prototype,!(!e||!e.isReactComponent)}function xp(e){if(typeof e=="function")return oa(e)?1:0;if(e!=null){if(e=e.$$typeof,e===Cs)return 11;if(e===Ms)return 14}return 2}function Lt(e,t){var n=e.alternate;return n===null?(n=He(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Gr(e,t,n,r,l,i){var s=2;if(r=e,typeof e=="function")oa(e)&&(s=1);else if(typeof e=="string")s=5;else e:switch(e){case tn:return Qt(n.children,l,i,t);case Es:s=8,l|=8;break;case ji:return e=He(12,n,t,l|2),e.elementType=ji,e.lanes=i,e;case Ei:return e=He(13,n,t,l),e.elementType=Ei,e.lanes=i,e;case Ci:return e=He(19,n,t,l),e.elementType=Ci,e.lanes=i,e;case Yo:return $l(n,l,i,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case bo:s=10;break e;case qo:s=9;break e;case Cs:s=11;break e;case Ms:s=14;break e;case yt:s=16,r=null;break e}throw Error(k(130,e==null?e:typeof e,""))}return t=He(s,n,t,l),t.elementType=e,t.type=r,t.lanes=i,t}function Qt(e,t,n,r){return e=He(7,e,r,t),e.lanes=n,e}function $l(e,t,n,r){return e=He(22,e,r,t),e.elementType=Yo,e.lanes=n,e.stateNode={isHidden:!1},e}function xi(e,t,n){return e=He(6,e,null,t),e.lanes=n,e}function wi(e,t,n){return t=He(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function wp(e,t,n,r,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=ei(0),this.expirationTimes=ei(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=ei(0),this.identifierPrefix=r,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function ua(e,t,n,r,l,i,s,o,u){return e=new wp(e,t,n,o,u),t===1?(t=1,i===!0&&(t|=8)):t=0,i=He(3,null,null,t),e.current=i,i.stateNode=e,i.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},Ks(i),e}function Sp(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(Hc)}catch(e){console.error(e)}}Hc(),Ho.exports=Be;var Ep=Ho.exports,Co=Ep;ki.createRoot=Co.createRoot,ki.hydrateRoot=Co.hydrateRoot;const Cp={"Content-Type":"application/json"};async function Ir(e,t={}){const n=await fetch(e,{credentials:"same-origin",headers:{...t.body?Cp:{},...t.headers||{}},...t});if(n.status===204)return null;const r=n.headers.get("content-type")||"",l=await n.text();let i=null;if(l!=="")if(r.includes("application/json"))try{i=JSON.parse(l)}catch{const s=new Error("Server lieferte ungueltiges JSON.");throw s.status=n.status,s.payload={raw:l},s}else i={raw:l};if(!n.ok){const s=(i==null?void 0:i.message)||(l===""?`Request failed with status ${n.status}`:"Server lieferte keine JSON-Antwort."),o=new Error(s);throw o.status=n.status,o.payload=i,o}return i}const ye={get:e=>Ir(e),post:(e,t)=>Ir(e,{method:"POST",body:JSON.stringify(t)}),patch:(e,t)=>Ir(e,{method:"PATCH",body:JSON.stringify(t)}),delete:e=>Ir(e,{method:"DELETE"})};function Mp(e){const t=()=>window.matchMedia(e).matches,[n,r]=F.useState(t);return F.useEffect(()=>{const l=window.matchMedia(e),i=s=>r(s.matches);return l.addEventListener("change",i),r(l.matches),()=>l.removeEventListener("change",i)},[e]),n}const Pp=[{id:"calendar",label:"Kalender",roles:["tenant_admin","dispatcher","instructor"]},{id:"students",label:"Fahrschueler",roles:["tenant_admin","dispatcher"]},{id:"templates",label:"Klassen",roles:["tenant_admin"]},{id:"lesson-types",label:"Stundenarten",roles:["tenant_admin"]},{id:"instructors",label:"Fahrlehrer",roles:["tenant_admin"]},{id:"tenant-profile",label:"Fahrschule",roles:["tenant_admin"]},{id:"users",label:"Benutzer",roles:["tenant_admin","superadmin"]},{id:"tenants",label:"Mandanten",roles:["superadmin"]}],Wc=[{value:"BW",label:"Baden-Wuerttemberg"},{value:"BY",label:"Bayern"},{value:"BE",label:"Berlin"},{value:"BB",label:"Brandenburg"},{value:"HB",label:"Bremen"},{value:"HH",label:"Hamburg"},{value:"HE",label:"Hessen"},{value:"MV",label:"Mecklenburg-Vorpommern"},{value:"NI",label:"Niedersachsen"},{value:"NW",label:"Nordrhein-Westfalen"},{value:"RP",label:"Rheinland-Pfalz"},{value:"SL",label:"Saarland"},{value:"SN",label:"Sachsen"},{value:"ST",label:"Sachsen-Anhalt"},{value:"SH",label:"Schleswig-Holstein"},{value:"TH",label:"Thueringen"}];function vs(e=new Date){const t=new Date(e),r=(t.getDay()+6)%7;return t.setDate(t.getDate()-r),t.setHours(0,0,0,0),t}function Gn(e,t){const n=new Date(e);return n.setDate(n.getDate()+t),n}function xe(e){const t=e.getFullYear(),n=String(e.getMonth()+1).padStart(2,"0"),r=String(e.getDate()).padStart(2,"0");return`${t}-${n}-${r}`}function Tp(e,t){return xe(e)===xe(t)}function Qc(e,t){const n=new Date(e),r=new Date(t);return n.setHours(0,0,0,0),r.setHours(0,0,0,0),n0?l:r,s=Number((e==null?void 0:e.fixed_duration)||0)===1,o=s?1:Math.max(1,Math.round(i/r));return{baseDuration:r,duration:i,units:o,isFixed:s}}function _t(e,t=!1){return new Intl.DateTimeFormat("de-DE",{weekday:t?void 0:"short",day:"2-digit",month:"2-digit",hour:t?"2-digit":void 0,minute:t?"2-digit":void 0}).format(new Date(e))}function zp(e){return new Intl.DateTimeFormat("de-DE",{weekday:"long",day:"2-digit",month:"2-digit",year:"numeric"}).format(new Date(e))}function Po(e){return new Intl.DateTimeFormat("de-DE",{hour:"2-digit",minute:"2-digit"}).format(new Date(e))}function To(e){const t=Math.max(0,Number(e||0)),n=Math.floor(t/60),r=t%60;return`${String(n).padStart(2,"0")}:${String(r).padStart(2,"0")}`}function Dp(e){return e>9*60?45:e>6*60?30:0}function Lp(e,t){const n=new Date(t);n.setHours(0,0,0,0);const r=new Date(t);r.setHours(24,0,0,0);const l=(e||[]).filter(x=>{if(x.status==="cancelled")return!1;const w=new Date(x.start_at),v=new Date(x.end_at);return wn}).map(x=>{const w=new Date(x.start_at),v=new Date(x.end_at);return{...x,visibleStart:w>n?w:n,visibleEnd:vx.visibleEnd>x.visibleStart).sort((x,w)=>x.visibleStart-w.visibleStart),i=l.reduce((x,w)=>w.category!=="student"?x:x+Math.max(0,Number(w.units||0)),0);if(!l.length)return{taughtUnits:i,workSpanMinutes:0,breakMinutes:0,requiredBreakMinutes:0,breakStatus:"ok"};const s=[];for(const x of l){const w={start:x.visibleStart,end:x.visibleEnd},v=s[s.length-1];if(!v||w.start>v.end){s.push(w);continue}w.end>v.end&&(v.end=w.end)}let o=0,u=0;s.forEach((x,w)=>{if(o+=Math.max(0,Math.round((x.end.getTime()-x.start.getTime())/6e4)),w===0)return;const v=s[w-1];u+=Math.max(0,Math.round((x.start.getTime()-v.end.getTime())/6e4))});const d=s[0].start,g=s[s.length-1].end,h=Math.max(0,Math.round((g.getTime()-d.getTime())/6e4)),m=Dp(o);return{taughtUnits:i,workSpanMinutes:h,breakMinutes:u,requiredBreakMinutes:m,breakStatus:u>=m?"ok":"warning"}}function Fp(e){return String(e||"").replace(/[^\d+]/g,"").replace(/^\+/,"")}function Rp(e,t,n,r){if(!n||!r)return"";const l=e!=null&&e.first_name?` ${e.first_name}`:"",i=t?`${t.first_name} ${t.last_name}`.trim():"dein Fahrlehrer";return`Hi${l}, die Fahrstunde ist am ${zp(n)} um ${Po(n)} Uhr und geht voraussichtlich bis ${Po(r)} Uhr. Gruss ${i}`}function $p(e,t){const n=[0,1,2,3,4,5,6],r=new Date(e),l=Gn(e,7);return{start:r,end:l,days:n.map(i=>Gn(e,i))}}function Op(e){const t=[];for(const n of e.conflicts||[])t.push(`- ${n.message}`);for(const n of e.rest_period_warnings||[])t.push(`- ${n.message}`);return t.join(` +`)}function Ip(e){return Object.fromEntries(e.map(t=>[t.id,t.progress||[]]))}function Bp(e){return{...e,requirements:(e.requirements||[]).map((t,n)=>({...t,sort_order:n,required_units:Number(t.required_units||0),phase_label:t.phase_label||""}))}}function Ap(e,t){return e?`${e}::${t}`:t}function Kc(e){if(!Number(e==null?void 0:e.is_combination))return[""];const t=[...new Set((e.requirements||[]).map(n=>n.phase_label||"").filter(Boolean))];return t.length>0?t:["C","CE"]}function Zr(e,t){const n=new Set(e.lesson_type_ids&&e.lesson_type_ids.length>0?e.lesson_type_ids.map(s=>Number(s)):t.filter(s=>s.category==="student").map(s=>Number(s.id))),r=new Map((e.requirements||[]).map(s=>[s.requirement_key,s])),l=Kc(e),i=[];return t.filter(s=>s.category==="student"&&Number(s.is_counted)===1&&n.has(Number(s.id))).forEach((s,o)=>{const u=s.effective_requirement_key||s.requirement_key||`lesson_type_${s.id}`;l.forEach((d,g)=>{const h=Ap(d,u),m=r.get(h)||(g===0?r.get(u):null);i.push({requirement_key:h,phase_label:d,base_requirement_key:u,label:s.name,required_units:Number((m==null?void 0:m.required_units)||0),sort_order:o+g*1e3,lesson_type_id:Number(s.id),color:s.color})})}),i}function pa(e){const t=[];for(const n of e||[]){const r=n.phase_label||"";let l=t.find(i=>i.phase_label===r);l||(l={phase_label:r,rows:[]},t.push(l)),l.rows.push(n)}return t}function bc(e){const t=e.reduce((r,l)=>r+Number(l.required_units||0),0),n=e.reduce((r,l)=>r+Number(l.open_units||0),0);return{required:t,open:n,status:n>0?"missing":n<0?"over":"complete"}}function Jr(e){return Number(e)>0?{className:"is-missing",text:`${e} offen`}:Number(e)<0?{className:"is-over",text:`${Math.abs(e)} ueberplant`}:{className:"is-complete",text:"0 offen"}}function qc(e,t){return e.category!=="student"||!t?!0:(e.visible_template_ids||[]).includes(t)}function Up(e,t,n){if(!t)return e;const r=n.find(l=>Number(l.id)===Number(t));return r?e.filter(l=>l.category==="student"&&qc(l,Number(r.template_id))):e}function Yc(e){return{student:"Fahrschueler",private:"Privat",work:"Sonstige Arbeiten",theory:"Theorie",other:"Sonstiges",blocked:"Sperre"}[e]||e}function Vp(){var _,b;const e=Mp("(max-width: 960px)"),[t,n]=F.useState({user:null}),[r,l]=F.useState(null),[i,s]=F.useState(!0),[o,u]=F.useState("calendar"),[d,g]=F.useState(vs()),[h,m]=F.useState(null),[x,w]=F.useState("student"),[v,D]=F.useState(null),[f,c]=F.useState(null),[p,y]=F.useState(""),[j,M]=F.useState(()=>window.localStorage.getItem("calendar-theme")||"light"),S=F.useMemo(()=>$p(d),[d]);async function T(){try{const E=await ye.get("/api/v1/auth/me");return n(E),E.user&&await $(E.user,S.start,S.end),E}catch(E){return y(E.message),{user:null}}finally{s(!1)}}async function $(E=t.user,B=S.start,me=S.end){var he;if(!E)return;const ke=new URLSearchParams({from:B.toISOString(),to:me.toISOString()}),Me=await ye.get(`/api/v1/bootstrap?${ke.toString()}`);l(Me),!h&&((he=Me.students)!=null&&he.length)&&m(Me.students[0].id)}F.useEffect(()=>{T()},[]),F.useEffect(()=>{t.user&&t.user.role!=="superadmin"&&$(t.user,S.start,S.end).catch(E=>y(E.message))},[d]),F.useEffect(()=>{document.documentElement.setAttribute("data-theme",j),window.localStorage.setItem("calendar-theme",j)},[j]);const L=(r==null?void 0:r.students)||[],W=(r==null?void 0:r.lessonTypes)||[],C=(r==null?void 0:r.instructors)||[],R=(r==null?void 0:r.templates)||[],A=(r==null?void 0:r.users)||[],N=(r==null?void 0:r.tenants)||[],V=(r==null?void 0:r.appointments)||[],J=(r==null?void 0:r.nextAppointment)||null,P=(r==null?void 0:r.sunTimes)||{},O=(r==null?void 0:r.dayMeta)||{},I=L.find(E=>E.id===h)||null,q=Ip(L),Q=Pp.filter(E=>{var B;return E.roles.includes((B=t.user)==null?void 0:B.role)});F.useEffect(()=>{Q.length&&!Q.some(E=>E.id===o)&&u(Q[0].id)},[o,Q]);async function it(E){y("");try{await ye.post("/api/v1/auth/login",E);const B=await T();B!=null&&B.user||y("Login war erfolgreich, aber die Session konnte nicht geladen werden. Bitte Server-Session und Cookies pruefen.")}catch(B){y(B.message)}}async function Y(){await ye.post("/api/v1/auth/logout",{}),window.location.reload()}async function Fe(E,B,me=null){me?await ye.patch(`${E}/${me}`,B):await ye.post(E,B),await $(t.user,S.start,S.end)}async function _e(E,B){await ye.delete(`${E}/${B}`),await $(t.user,S.start,S.end)}async function vt(E,B){await ye.patch(`/api/v1/students/${E}/training`,{rows:B}),await $(t.user,S.start,S.end)}async function Wl(E){E.preventDefault();const B=W.find(Me=>Number(Me.id)===Number(v==null?void 0:v.lesson_type_id)),me=ys(B,v==null?void 0:v.start_at,v==null?void 0:v.end_at);if(!(v!=null&&v.start_at)||!(v!=null&&v.end_at)||Ul(v.start_at,v.end_at)<=0){y("Bitte eine gueltige Start- und Endzeit waehlen.");return}const ke={student_id:v.student_id?Number(v.student_id):null,lesson_type_id:Number(v.lesson_type_id),instructor_id:Number(v.instructor_id),start_at:new Date(v.start_at).toISOString(),end_at:new Date(v.end_at).toISOString(),units:me.units,notes:v.notes,status:v.status,requirement_phase:v.requirement_phase||"",warning_acknowledged:!1};try{f?await ye.patch(`/api/v1/appointments/${f}`,ke):await ye.post("/api/v1/appointments",ke),D(null),c(null),await $(t.user,S.start,S.end)}catch(Me){Me.status===409?window.confirm(`Warnungen beim Einplanen: +${Op(Me.payload.warnings)} + +Trotzdem speichern?`)&&(ke.warning_acknowledged=!0,f?await ye.patch(`/api/v1/appointments/${f}`,ke):await ye.post("/api/v1/appointments",ke),D(null),c(null),await $(t.user,S.start,S.end)):y(Me.message)}}async function Ql(){f&&window.confirm("Termin wirklich loeschen?")&&(await ye.delete(`/api/v1/appointments/${f}`),D(null),c(null),await $(t.user,S.start,S.end))}function wr({lessonTypeId:E,day:B,hour:me,minute:ke=0,instructorId:Me,appointment:he=null,startAt:Kl=null,endAt:ma=null}){var va,ga;if(he){c(he.id),D({student_id:he.student_id||"",lesson_type_id:he.lesson_type_id,instructor_id:he.instructor_id,start_at:Un(new Date(he.start_at)),end_at:Un(new Date(he.end_at)),units:he.units,notes:he.notes||"",status:he.status,requirement_phase:(he.counted_requirement_key||"").includes("::")?he.counted_requirement_key.split("::")[0]:""});return}const bl=Kl?new Date(Kl):new Date(B);Kl||bl.setHours(me,ke,0,0);const ha=E||((va=W[0])==null?void 0:va.id)||"",ql=W.find(Gc=>Number(Gc.id)===Number(ha)),Xc=ma?new Date(ma):new Date(bl.getTime()+Math.max(1,Number((ql==null?void 0:ql.default_duration)||45))*60*1e3);c(null),D({student_id:x==="student"&&h||"",lesson_type_id:ha,instructor_id:Me||((ga=C[0])==null?void 0:ga.id)||"",start_at:Un(bl),end_at:Un(Xc),units:1,notes:"",status:"planned",requirement_phase:""})}return i?a.jsx("div",{className:"app-shell",children:a.jsx("div",{className:"loading-panel",children:"Lade Anwendung ..."})}):t.user?a.jsxs("div",{className:"app-shell",children:[a.jsxs("header",{className:"topbar",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Fahrschul-Kalender"}),a.jsx("h1",{children:"DriveTime Planner"}),a.jsx("p",{className:"meta-line",children:t.user.role==="superadmin"?"Systemweit":`${(_=r==null?void 0:r.tenant)==null?void 0:_.name} · ${(b=r==null?void 0:r.tenant)==null?void 0:b.timezone}`})]}),a.jsxs("div",{className:"topbar-actions",children:[a.jsxs("span",{className:"badge",children:[t.user.first_name," ",t.user.last_name]}),a.jsx("button",{className:"ghost-button",onClick:()=>M(E=>E==="dark"?"light":"dark"),children:j==="dark"?"Hell":"Dunkel"}),a.jsx("button",{className:"ghost-button",onClick:Y,children:"Logout"})]})]}),p&&a.jsx("div",{className:"flash-error",children:p}),a.jsx("nav",{className:"tab-strip",children:Q.map(E=>a.jsx("button",{className:E.id===o?"tab-button active":"tab-button",onClick:()=>u(E.id),children:E.label},E.id))}),a.jsxs("main",{className:"content-grid",children:[o==="calendar"&&t.user.role!=="superadmin"&&a.jsx(Wp,{isMobile:e,weekStart:d,setWeekStart:g,range:S,instructors:C,students:L,lessonTypes:W,appointments:V,nextAppointment:J,sunTimes:P,dayMeta:O,selectedStudent:I,selectedStudentId:h,setSelectedStudentId:m,plannerMode:x,setPlannerMode:w,progress:I?q[I.id]||[]:[],onCreate:wr}),o==="students"&&t.user.role!=="superadmin"&&a.jsx(bp,{students:L,templates:R,onSave:(E,B)=>Fe("/api/v1/students",E,B),onSaveTraining:vt,onDelete:E=>_e("/api/v1/students",E)}),o==="lesson-types"&&t.user.role!=="superadmin"&&a.jsx(qp,{lessonTypes:W,templates:R,onSave:(E,B)=>Fe("/api/v1/lesson-types",E,B),onDelete:E=>_e("/api/v1/lesson-types",E),onReorder:async E=>{await ye.post("/api/v1/lesson-types/reorder",{lesson_type_ids:E}),await $(t.user,S.start,S.end)},onSaveMatrix:async E=>{await ye.post("/api/v1/lesson-types/visibility",{rows:E}),await $(t.user,S.start,S.end)}}),o==="templates"&&t.user.role!=="superadmin"&&a.jsx(Xp,{templates:R,lessonTypes:W,onSave:(E,B)=>Fe("/api/v1/license-class-templates",E,B)}),o==="instructors"&&t.user.role!=="superadmin"&&a.jsx(Yp,{instructors:C,onSave:(E,B)=>Fe("/api/v1/instructors",E,B)}),o==="users"&&a.jsx(Gp,{users:A,tenants:N,currentUser:t.user,onSave:(E,B)=>Fe("/api/v1/users",E,B)}),o==="tenant-profile"&&t.user.role==="tenant_admin"&&(r==null?void 0:r.tenant)&&a.jsx(Jp,{tenant:r.tenant,onSave:async E=>{await ye.patch("/api/v1/tenant/profile",E),await $(t.user,S.start,S.end)}}),o==="tenants"&&t.user.role==="superadmin"&&a.jsx(Zp,{tenants:N,onSave:(E,B)=>Fe("/api/v1/tenants",E,B)})]}),v&&a.jsx(tm,{draft:v,setDraft:D,lessonTypes:W,instructors:C,students:L,templates:R,onPickStudent:E=>{E&&(m(Number(E)),w("student"),D(null),c(null))},onSubmit:Wl,onDelete:Ql,onClose:()=>{D(null),c(null)},isEditing:!!f})]}):a.jsx(Hp,{onSubmit:it,error:p})}function Hp({onSubmit:e,error:t}){async function n(r){r.preventDefault();const l=new FormData(r.currentTarget);await e({email:l.get("email"),password:l.get("password")})}return a.jsx("div",{className:"login-shell",children:a.jsxs("div",{className:"login-card",children:[a.jsx("p",{className:"eyebrow",children:"Mandantenfaehig · Kalenderfokus"}),a.jsx("h1",{children:"Dispositionsfenster"}),a.jsx("p",{className:"login-copy",children:"Planung fuer Fahrstunden, Vorlagen und Ruhezeitwarnungen in einer kompakten Leitstelle."}),a.jsxs("form",{onSubmit:n,className:"stack-form",children:[a.jsxs("label",{children:["E-Mail",a.jsx("input",{name:"email",type:"email",defaultValue:"admin@nordring.test",required:!0})]}),a.jsxs("label",{children:["Passwort",a.jsx("input",{name:"password",type:"password",defaultValue:"secret123",required:!0})]}),t&&a.jsx("div",{className:"inline-error",children:t}),a.jsx("button",{className:"primary-button",type:"submit",children:"Einloggen"})]}),a.jsx("p",{className:"hint-line",children:"Demo: `admin@nordring.test` / `secret123` oder `superadmin@example.com` / `secret123`"})]})})}function Wp(e){var wr;const{isMobile:t,weekStart:n,setWeekStart:r,range:l,instructors:i,students:s,lessonTypes:o,appointments:u,nextAppointment:d,sunTimes:g,dayMeta:h,selectedStudent:m,selectedStudentId:x,setSelectedStudentId:w,plannerMode:v,setPlannerMode:D,progress:f,onCreate:c}=e,p=t?[new Date]:l.days,[y,j]=F.useState(()=>new Date),[M,S]=F.useState(""),[T,$]=F.useState(!1),[L,W]=F.useState(null),[C,R]=F.useState(((wr=i[0])==null?void 0:wr.id)||null),[A,N]=F.useState(()=>xe(new Date)),[V,J]=F.useState("08:00"),[P,O]=F.useState("10:00"),I=[{id:"student",label:"Fahrschueler"},{id:"private",label:"Privat"},{id:"work",label:"Sonstige Arbeiten"},{id:"theory",label:"Theorie"}],q=Array.from({length:96},(_,b)=>{const E=Math.floor(b/4),B=b%4*15;return{hour:E,minute:B,label:`${String(E).padStart(2,"0")}:${String(B).padStart(2,"0")}`,rowClass:B===0?"full-hour":""}}),Q=o.filter(_=>v==="student"?_.category==="student"&&qc(_,m==null?void 0:m.template_id):v==="private"?_.category==="private":v==="work"?_.category==="work"||_.category==="other"||_.category==="blocked":v==="theory"?_.category==="theory":!1),it=s.filter(_=>`${_.first_name} ${_.last_name} ${_.template_code}`.toLowerCase().includes(M.toLowerCase())).slice(0,8);F.useEffect(()=>{const _=window.setInterval(()=>{j(new Date)},6e4);return()=>window.clearInterval(_)},[]),F.useEffect(()=>{m&&S(`${m.first_name} ${m.last_name}`)},[x,m]),F.useEffect(()=>{if(!i.length){R(null);return}i.some(_=>_.id===C)||R(i[0].id)},[i,C]),F.useEffect(()=>{if(Q.length===0){W(null);return}Q.some(_=>_.id===L)||W(Q[0].id)},[L,Q]);const Y=i.find(_=>_.id===C)||i[0]||null;Q.find(_=>_.id===L);const Fe=A&&Ul(jt(A,V),jt(A,P))>0,_e=Y?u.filter(_=>_.instructor_id===Y.id):[],vt=Math.max(0,Number((Y==null?void 0:Y.pre_start_buffer_minutes)||0)),Wl=Array.from(_e.filter(_=>Number(_.is_rest_relevant)===1).reduce((_,b)=>{const E=new Date(b.end_at),B=`${E.getFullYear()}-${String(E.getMonth()+1).padStart(2,"0")}-${String(E.getDate()).padStart(2,"0")}`,me=_.get(B);return(!me||E>me.endAt)&&_.set(B,{endAt:E}),_},new Map).values()).map(({endAt:_})=>{const b=_,E=new Date(_.getTime()+11*60*60*1e3),B=_e.some(me=>{const ke=new Date(me.start_at);return ke>b&&ke_.start-b.start);function Ql(){!L||!(Y!=null&&Y.id)||!Fe||c({lessonTypeId:L,instructorId:Y.id,startAt:jt(A,V),endAt:jt(A,P)})}return a.jsxs(a.Fragment,{children:[a.jsxs("section",{className:"calendar-stage",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Einsatzfenster"}),a.jsxs("h2",{children:[_t(l.start)," - ",_t(Gn(l.end,-1))]})]}),a.jsxs("div",{className:"toolbar",children:[a.jsxs("label",{className:"calendar-filter",children:["Fahrlehrer",a.jsx("select",{value:C||"",onChange:_=>R(Number(_.target.value)),children:i.map(_=>a.jsxs("option",{value:_.id,children:[_.first_name," ",_.last_name]},_.id))})]}),a.jsx("button",{className:"ghost-button",onClick:()=>r(Gn(n,-7)),children:"Zurueck"}),a.jsx("button",{className:"ghost-button",onClick:()=>r(vs()),children:"Heute"}),d&&a.jsx("button",{className:"ghost-button",onClick:()=>r(vs(new Date(d.start_at))),children:"Naechster Termin"}),a.jsx("button",{className:"ghost-button",onClick:()=>r(Gn(n,7)),children:"Weiter"})]})]}),a.jsxs("div",{className:"calendar-grid",children:[a.jsx("div",{className:"calendar-head time-col",children:"Zeit"}),p.map(_=>(()=>{var E,B,me,ke,Me;const b=Lp(_e,_);return a.jsxs("div",{className:["calendar-head day-col",Qc(_,y)?"past-head":"",(E=h[xe(_)])!=null&&E.holidayName?"holiday-head":"",(B=h[xe(_)])!=null&&B.isSunday?"sunday-head":""].filter(Boolean).join(" "),children:[a.jsx("strong",{children:_t(_)}),a.jsxs("div",{className:"day-summary",children:[a.jsxs("span",{className:"day-summary-line",children:[a.jsxs("strong",{children:[b.taughtUnits," UE"]}),a.jsx("span",{children:"geschult"})]}),a.jsxs("span",{className:"day-summary-line",children:[a.jsx("strong",{children:To(b.workSpanMinutes)}),a.jsx("span",{children:"Arbeitszeit"})]}),a.jsxs("span",{className:`day-summary-line break-line ${b.breakStatus}`,children:[a.jsx("strong",{children:To(b.breakMinutes)}),a.jsxs("span",{children:["(Pause ",b.requiredBreakMinutes," min)"]})]})]}),((me=h[xe(_)])==null?void 0:me.holidayName)&&a.jsx("span",{className:"holiday-meta",children:h[xe(_)].holidayName}),((ke=g[xe(_)])==null?void 0:ke.sunrise)&&((Me=g[xe(_)])==null?void 0:Me.sunset)&&a.jsxs("span",{className:"sun-meta",children:["Auf ",g[xe(_)].sunrise," · Unter ",g[xe(_)].sunset]})]},_.toISOString())})()),a.jsx("div",{className:"time-grid",children:q.map(_=>a.jsx("div",{className:`slot-label ${_.rowClass}`,children:_.label},_.label))}),p.map(_=>a.jsx(Qp,{day:_,slots:q,instructor:Y,appointments:_e,preStartBufferMinutes:vt,restIntervals:Wl,sunInfo:g[xe(_)]||null,dayMeta:h[xe(_)]||null,now:y,armedLessonTypeId:L,onCreate:c},_.toISOString()))]})]}),a.jsxs("aside",{className:"planner-panel",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Planung"}),a.jsx("h2",{children:"Bausteine"})]}),t&&a.jsx("button",{className:"primary-button compact",onClick:()=>c({day:new Date,hour:8,minute:0,instructorId:Y==null?void 0:Y.id}),children:"+"})]}),a.jsx("div",{className:"mode-switch",children:I.map(_=>a.jsx("button",{className:v===_.id?"tab-button active":"tab-button",onClick:()=>D(_.id),children:_.label},_.id))}),v==="student"&&a.jsxs("label",{className:"stack-form",children:["Fahrschueler",a.jsxs("div",{className:"autocomplete-shell",children:[a.jsx("input",{type:"text",value:M,placeholder:"Name eingeben ...",onFocus:()=>$(!0),onChange:_=>{S(_.target.value),$(!0)},onBlur:()=>{window.setTimeout(()=>$(!1),120)}}),T&&it.length>0&&a.jsx("div",{className:"autocomplete-list",children:it.map(_=>a.jsxs("button",{type:"button",className:"autocomplete-option",onMouseDown:b=>{b.preventDefault(),w(_.id),S(`${_.first_name} ${_.last_name}`),$(!1)},children:[a.jsxs("strong",{children:[_.first_name," ",_.last_name]}),a.jsx("span",{children:_.template_code})]},_.id))})]})]}),a.jsx("div",{className:"block-bank",children:Q.map(_=>a.jsxs("div",{className:L===_.id?"lesson-chip active":"lesson-chip",draggable:!t,onDragStart:b=>{const E=JSON.stringify({lessonTypeId:_.id});b.dataTransfer.effectAllowed="move",b.dataTransfer.setData("application/json",E),b.dataTransfer.setData("text/plain",E)},onClick:()=>{W(_.id),t&&c({day:new Date,hour:8,minute:0,lessonTypeId:_.id,instructorId:Y==null?void 0:Y.id})},style:{"--chip-color":_.color},children:[a.jsx("strong",{children:_.name}),a.jsx("span",{children:_.fixed_duration?`${_.default_duration} min fix`:`${_.default_duration} min pro Einheit`})]},_.id))}),v==="private"&&L&&a.jsxs("div",{className:"quick-plan-panel",children:[a.jsx("div",{className:"section-head compact-head",children:a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Privat"}),a.jsx("h2",{children:"Schnell eintragen"})]})}),a.jsxs("label",{className:"stack-form",children:["Datum",a.jsx("input",{type:"date",value:A,onChange:_=>N(_.target.value)})]}),a.jsxs("div",{className:"time-editor-grid two-col",children:[a.jsx(Nl,{label:"Start",value:V,onChange:J}),a.jsx(Nl,{label:"Ende",value:P,onChange:O})]}),a.jsx("button",{type:"button",className:"primary-button",onClick:Ql,disabled:!Fe,children:"Privatblock anlegen"})]}),m&&a.jsxs("div",{className:"progress-panel",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Reststunden"}),a.jsxs("h2",{children:[m.first_name," ",m.last_name]})]}),a.jsx("span",{className:"badge",children:m.template_code})]}),pa(f).map(_=>{const b=bc(_.rows);return a.jsxs("div",{className:"progress-phase",children:[_.phase_label&&a.jsxs("div",{className:"phase-head",children:[a.jsx("strong",{children:_.phase_label}),a.jsx("span",{className:`phase-state ${b.status}`,children:Jr(b.open).text})]}),_.rows.map(E=>a.jsxs("div",{className:"progress-row",children:[a.jsxs("div",{children:[a.jsx("strong",{children:E.label}),a.jsxs("p",{children:["Soll ",E.required_units," · Geplant ",E.planned_units," · Erledigt ",E.completed_units]})]}),a.jsx("div",{className:`open-pill ${Jr(E.open_units).className}`,children:Jr(E.open_units).text})]},E.requirement_key))]},_.phase_label||"default")})]})]})]})}function Qp({day:e,slots:t,instructor:n,appointments:r,preStartBufferMinutes:l,restIntervals:i,sunInfo:s,dayMeta:o,now:u,armedLessonTypeId:d,onCreate:g}){const h=new Date(e);h.setHours(0,0,0,0);const m=new Date(e);m.setHours(24,0,0,0);const x=Tp(e,u),w=Qc(e,u),v=u.getHours()*60+u.getMinutes(),D=Math.min(96,Math.max(0,v/15)),f=Math.min(96,Math.max(0,Math.ceil(D))),c=r.filter(C=>{const R=new Date(C.start_at),A=new Date(C.end_at);return Rh}),p=r.filter(C=>{const R=new Date(C.start_at);return R>=h&&Rnew Date(C.start_at)-new Date(R.start_at))[0]||null,y=(()=>{if(!p||l<=0)return null;const C=new Date(p.start_at),R=new Date(Math.max(h.getTime(),C.getTime()-l*60*1e3)),A=C;if(A<=R)return null;const N=R.getHours()*60+R.getMinutes(),V=A.getHours()*60+A.getMinutes(),J=Math.max(0,Math.floor(N/15)),P=Math.max(1,Math.ceil((V-N)/15));return{startIndex:J,spanBlocks:Math.min(P,96-J),label:`Anfahrt ${l} min`}})(),j=i.map(C=>{const R=C.start>h?C.start:h,A=C.end=m?24*60:A.getHours()*60+A.getMinutes(),J=Math.max(0,Math.floor(N/15)),P=Math.max(1,Math.ceil((V-N)/15));return{key:`${C.start.toISOString()}-${C.end.toISOString()}-${e.toISOString()}`,startIndex:J,spanBlocks:Math.min(P,96-J),label:`Ruhezeit bis ${String(C.end.getHours()).padStart(2,"0")}:${String(C.end.getMinutes()).padStart(2,"0")}`,hasConflict:C.hasConflict}}).filter(Boolean),M=Mo(s==null?void 0:s.sunrise),S=Mo(s==null?void 0:s.sunset),T=[];if(M!==null&&M>0&&T.push({key:`night-before-${xe(e)}`,startIndex:0,spanBlocks:Math.max(1,Math.ceil(M/15)),label:`Nacht bis ${s.sunrise}`,align:"end"}),S!==null&&S<24*60){const C=Math.max(0,Math.floor(S/15));T.push({key:`night-after-${xe(e)}`,startIndex:C,spanBlocks:Math.max(1,Math.ceil((24*60-S)/15)),label:`Dunkel ab ${s.sunset}`,align:"start"})}function $(C){g({day:e,hour:C.hour,minute:C.minute,instructorId:n==null?void 0:n.id,lessonTypeId:d||void 0})}function L(C,R){C.preventDefault();const A=C.dataTransfer.getData("application/json")||C.dataTransfer.getData("text/plain");if(!A)return;const N=JSON.parse(A);g({lessonTypeId:N.lessonTypeId,day:e,hour:R.hour,minute:R.minute,instructorId:n==null?void 0:n.id})}function W(C,R){const A=C.currentTarget.getBoundingClientRect(),N=Math.max(0,Math.min(A.height-1,C.clientY-A.top)),V=Math.floor(N/28),J=Math.min(95,R.startIndex+V);return t[J]}return a.jsxs("div",{className:["day-grid",w?"past-column":"",o!=null&&o.holidayName?"holiday-column":"",o!=null&&o.isSunday?"sunday-column":""].filter(Boolean).join(" "),children:[w&&a.jsx("div",{className:"day-past-overlay",style:{gridRow:"1 / span 96"}}),x&&f>0&&a.jsx("div",{className:"current-time-past",style:{gridRow:`1 / span ${f}`},"aria-hidden":"true"}),x&&D>0&&D<96&&a.jsx("div",{className:"current-time-line",style:{top:`${D*28}px`},"aria-hidden":"true",children:a.jsx("span",{children:"Jetzt"})}),t.map((C,R)=>a.jsx("button",{type:"button",className:`slot-cell quarter-hour ${C.rowClass}`,style:{gridRow:R+1},onDragOver:A=>{A.preventDefault(),A.dataTransfer.dropEffect="move"},onDrop:A=>L(A,C),onClick:()=>$(C)},C.label)),T.map(C=>a.jsx("div",{className:`sun-band ${C.align==="end"?"top-band":"bottom-band"}`,style:{gridRow:`${C.startIndex+1} / span ${C.spanBlocks}`},children:a.jsx("span",{children:C.label})},C.key)),y&&a.jsx("div",{className:"pre-start-stripe",style:{gridRow:`${y.startIndex+1} / span ${y.spanBlocks}`},"aria-hidden":"true",children:a.jsx("span",{children:y.label})}),c.map(C=>{const R=new Date(C.start_at),A=new Date(C.end_at),N=R>h?R:h,V=A=m?24*60:V.getHours()*60+V.getMinutes(),O=Math.max(0,Math.floor(J/15)),I=Math.max(1,Math.min(96-O,Math.ceil((P-J)/15)));return a.jsx(Kp,{appointment:C,onOpen:()=>g({appointment:C}),compact:!0,spanBlocks:I,gridRowStart:O+1},`${C.id}-${xe(e)}`)}),j.map(C=>a.jsx("div",{className:C.hasConflict?"rest-stripe warning":"rest-stripe ok",style:{gridRow:`${C.startIndex+1} / span ${C.spanBlocks}`},onDragOver:R=>{R.preventDefault(),R.dataTransfer.dropEffect="move"},onDrop:R=>{const A=W(R,C);L(R,A)},onClick:R=>{const A=W(R,C);$(A)},children:a.jsx("span",{children:C.label})},C.key))]})}function Kp({appointment:e,onOpen:t,compact:n=!1,spanBlocks:r=1,gridRowStart:l=null}){const s=`${e.student_first_name||""} ${e.student_last_name||""}`.trim()||Yc(e.category),o=r>=3,u=Number(e.student_needs_glasses||0)===1;return a.jsx("button",{className:n?"appointment-card compact-card day-appointment":"appointment-card",onClick:d=>{d.stopPropagation(),t()},style:{"--appointment-color":e.lesson_type_color,"--span-blocks":r,...l?{gridRow:`${l} / span ${r}`}:{}},children:n?a.jsxs(a.Fragment,{children:[a.jsxs("div",{className:"appointment-top",children:[a.jsxs("strong",{className:"appointment-title-row",children:[a.jsx("span",{children:e.lesson_type_name}),u&&a.jsx("span",{className:"vision-badge",title:"Brille erforderlich",children:"[Br]"})]}),a.jsxs("span",{className:"appointment-student",children:[s,u&&a.jsx("span",{className:"vision-inline","aria-hidden":"true",children:" [Brille]"})]}),o&&a.jsxs("span",{className:"appointment-meta",children:[_t(e.start_at)," · ",_t(e.start_at,!0)]})]}),a.jsx("div",{className:"appointment-bottom",children:a.jsxs("span",{children:["bis ",_t(e.end_at,!0)]})})]}):a.jsxs(a.Fragment,{children:[a.jsxs("strong",{className:"appointment-title-row",children:[a.jsx("span",{children:e.lesson_type_name}),u&&a.jsx("span",{className:"vision-badge",title:"Brille erforderlich",children:"[Br]"})]}),a.jsxs("span",{children:[_t(e.start_at,!0)," - ",_t(e.end_at,!0)]}),a.jsxs("span",{children:[s,u&&a.jsx("span",{className:"vision-inline",children:" [Brille]"})]})]})})}function bp({students:e,templates:t,onSave:n,onSaveTraining:r,onDelete:l}){var g;const[i,s]=F.useState(null),[o,u]=F.useState("master"),d=e.find(h=>Number(h.id)===Number(i))||null;return i==="new"?a.jsx(zo,{student:{first_name:"",last_name:"",phone:"",email:"",needs_glasses:0,notes:"",template_id:(g=t[0])==null?void 0:g.id,status:"active",progress:[]},templates:t,activeTab:"master",setActiveTab:u,onBack:()=>s(null),onSave:async h=>{await n(h,null),s(null)},onSaveTraining:null,onDelete:null,isNew:!0}):d?a.jsx(zo,{student:d,templates:t,activeTab:o,setActiveTab:u,onBack:()=>{s(null),u("master")},onSave:async h=>{await n(h,d.id)},onSaveTraining:async h=>{await r(d.id,h)},onDelete:async()=>{window.confirm(`Fahrschueler ${d.first_name} ${d.last_name} wirklich loeschen? Termine bleiben erhalten, verlieren aber den Fahrschueler-Bezug.`)&&(await l(d.id),s(null),u("master"))}}):a.jsxs("section",{className:"list-panel student-page",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Verwaltung"}),a.jsx("h2",{children:"Fahrschueler"}),a.jsx("p",{className:"meta-line",children:"Klick oeffnet die eigene Fahrschueler-Seite mit Stammdaten und Ausbildung."})]}),a.jsx("button",{className:"primary-button",onClick:()=>{u("master"),s("new")},children:"Neu"})]}),a.jsx("div",{className:"entity-list",children:e.map(h=>a.jsxs("button",{className:"entity-item student-list-row",onClick:()=>s(h.id),children:[a.jsxs("strong",{children:[h.first_name," ",h.last_name]}),a.jsxs("span",{children:[h.template_code," · ",h.status]})]},h.id))})]})}function zo({student:e,templates:t,activeTab:n,setActiveTab:r,onBack:l,onSave:i,onSaveTraining:s,onDelete:o,isNew:u=!1}){const[d,g]=F.useState(e),[h,m]=F.useState(e.progress||[]),[x,w]=F.useState(!1);F.useEffect(()=>{g(e),m(e.progress||[])},[e]);async function v(f){f.preventDefault(),await i(d)}async function D(f){if(f.preventDefault(),!!s){w(!0);try{await s(h.map(c=>({requirement_key:c.requirement_key,phase_label:c.phase_label||"",prior_completed_units:Number(c.prior_completed_units||0)})))}finally{w(!1)}}}return a.jsxs("section",{className:"list-panel student-page student-detail-page",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Fahrschueler"}),a.jsx("h2",{children:u?"Neuer Fahrschueler":`${e.first_name} ${e.last_name}`}),a.jsx("p",{className:"meta-line",children:u?"Stammdaten anlegen.":`${e.template_name||e.template_code} · ${e.status}`})]}),a.jsx("button",{type:"button",className:"ghost-button",onClick:l,children:"Zurueck"})]}),!u&&a.jsxs("div",{className:"mode-switch detail-tabs",children:[a.jsx("button",{className:n==="master"?"tab-button active":"tab-button",onClick:()=>r("master"),children:"Stammdaten"}),a.jsx("button",{className:n==="training"?"tab-button active":"tab-button",onClick:()=>r("training"),children:"Ausbildung"})]}),(n==="master"||u)&&a.jsxs("form",{className:"stack-form",onSubmit:v,children:[a.jsxs("label",{children:["Vorname",a.jsx("input",{value:d.first_name||"",onChange:f=>g({...d,first_name:f.target.value}),required:!0})]}),a.jsxs("label",{children:["Nachname",a.jsx("input",{value:d.last_name||"",onChange:f=>g({...d,last_name:f.target.value}),required:!0})]}),a.jsxs("label",{children:["Telefon",a.jsx("input",{value:d.phone||"",onChange:f=>g({...d,phone:f.target.value})})]}),a.jsxs("label",{children:["E-Mail",a.jsx("input",{type:"email",value:d.email||"",onChange:f=>g({...d,email:f.target.value})})]}),a.jsxs("label",{className:"checkbox-row",children:[a.jsx("input",{type:"checkbox",checked:!!d.needs_glasses,onChange:f=>g({...d,needs_glasses:f.target.checked?1:0})}),a.jsx("span",{children:"Benoetigt Brille beim Fahren"})]}),a.jsxs("label",{children:["Fuehrerscheinklasse",a.jsx("select",{value:d.template_id||"",onChange:f=>g({...d,template_id:f.target.value}),children:t.map(f=>a.jsx("option",{value:f.id,children:f.name},f.id))})]}),a.jsxs("label",{children:["Status",a.jsxs("select",{value:d.status||"active",onChange:f=>g({...d,status:f.target.value}),children:[a.jsx("option",{value:"active",children:"aktiv"}),a.jsx("option",{value:"inactive",children:"inaktiv"})]})]}),a.jsxs("label",{children:["Notiz",a.jsx("textarea",{rows:"5",value:d.notes||"",onChange:f=>g({...d,notes:f.target.value})})]}),a.jsxs("div",{className:"modal-actions",children:[!u&&o&&a.jsx("button",{type:"button",className:"danger-button",onClick:o,children:"Fahrschueler loeschen"}),a.jsx("button",{className:"primary-button",type:"submit",children:"Speichern"})]})]}),!u&&n==="training"&&a.jsxs("form",{className:"training-panel",onSubmit:D,children:[(e.progress||[]).length===0&&a.jsx("p",{className:"meta-line",children:"Noch keine Ausbildungsvorgaben hinterlegt."}),pa(h).map(f=>{const c=bc(f.rows);return a.jsxs("div",{className:"training-phase",children:[f.phase_label&&a.jsxs("div",{className:"phase-head",children:[a.jsx("strong",{children:f.phase_label}),a.jsx("span",{className:`phase-state ${c.status}`,children:Jr(c.open).text})]}),f.rows.map(p=>{const y=h.findIndex(C=>C.requirement_key===p.requirement_key),j=Number(p.required_units||0),M=Number(p.completed_units||0),S=Number(p.prior_completed_units||0),T=Number(p.appointment_completed_units||0),$=Number(p.planned_units||0),L=M+$,W=j>0?Math.min(100,Math.round(L/j*100)):100;return a.jsxs("div",{className:"training-row",children:[a.jsxs("div",{className:"training-row-head",children:[a.jsxs("div",{children:[a.jsx("strong",{children:p.label}),a.jsxs("p",{children:["Altbestand ",S," · Termine erledigt ",T," · Geplant ",$," · Soll ",j," · Offen ",p.open_units]})]}),a.jsxs("label",{className:"training-import-field",children:["Schon erledigt",a.jsx("input",{type:"number",min:"0",step:"1",value:p.prior_completed_units??0,onChange:C=>{const R=[...h];R[y]={...p,prior_completed_units:C.target.value},m(R)}})]})]}),a.jsx("div",{className:"training-meter","aria-label":`${W} Prozent`,children:a.jsx("span",{style:{width:`${W}%`}})})]},p.requirement_key)})]},f.phase_label||"default")}),(e.progress||[]).length>0&&a.jsx("div",{className:"modal-actions",children:a.jsx("button",{className:"primary-button",type:"submit",disabled:x,children:x?"Speichert ...":"Ausbildung speichern"})})]})]})}function qp({lessonTypes:e,templates:t,onSave:n,onDelete:r,onReorder:l,onSaveMatrix:i}){const[s,o]=F.useState(null),[u,d]=F.useState([]),[g,h]=F.useState(!1),[m,x]=F.useState(e),w=e.filter(c=>c.category==="student");F.useEffect(()=>{d(t.map(c=>({template_id:c.id,lesson_type_ids:c.lesson_type_ids||[]})))},[t]),F.useEffect(()=>{x(e)},[e]);function v(c,p){d(y=>y.map(j=>{if(j.template_id!==c)return j;const M=j.lesson_type_ids.includes(p);return{...j,lesson_type_ids:M?j.lesson_type_ids.filter(S=>S!==p):[...j.lesson_type_ids,p]}}))}function D(c,p){const y=[...m];if(c<0||p<0||c>=y.length||p>=y.length)return y;const[j]=y.splice(c,1);return y.splice(p,0,j),y}async function f(c){x(c),await l(c.map(p=>p.id))}return a.jsxs(a.Fragment,{children:[a.jsxs(a.Fragment,{children:[a.jsxs("section",{className:"list-panel",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Verwaltung"}),a.jsx("h2",{children:"Stundenarten"}),a.jsx("p",{className:"meta-line",children:"Mit Hoch-/Runter-Knoepfen umsortieren. Farben, Dauer und Kategorie gelten mandantenweit."})]}),a.jsx("button",{className:"primary-button",onClick:()=>o({name:"",color:"#0E5D80",default_duration:45,category:"student",requirement_key:"",is_billable:1,is_counted:1,is_rest_relevant:1,fixed_duration:0,sort_order:m.length+1,system_key:""}),children:"Neu"})]}),a.jsx("div",{className:"entity-list lesson-type-list",children:m.map((c,p)=>a.jsxs("div",{className:"entity-item lesson-type-row",children:[a.jsxs("div",{className:"lesson-type-actions",children:[a.jsx("button",{type:"button",className:"ghost-button compact reorder-button",disabled:p===0,onClick:async y=>{y.stopPropagation(),await f(D(p,p-1))},children:"Hoch"}),a.jsx("button",{type:"button",className:"ghost-button compact reorder-button",disabled:p===m.length-1,onClick:async y=>{y.stopPropagation(),await f(D(p,p+1))},children:"Runter"})]}),a.jsx("span",{className:"color-dot",style:{"--dot-color":c.color||"#67625a"}}),a.jsxs("span",{className:"lesson-type-main",children:[a.jsx("strong",{children:c.name}),a.jsxs("span",{className:"meta-line",children:[c.default_duration," min · ",Yc(c.category)]})]}),a.jsx("button",{type:"button",className:"ghost-button compact",onClick:()=>o(c),children:"Bearbeiten"})]},c.id))})]}),s&&a.jsx(Hl,{title:"Stundenart",initial:s,onClose:()=>o(null),onSubmit:async c=>{c.category=c.category||s.category,c.default_duration=Number(c.default_duration),c.sort_order=Number(c.sort_order),await n(c,s.id),o(null)},onDelete:s.id&&s.system_key!=="private_block"?async()=>{window.confirm(`Stundenart "${s.name}" wirklich loeschen?`)&&(await r(s.id),o(null))}:null,fields:[{name:"name",label:"Bezeichnung"},{name:"color",label:"Farbe",type:"color"},{name:"default_duration",label:"Dauer in Minuten",type:"number"},{name:"category",label:"Kategorie",type:"select",disabled:s.system_key==="private_block",options:[{value:"student",label:"Fahrschueler"},{value:"private",label:"Privat"},{value:"work",label:"Sonstige Arbeiten"},{value:"theory",label:"Theorie"},{value:"blocked",label:"Sperre"}]},{name:"requirement_key",label:"Pflichtbaustein-Key"},{name:"sort_order",label:"Sortierung",type:"number"},{name:"is_billable",label:"Abrechenbar",type:"checkbox"},{name:"is_counted",label:"Zaehlt fuer Pflichtstunden",type:"checkbox"},{name:"is_rest_relevant",label:"Ruhezeit-relevant",type:"checkbox"},{name:"fixed_duration",label:"Feste Dauer",type:"checkbox"}]})]}),w.length>0&&t.length>0&&a.jsxs("section",{className:"list-panel",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Sichtbarkeit"}),a.jsx("h2",{children:"Klassenmatrix"}),a.jsx("p",{className:"meta-line",children:"Fuehrerscheinklassen stehen vertikal, schuelerbezogene Stundenarten horizontal."})]}),a.jsx("button",{className:"primary-button",onClick:async()=>{h(!0);try{await i(u)}finally{h(!1)}},disabled:g,children:g?"Speichert ...":"Matrix speichern"})]}),a.jsx("div",{className:"visibility-matrix-wrap",children:a.jsxs("table",{className:"visibility-matrix",children:[a.jsx("thead",{children:a.jsxs("tr",{children:[a.jsx("th",{children:"Fuehrerscheinklasse"}),w.map(c=>a.jsx("th",{children:c.name},c.id))]})}),a.jsx("tbody",{children:t.map(c=>{const p=u.find(y=>y.template_id===c.id)||{lesson_type_ids:[]};return a.jsxs("tr",{children:[a.jsx("th",{children:c.name}),w.map(y=>a.jsx("td",{children:a.jsx("input",{type:"checkbox",checked:p.lesson_type_ids.includes(y.id),onChange:()=>v(c.id,y.id)})},y.id))]},c.id)})})]})})]})]})}function Yp({instructors:e,onSave:t}){const[n,r]=F.useState(null);return a.jsx(Vl,{title:"Fahrlehrer",subtitle:"Mehrere Ressourcen werden direkt im Kalender abgebildet.",items:e,columns:l=>`${l.first_name} ${l.last_name} · ${l.is_active?"aktiv":"inaktiv"} · Anfahrt ${Number(l.pre_start_buffer_minutes||0)} min`,onEdit:l=>r(l),onCreate:()=>r({first_name:"",last_name:"",color:"#0E5D80",notes:"",is_active:1,pre_start_buffer_minutes:0}),form:n&&a.jsx(Hl,{title:"Fahrlehrer",initial:n,onClose:()=>r(null),onSubmit:async l=>{await t(l,n.id),r(null)},fields:[{name:"first_name",label:"Vorname"},{name:"last_name",label:"Nachname"},{name:"color",label:"Farbe",type:"color"},{name:"pre_start_buffer_minutes",label:"Anfahrtszeit vor erstem Termin (Minuten)",type:"number"},{name:"notes",label:"Notiz",type:"textarea"},{name:"is_active",label:"Aktiv",type:"checkbox"}]})})}function Xp({templates:e,lessonTypes:t,onSave:n}){const[r,l]=F.useState(null);return a.jsx(Vl,{title:"Klassenvorlagen",subtitle:"Kombinationen wie C + CE sind eigene Vorlagen mit festen Sollwerten.",items:e,columns:i=>`${i.code} · ${Zr(i,t).map(s=>`${s.label}: ${s.required_units}`).join(", ")||"keine Pflichtstunden"}`,onEdit:i=>{const s=Bp(i);l({...s,requirements:Zr(s,t)})},onCreate:()=>l({code:"",name:"",is_combination:0,lesson_type_ids:t.filter(i=>i.category==="student").map(i=>i.id),requirements:Zr({lesson_type_ids:t.filter(i=>i.category==="student").map(i=>i.id),requirements:[]},t)}),form:r&&a.jsx(em,{template:r,setTemplate:l,lessonTypes:t,onClose:()=>l(null),onSubmit:async()=>{await n(r,r.id),l(null)}})})}function Gp({users:e,tenants:t,currentUser:n,onSave:r}){const[l,i]=F.useState(null);return a.jsx(Vl,{title:"Benutzer",subtitle:"Rollen und Zugriff werden serverseitig geprueft.",items:e,columns:s=>`${s.first_name} ${s.last_name} · ${s.role} · ${s.email}${s.tenant_name?` · ${s.tenant_name}`:""}`,onEdit:s=>i(s),onCreate:()=>{var s;return i({tenant_id:n.tenant_id||((s=t[0])==null?void 0:s.id)||"",role:"dispatcher",email:"",password:"",first_name:"",last_name:"",is_active:1})},form:l&&a.jsx(Hl,{title:"Benutzer",initial:l,onClose:()=>i(null),onSubmit:async s=>{await r(s,l.id),i(null)},fields:[...n.role==="superadmin"?[{name:"tenant_id",label:"Mandant",type:"select",options:t.map(s=>({value:s.id,label:s.name}))}]:[],{name:"role",label:"Rolle",type:"select",options:[{value:"tenant_admin",label:"Mandanten-Admin"},{value:"dispatcher",label:"Disponent"},{value:"instructor",label:"Fahrlehrer"},...n.role==="superadmin"?[{value:"superadmin",label:"Superadmin"}]:[]]},{name:"email",label:"E-Mail"},{name:"password",label:"Passwort"},{name:"first_name",label:"Vorname"},{name:"last_name",label:"Nachname"},{name:"is_active",label:"Aktiv",type:"checkbox"}]})})}function Zp({tenants:e,onSave:t}){const[n,r]=F.useState(null);return a.jsx(Vl,{title:"Mandanten",subtitle:"Superadmin-Verwaltung fuer Mandantenstatus und Zeitzone.",items:e,columns:l=>`${l.name} · ${l.timezone} · ${l.is_active?"aktiv":"inaktiv"}`,onEdit:l=>r(l),onCreate:()=>r({name:"",timezone:"Europe/Berlin",is_active:1}),form:n&&a.jsx(Hl,{title:"Mandant",initial:n,onClose:()=>r(null),onSubmit:async l=>{await t(l,n.id),r(null)},fields:[{name:"name",label:"Name"},{name:"timezone",label:"Zeitzone"},{name:"federal_state",label:"Bundesland",type:"select",options:Wc},{name:"location_label",label:"Ort / Filiale"},{name:"latitude",label:"Breitengrad",type:"number"},{name:"longitude",label:"Laengengrad",type:"number"},{name:"is_active",label:"Aktiv",type:"checkbox"}]})})}function Jp({tenant:e,onSave:t}){const[n,r]=F.useState(e),[l,i]=F.useState((e==null?void 0:e.location_label)||""),[s,o]=F.useState([]),[u,d]=F.useState(!1),[g,h]=F.useState("");F.useEffect(()=>{r(e),i((e==null?void 0:e.location_label)||""),o([]),h("")},[e]);const m=Number(n==null?void 0:n.latitude),x=Number(n==null?void 0:n.longitude),w=Number.isFinite(m)&&Number.isFinite(x),v=.025,D=w?`https://www.openstreetmap.org/export/embed.html?bbox=${x-v}%2C${m-v}%2C${x+v}%2C${m+v}&layer=mapnik&marker=${m}%2C${x}`:"";async function f(){const c=l.trim();if(!c){o([]),h("Bitte einen Ort oder eine Adresse eingeben.");return}d(!0),h("");try{const p=await fetch(`https://nominatim.openstreetmap.org/search?format=jsonv2&limit=5&q=${encodeURIComponent(c)}`);if(!p.ok)throw new Error(`Ortssuche fehlgeschlagen (${p.status})`);const y=await p.json();o(y),y.length||h("Kein passender Ort gefunden.")}catch(p){h(p.message),o([])}finally{d(!1)}}return a.jsxs("section",{className:"list-panel",children:[a.jsx("div",{className:"section-head",children:a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Fahrschule"}),a.jsx("h2",{children:"Standort und Zeit"}),a.jsx("p",{className:"meta-line",children:"Ort, Bundesland und Zeitzone steuern Sonnenzeiten, Feiertage und die Hell-Dunkel-Markierung im Kalender."})]})}),a.jsxs("form",{className:"stack-form",onSubmit:async c=>{c.preventDefault(),await t(n)},children:[a.jsxs("label",{children:["Name",a.jsx("input",{value:n.name||"",onChange:c=>r({...n,name:c.target.value})})]}),a.jsxs("label",{children:["Zeitzone",a.jsx("input",{value:n.timezone||"",onChange:c=>r({...n,timezone:c.target.value})})]}),a.jsxs("label",{children:["Bundesland",a.jsx("select",{value:n.federal_state||"BE",onChange:c=>r({...n,federal_state:c.target.value}),children:Wc.map(c=>a.jsx("option",{value:c.value,children:c.label},c.value))})]}),a.jsxs("label",{children:["Ort / Filiale",a.jsxs("div",{className:"location-search",children:[a.jsx("input",{value:l,onChange:c=>{const p=c.target.value;i(p),r({...n,location_label:p})},placeholder:"z. B. Duisburg, Bahnhofstrasse 12"}),a.jsx("button",{type:"button",className:"ghost-button",onClick:f,disabled:u,children:u?"Suche...":"Ort suchen"})]})]}),g&&a.jsx("p",{className:"form-error",children:g}),s.length>0&&a.jsx("div",{className:"location-result-list",children:s.map(c=>a.jsxs("button",{type:"button",className:"location-result",onClick:()=>{r({...n,location_label:c.display_name,latitude:Number(c.lat),longitude:Number(c.lon)}),i(c.display_name),o([]),h("")},children:[a.jsx("strong",{children:c.display_name}),a.jsxs("span",{children:[Number(c.lat).toFixed(6)," / ",Number(c.lon).toFixed(6)]})]},`${c.place_id}-${c.lat}-${c.lon}`))}),a.jsxs("div",{className:"coordinate-grid",children:[a.jsxs("label",{children:["Breitengrad",a.jsx("input",{type:"number",step:"0.000001",value:n.latitude??"",onChange:c=>r({...n,latitude:c.target.value})})]}),a.jsxs("label",{children:["Laengengrad",a.jsx("input",{type:"number",step:"0.000001",value:n.longitude??"",onChange:c=>r({...n,longitude:c.target.value})})]})]}),w&&a.jsxs("div",{className:"map-preview",children:[a.jsx("iframe",{title:"Standortkarte",src:D,loading:"lazy",referrerPolicy:"no-referrer-when-downgrade"}),a.jsx("p",{className:"meta-line",children:"Karte zeigt den derzeit gespeicherten Standort."})]}),a.jsxs("div",{className:"location-hint",children:[a.jsx("strong",{children:"Hinweis"}),a.jsx("span",{children:"Feiertage richten sich nach dem Bundesland. Sonnenaufgang und Sonnenuntergang richten sich nach dem gespeicherten Standort."})]}),a.jsx("button",{className:"primary-button",type:"submit",children:"Speichern"})]})]})}function Vl({title:e,subtitle:t,items:n,columns:r,onEdit:l,onCreate:i,form:s}){return a.jsxs(a.Fragment,{children:[a.jsxs("section",{className:"list-panel",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Verwaltung"}),a.jsx("h2",{children:e}),a.jsx("p",{className:"meta-line",children:t})]}),a.jsx("button",{className:"primary-button",onClick:i,children:"Neu"})]}),a.jsx("div",{className:"entity-list",children:n.map(o=>a.jsx("button",{className:"entity-item",onClick:()=>l(o),children:r(o)},o.id))})]}),s]})}function Hl({title:e,initial:t,fields:n,onSubmit:r,onClose:l,onDelete:i=null}){async function s(o){o.preventDefault();const u=new FormData(o.currentTarget),d=Object.fromEntries(u.entries());for(const g of n)g.type==="checkbox"&&(d[g.name]=u.get(g.name)?1:0);await r(d)}return a.jsx("div",{className:"modal-shell",children:a.jsxs("form",{className:"modal-card stack-form",onSubmit:s,children:[a.jsxs("div",{className:"section-head",children:[a.jsx("h2",{children:e}),a.jsx("button",{type:"button",className:"ghost-button",onClick:l,children:"Schliessen"})]}),n.map(o=>a.jsxs("label",{children:[o.label,o.type==="textarea"?a.jsx("textarea",{name:o.name,defaultValue:t[o.name]||"",rows:"4",disabled:o.disabled}):o.type==="select"?a.jsx("select",{name:o.name,defaultValue:String(t[o.name]??""),disabled:o.disabled,children:o.options.map(u=>a.jsx("option",{value:u.value,children:u.label},u.value))}):o.type==="checkbox"?a.jsx("input",{name:o.name,type:"checkbox",defaultChecked:!!t[o.name],disabled:o.disabled}):a.jsx("input",{name:o.name,type:o.type||"text",defaultValue:t[o.name]||"",disabled:o.disabled})]},o.name)),a.jsxs("div",{className:"modal-actions",children:[i&&a.jsx("button",{type:"button",className:"danger-button",onClick:i,children:"Loeschen"}),a.jsx("button",{className:"primary-button",type:"submit",children:"Speichern"})]})]})})}function em({template:e,setTemplate:t,lessonTypes:n,onClose:r,onSubmit:l}){function i(s){const o={...e,is_combination:s?1:0};t({...o,requirements:Zr(o,n)})}return a.jsx("div",{className:"modal-shell",children:a.jsxs("form",{className:"modal-card stack-form",onSubmit:s=>{s.preventDefault(),l()},children:[a.jsxs("div",{className:"section-head",children:[a.jsx("h2",{children:"Klassenvorlage"}),a.jsx("button",{type:"button",className:"ghost-button",onClick:r,children:"Schliessen"})]}),a.jsxs("label",{children:["Code",a.jsx("input",{value:e.code,onChange:s=>t({...e,code:s.target.value})})]}),a.jsxs("label",{children:["Name",a.jsx("input",{value:e.name,onChange:s=>t({...e,name:s.target.value})})]}),a.jsxs("label",{children:["Kombination",a.jsx("input",{type:"checkbox",checked:!!e.is_combination,onChange:s=>i(s.target.checked)})]}),pa(e.requirements).map(s=>a.jsxs("div",{className:"requirement-phase",children:[s.phase_label&&a.jsx("h3",{children:s.phase_label}),a.jsx("div",{className:"requirements-grid",children:s.rows.map(o=>{const u=e.requirements.findIndex(d=>d.requirement_key===o.requirement_key);return a.jsxs("div",{className:"requirement-editor",children:[a.jsxs("strong",{className:"requirement-title",children:[a.jsx("span",{className:"color-dot",style:{"--dot-color":o.color||"#67625a"}}),o.label]}),a.jsx("input",{type:"number",min:"0",value:o.required_units,onChange:d=>{const g=[...e.requirements];g[u]={...g[u],required_units:Number(d.target.value)},t({...e,requirements:g})}})]},o.requirement_key)})})]},s.phase_label||"default")),a.jsx("button",{className:"primary-button",type:"submit",children:"Speichern"})]})})}function Nl({label:e,value:t,onChange:n}){const[r,l]=F.useState(!1),[i,s]=F.useState("hour"),o=Number((t||"00:00").split(":")[0]||0),u=Number((t||"00:00").split(":")[1]||0);F.useEffect(()=>{r&&s("hour")},[r]);const d=Array.from({length:12},(v,D)=>D),g=Array.from({length:12},(v,D)=>D+12),h=Array.from({length:12},(v,D)=>D*5);function m(v,D){const f=v/12*Math.PI*2-Math.PI/2,c=Math.cos(f)*D,p=Math.sin(f)*D;return{left:`calc(50% + ${c}px)`,top:`calc(50% + ${p}px)`}}function x(v){n(`${String(v).padStart(2,"0")}:${String(u).padStart(2,"0")}`),s("minute")}function w(v){n(`${String(o).padStart(2,"0")}:${String(v).padStart(2,"0")}`),l(!1)}return a.jsxs("div",{className:"time-dial-field",children:[a.jsxs("button",{type:"button",className:"time-dial-button",onClick:()=>l(v=>!v),children:[a.jsx("span",{children:e}),a.jsx("strong",{children:t})]}),r&&a.jsxs("div",{className:"time-dial-popover",children:[a.jsxs("div",{className:"time-dial-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Uhrzeit"}),a.jsx("strong",{children:t})]}),a.jsx("button",{type:"button",className:"ghost-button compact",onClick:()=>l(!1),children:"Schliessen"})]}),a.jsxs("div",{className:"mode-switch",children:[a.jsx("button",{type:"button",className:i==="hour"?"tab-button active compact":"tab-button compact",onClick:()=>s("hour"),children:"Stunde"}),a.jsx("button",{type:"button",className:i==="minute"?"tab-button active compact":"tab-button compact",onClick:()=>s("minute"),children:"Minute"})]}),a.jsxs("div",{className:"time-dial-face",children:[a.jsx("div",{className:"time-dial-center",children:i==="hour"?String(o).padStart(2,"0"):String(u).padStart(2,"0")}),i==="hour"?a.jsxs(a.Fragment,{children:[g.map((v,D)=>a.jsx("button",{type:"button",className:["time-dial-point","outer-ring",v===o?"active":""].filter(Boolean).join(" "),style:m(D,112),onClick:()=>x(v),children:String(v).padStart(2,"0")},`outer-${v}`)),d.map((v,D)=>a.jsx("button",{type:"button",className:["time-dial-point","inner-ring",v===o?"active":""].filter(Boolean).join(" "),style:m(D,72),onClick:()=>x(v),children:String(v).padStart(2,"0")},`inner-${v}`))]}):h.map((v,D)=>a.jsx("button",{type:"button",className:["time-dial-point",v===u?"active":""].filter(Boolean).join(" "),style:m(D,108),onClick:()=>w(v),children:String(v).padStart(2,"0")},`${i}-${v}`))]}),a.jsxs("label",{className:"stack-form",children:["Direkt eingeben",a.jsx("input",{type:"time",step:"300",value:t,onChange:v=>n(v.target.value)})]})]})]})}function tm({draft:e,setDraft:t,lessonTypes:n,instructors:r,students:l,templates:i,onPickStudent:s,onSubmit:o,onDelete:u,onClose:d,isEditing:g}){const h=Up(n,e.student_id,l),m=n.find(N=>Number(N.id)===Number(e.lesson_type_id)),x=h.some(N=>Number(N.id)===Number(e.lesson_type_id))?h:m?[m,...h]:h,w=l.find(N=>Number(N.id)===Number(e.student_id)),v=i.find(N=>Number(N.id)===Number(w==null?void 0:w.template_id)),D=r.find(N=>Number(N.id)===Number(e.instructor_id)),f=v&&Number(v.is_combination)?Kc(v):[],c=ys(m,e.start_at,e.end_at),p=Si(e.start_at),y=Br(e.start_at),j=Br(e.end_at),M=Rp(w,D,e.start_at,e.end_at),S=Fp(w==null?void 0:w.phone),T=S&&M?`https://wa.me/${S}?text=${encodeURIComponent(M)}`:"",$=S&&M?`sms:${S}?body=${encodeURIComponent(M)}`:"",L=w!=null&&w.email&&M?`mailto:${encodeURIComponent(w.email)}?subject=${encodeURIComponent("Fahrstunde")}&body=${encodeURIComponent(M)}`:"";F.useEffect(()=>{x.length&&(x.some(N=>Number(N.id)===Number(e.lesson_type_id))||t(N=>({...N,lesson_type_id:x[0].id})))},[e.lesson_type_id,x,t]),F.useEffect(()=>{e.end_at&&Ul(e.start_at,e.end_at)>0||t(N=>({...N,end_at:_i(N.start_at,Math.max(1,Number((m==null?void 0:m.default_duration)||45)))}))},[m==null?void 0:m.default_duration,e.end_at,e.start_at,t]);function W(N){N&&t(V=>({...V,start_at:jt(N,Br(V.start_at)),end_at:jt(N,Br(V.end_at))}))}function C(N){t(V=>{const J=jt(Si(V.start_at),N);return{...V,start_at:J,end_at:_i(J,ys(m,V.start_at,V.end_at).duration)}})}function R(N){t(V=>({...V,end_at:jt(Si(V.start_at),N)}))}function A(N){const V=Number((m==null?void 0:m.fixed_duration)||0)===1?Math.max(1,Number((m==null?void 0:m.default_duration)||45)):Math.max(1,Number((m==null?void 0:m.default_duration)||45))*N;t(J=>({...J,units:N,end_at:_i(J.start_at,V)}))}return a.jsx("div",{className:"modal-shell",children:a.jsxs("form",{className:"modal-card stack-form",onSubmit:o,children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Termin"}),a.jsx("h2",{children:g?"Termin bearbeiten":"Termin einplanen"})]}),a.jsx("button",{type:"button",className:"ghost-button",onClick:d,children:"Schliessen"})]}),a.jsxs("label",{children:["Fahrschueler",a.jsxs("select",{name:"student_id",value:e.student_id,onChange:N=>t({...e,student_id:N.target.value}),children:[a.jsx("option",{value:"",children:"Kein Fahrschueler"}),l.map(N=>a.jsxs("option",{value:N.id,children:[N.first_name," ",N.last_name]},N.id))]})]}),a.jsxs("label",{children:["Stundenart",a.jsx("select",{name:"lesson_type_id",value:e.lesson_type_id,onChange:N=>t({...e,lesson_type_id:N.target.value}),children:x.map(N=>a.jsx("option",{value:N.id,children:N.name},N.id))})]}),f.length>0&&a.jsxs("label",{children:["Ausbildungsteil",a.jsx("select",{name:"requirement_phase",value:e.requirement_phase||f[0],onChange:N=>t({...e,requirement_phase:N.target.value}),children:f.map(N=>a.jsx("option",{value:N,children:N},N))})]}),a.jsxs("label",{children:["Fahrlehrer",a.jsx("select",{name:"instructor_id",value:e.instructor_id,onChange:N=>t({...e,instructor_id:N.target.value}),children:r.map(N=>a.jsxs("option",{value:N.id,children:[N.first_name," ",N.last_name]},N.id))})]}),a.jsxs("div",{className:"time-editor-grid",children:[a.jsxs("label",{children:["Datum",a.jsx("input",{type:"date",value:p,onChange:N=>W(N.target.value)})]}),a.jsxs("div",{className:"stack-form",children:[a.jsx("span",{children:"Startzeit"}),a.jsx(Nl,{label:"Start",value:y,onChange:C})]}),a.jsxs("div",{className:"stack-form",children:[a.jsx("span",{children:"Endzeit"}),a.jsx(Nl,{label:"Ende",value:j,onChange:R})]})]}),a.jsxs("div",{className:"duration-preset-panel",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Dauer"}),a.jsx("h2",{children:"Fahrstunden"})]}),a.jsxs("span",{className:"badge",children:[c.duration," min · ",c.units," FS"]})]}),a.jsxs("div",{className:"duration-preset-row",children:[[1,2,3,4].map(N=>a.jsxs("button",{type:"button",className:c.units===N?"tab-button active compact":"tab-button compact",onClick:()=>A(N),children:[N," FS"]},N)),Number((m==null?void 0:m.fixed_duration)||0)===1&&a.jsxs("button",{type:"button",className:"ghost-button compact",onClick:()=>A(1),children:["Standard ",m==null?void 0:m.default_duration," min"]})]}),a.jsx("p",{className:"hint-line",children:"Start und Ende lassen sich direkt ueber die Uhr setzen. Die angerechneten Fahrstunden werden daraus automatisch berechnet."})]}),a.jsxs("div",{className:"share-panel",children:[a.jsx("div",{className:"section-head",children:a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Benachrichtigung"}),a.jsx("h2",{children:"Termin teilen"})]})}),a.jsxs("div",{className:"share-actions",children:[a.jsxs("a",{className:T?"share-button":"share-button disabled",href:T||void 0,target:"_blank",rel:"noreferrer","aria-disabled":!T,onClick:N=>{T||N.preventDefault()},children:[a.jsx("span",{className:"share-badge whatsapp",children:"WA"}),"WhatsApp"]}),a.jsxs("a",{className:$?"share-button":"share-button disabled",href:$||void 0,"aria-disabled":!$,onClick:N=>{$||N.preventDefault()},children:[a.jsx("span",{className:"share-badge sms",children:"SMS"}),"SMS"]}),a.jsxs("a",{className:L?"share-button":"share-button disabled",href:L||void 0,"aria-disabled":!L,onClick:N=>{L||N.preventDefault()},children:[a.jsx("span",{className:"share-badge email",children:"@"}),"E-Mail"]})]}),a.jsx("p",{className:"hint-line",children:w?"Die Nachricht wird mit Datum, Start- und Endzeit vorbefuellt.":"Fahrschueler waehlen, damit Kontakt-Buttons aktiv werden."})]}),a.jsxs("label",{children:["Status",a.jsxs("select",{name:"status",value:e.status,onChange:N=>t({...e,status:N.target.value}),children:[a.jsx("option",{value:"planned",children:"geplant"}),a.jsx("option",{value:"completed",children:"erledigt"}),a.jsx("option",{value:"cancelled",children:"abgesagt"})]})]}),a.jsxs("label",{children:["Notiz",a.jsx("textarea",{name:"notes",rows:"4",value:e.notes,onChange:N=>t({...e,notes:N.target.value})})]}),a.jsxs("div",{className:"modal-actions",children:[g&&a.jsx("button",{type:"button",className:"danger-button",onClick:u,children:"Termin loeschen"}),a.jsx("button",{type:"button",className:"ghost-button",onClick:()=>s==null?void 0:s(e.student_id),disabled:!e.student_id,children:"Fahrschueler waehlen"}),a.jsx("button",{className:"primary-button",type:"submit",children:"Speichern"})]})]})})}ki.createRoot(document.getElementById("root")).render(a.jsx(md.StrictMode,{children:a.jsx(Vp,{})})); diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/index.html b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/index.html new file mode 100644 index 0000000..06213e8 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/index.html @@ -0,0 +1,27 @@ + + + + + +fahrschultermin.de – Erfolgreich eingerichtet + + + +

✅ fahrschultermin.de ist betriebsbereit

+

Diese Seite wurde automatisch vom Webhosting‑Stack erzeugt.

+
+ Nächste Schritte: +
    +
  • Ersetze diesen Inhalt durch deine eigene Website.
  • +
  • Lade Dateien in das öffentliche Verzeichnis dieser Domain hoch.
  • +
  • Für PHP‑Anwendungen lege eine index.php an.
  • +
+
+

Server: core

+ + \ No newline at end of file diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/index.php b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/index.php new file mode 100644 index 0000000..3fbfe27 --- /dev/null +++ b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/public/index.php @@ -0,0 +1,100 @@ +path(); + +if ($request->method() === 'OPTIONS') { + http_response_code(204); + exit; +} + +if (str_starts_with($path, '/api/v1')) { + $auth = new AuthController(); + $bootstrapController = new BootstrapController(); + $students = new StudentsController(); + $references = new ReferenceDataController(); + $appointments = new AppointmentsController(); + $users = new UsersController(); + $tenants = new TenantsController(); + $instructors = new InstructorsController(); + + $router->post('/api/v1/auth/login', [$auth, 'login']); + $router->post('/api/v1/auth/logout', [$auth, 'logout']); + $router->get('/api/v1/auth/me', [$auth, 'me']); + $router->get('/api/v1/bootstrap', $bootstrapController); + $router->get('/api/v1/students', [$students, 'index']); + $router->post('/api/v1/students', [$students, 'store']); + $router->patch('/api/v1/students/{id}/training', [$students, 'updateTraining']); + $router->patch('/api/v1/students/{id}', [$students, 'update']); + $router->delete('/api/v1/students/{id}', [$students, 'destroy']); + $router->get('/api/v1/lesson-types', [$references, 'lessonTypes']); + $router->post('/api/v1/lesson-types', [$references, 'storeLessonType']); + $router->post('/api/v1/lesson-types/reorder', [$references, 'reorderLessonTypes']); + $router->patch('/api/v1/lesson-types/{id}', [$references, 'updateLessonType']); + $router->delete('/api/v1/lesson-types/{id}', [$references, 'destroyLessonType']); + $router->post('/api/v1/lesson-types/visibility', [$references, 'updateLessonTypeMatrix']); + $router->get('/api/v1/license-class-templates', [$references, 'templates']); + $router->post('/api/v1/license-class-templates', [$references, 'storeTemplate']); + $router->patch('/api/v1/license-class-templates/{id}', [$references, 'updateTemplate']); + $router->get('/api/v1/instructors', [$instructors, 'index']); + $router->post('/api/v1/instructors', [$instructors, 'store']); + $router->patch('/api/v1/instructors/{id}', [$instructors, 'update']); + $router->get('/api/v1/users', [$users, 'index']); + $router->post('/api/v1/users', [$users, 'store']); + $router->patch('/api/v1/users/{id}', [$users, 'update']); + $router->get('/api/v1/tenants', [$tenants, 'index']); + $router->post('/api/v1/tenants', [$tenants, 'store']); + $router->patch('/api/v1/tenants/{id}', [$tenants, 'update']); + $router->patch('/api/v1/tenant/profile', [$tenants, 'updateOwn']); + $router->get('/api/v1/appointments', [$appointments, 'index']); + $router->post('/api/v1/appointments/validate', [$appointments, 'validate']); + $router->post('/api/v1/appointments', [$appointments, 'store']); + $router->patch('/api/v1/appointments/{id}', [$appointments, 'update']); + $router->delete('/api/v1/appointments/{id}', [$appointments, 'destroy']); + + $router->dispatch(); + exit; +} + +$assetManifestPath = __DIR__ . '/assets/.vite/manifest.json'; +$mainAsset = '/assets/index.js'; +$styleAsset = '/assets/index.css'; + +if (is_file($assetManifestPath)) { + $manifest = json_decode(file_get_contents($assetManifestPath), true); + $entry = $manifest['src/main.jsx'] ?? null; + if (is_array($entry)) { + $mainAsset = '/assets/' . ltrim((string) $entry['file'], '/'); + if (!empty($entry['css'][0])) { + $styleAsset = '/assets/' . ltrim((string) $entry['css'][0], '/'); + } + } +} + +?> + + + + + DriveTime Planner + + + +
+ + + diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/database/app.sqlite b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/database/app.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..aff7010de53509f2ca087989483c95f35217dfc2 GIT binary patch literal 126976 zcmeHw33wdGb!H#aGt+Z5L4q7WAjpA05`=iofm@^gSVj*r-iH(5Jz;$3H7 zRrOWXJu@Ip%TiKfnT46EdhgZ$UcEZ{RrT4^$4WIlHMd+@D%MgtVVxj~!huvu5C%^P zg0L0-Z+$x9kAU?BQqlEygFk`B-s_hYR9O2SDx;C4L2>>wM}Q;15#R`L1ULd50geDi zfFr;W;0SO8I08Qe1QOvmP?fZCLHkAchkrN%90861M}Q;15#R`L1ULd50geDifFr;W z@Izo$iV9tQYHqwM6lz;4&d%$VrNU}uc}3qhsaFSgqER%!(cBLUBeQ>!_t?A_hM?U*cfAqOcPaWv`AD36((DRlP=N}S7Lat98A3s!F zs8nYbRu}cuiRH>{rBt48z-S^x zqF-JuRrDqFcePwxsV*$ns^Xn(io9J~)T`Cy@^tOmieB9t(XI>V894d0MIgVpva(z% z*HGQ6@a{W9Kz6Y-qnE4tbWLAcSuECcH~p&c_DxZFpS#-hYPkgLE_DUvyG5(+%wn-x zwJB7?+HVQH7T*SGRFu0!*byj#B$5yd%iW^cz-m(awy>VArUm3)u~e?sDyuU!*h-L9 zSx$=;y`TFj~%*B%s<*r$ZVhZC{*jP`Q)lYck@90861M}Q;1 z5#R`L1ULd50gk|n8v!F3j0xiQU53&Xhv%^pSvE}(JKea z=F{B0HN9Fh<-7mziA&2DD%E|5i`mQb*VC2kTK9U|fqThFJ z;?ms8<;PZ!6h|%}FII{Nf&8*p$W{-mjcpPLxjhF$c7c#?HDg`-3VGzn#p1E0`Qw$* zBlo3O9(=sEdidN4{p@hAcJ1i!m3=F7XVTTbM}Q;15#R`L1ULd5 z0geDifFr;W;0U}#5!fK_5}6&U;AVNh*l2qw&?yg#zFRVKQqGA!yC4#(;k)Iapx6gl zTPJ8A(q0Y;|8N920vrL307rl$z!BgGa0EC490861N8sND0>>Z(LQKXXhGcwP5GDLr zy*I57&W@y=$LdoM;E@|1%dkNJ15@cyILAMhGas(sa}urxP&<=^+A)2(T+)wLigUHz ziQ$2f?2wxxIj-L-MB}802yVm#MJLvyH(eM!NK3djdI7Zl-ywWR(B7>LC!S3##Q!<| zw)jx&xmYC@j{ZvYP~>kSuaESpA6Fj;e>rS~wa~jmqruMxmx3YX7nI?^9|azf|5<)Y zPD&q^j*5RHzDDc>Vn4jk{EePJhZ&5gKB^yaeznY4}AM{k)+ap^`EaLILv8*Mu~y9`HbO7nVZs;EPHNiWrUhx56K z!ohp4E>|wezjpK7P`&A8u(`_ejXQwfb{D_gd3|P~sxP8lz*=u^a(H5Jcn@au-dki; zs}#%CIlVFs_l8{2AHC5D9Fs1Ny_2gTNbktx;6!$kwCQcP$ZC0R4o*xh-{=4q{Vo>! zP+;11eRY0iZV@7%Ot(dNz2O#lUC|dxGmHB4OtGTR-ADq*&MvVF)D}kWq3;wlwpi;O znjFrKr0-e5-*ox4H_yzhI(>0*dFIjrn1@KYA<6O%&HPYqDm^@w9U2?VFn);5aQx6L z7=}o>A)8ZUKg5!=Tgzscd}^~`3=Oz6_CqYOR$OLh!5$iLY3zq?mdng6*gyj=js4Kg za=AFWsUerfe&}Yo6w4cd3+0BSwfmtpb94OAN;~kQ+>o>eKXh}9Xg^fk030bdBsKFx zH^++jp_TQ(f^tLBn*7ks@pAj2`8ME4xgn{QA6hdr$`8%014dvUU4p8rVG-u6z^Hhs zR)Sli^wg=!Gas9Sz}?K4%^t4Z1t(;8@ zhdl!U)4)hBtA`@rhz7QgN!%DTwTPcHanpxz-_fzbbc+@&L^MIY8_|bxQw2jh3!%y|RF`V0eW^(_FmT!vn*4Jo58svy&skqqz&nw}x(>!*mc( zA{G@^kv>jUR&~^utHhutMsn~uhQ}_uK-PR_7p&%wqPy<&R6dj11^*P> zGOlgidsqR28+rg!H?>@IK-rhm#(KGWGA zf+JPX!xc~c&JB>&1$|rb)Jv{MwV+okbdL^fZG((%=+BCOUk-JyGx-I){4|qaz{@XT z@(Zy1;wHauvUr14~F<*L&BtX=&)gBRO29gc~Lcg&I%JhN8%#p>PS6~*(c81ON zNlg1>xie(8Ph#4qNXektK8a?pY*ozmN&Y+|9f1&>%mw{<*mcN|gVe`x3rL$JR0p)j zFxw)+??`$6yu$J5JB9V(?+3M4XyMqOCFT+xkznNiMvuo{8_PyM5P2xRBmN2X!|KD~ z???X^12g~15#R`L1ULd50geDi;3bH_!<*uQC~n*nk9Urb$HP)spD!-XSM>SiN(xTU zFD^qAD?Yq=MJJ(^a5R2+Dw7>cXU5W5cL1+X#?(eke5*w~gos~)_jA>*18sC9)I$}5 zbQ@94nkTr=wqvT@7FAQ^XFghnS35T#nxihNvPFf9s?3bjp(x;>^I|qn}K+$aVNtGseys~^1lyj-%D`MJv z-Njdq3{FusuM|MFj}W=Fnf-! z&cNYrw_JmJGMTB&Fx-#D=)ouBR2XFj^P$XFjyAl9tkHEBq#_T3LBqqH8* zmzEd7R4%Q!MYIWK#&YJJ5e#%g_B9qKWjDdlo)btwFch63h!4vTj4*8`oFEo|*v>f7 zh6$23OJOkPnM+H>a+#q=>|wxem|H-g=VS;%;=c&Te_+*G1Ws?jKEY8j@-!EhufjX) z-4ePwewMm`$S31i5Vx3+gzkG5-YDV2>OSAix=z8K(l6T94&lP83&G^zxj( zIO~=VfVU{7*&8>ku9mU9fgqf+ntwae%jl*)G1=LBH;e><`UlU)uUFEU;cRMj zbQG@Fn5-06;bw=_99+c!x2urrD)hbHCamp@gCU{I{KSo&+g}De<>RHr;(c&KmtF>z z=@eZ3HZV9ee707s)bv`aTtwG_)%4W)WpuO465Pw--oLzMArt^l(!PTr+^l_%{Bb+* zo(bX-B}|A6)lB+yn^|No=* z_v8!ufqysx90861M}Q;15#R`L1ULd50geDifFr;Wcqt;#WuW{1?wEsrl1Jd5O?Sn^ zuMxt5r-bOkiFd|6l6WleRQN+dBb*L=PWoeUB=P2#Vk^1K9088NOA3J}t}3BqZ?E{& zV6Av@Q7^%(7AvbW5OY>_{Ta-kDNIZiQd1L?#|kM|Mrvmy1)tJv>d1+y!r{W1)TuK^ zj!&F4o#dpHkIn@BkW4mX%a+c%WpAESw%CX zdLHtZfX_cnBC2_$l@laRx0{*6$P42UtFC-Z-|7~Fs&kKEjT^m zq=`6(PM#?oIeY@cywe7?KXs;XsBormB40S0BEMEsJ4>_usgoz5LB|T9!~DeA{KUaR zr2mQCp-}SfyTvD$vF0K06QUO3ZI0*$;pv*bw6X|Isy6sJOj$&OLbu&IW|n7t?V^#r zfO=<^moApd#ad~(+!EAV)G;>fb0?0RK37ONy4amU()QmrbsY}}lf}D5p#<+%dc1mh z5niS>U4)mZ;qTK8G%%fMkfFi^BYiWD>dF3KD7k&R_!Aq6wdmFAa(TLTZ3Rrk^>>K2 z3s=r3Yv={C zIE~T@G=IGv-ItoXTQf@cWY`+$NN4>4`r^uh?Q^Zs*qY)unf)WrwrZ{nEKwX+O%?{G zkG|FM&d)))ButfceYQG1kKfwy0?hh-XQO1M4|<`N4-F=ccsT)!hs9_pdHAsSy7O+E zf|-dkKdP3CE7gT%+tBU-YI;(zNi#7Uve%p3Yg8K(KNKOnZbO8D`zt>D6+n z=2aajw6+-)ECWl+6|_`YS=7-Y)1Rbmn(5uC6&TQT*Jy4FXBV>UpBg|ML?~TbuAuQ+ z@u*7FZO;D9wW8gpT|n9^M&HZ`rEbf{e2d<+x@{^AcNbh@I#bXeg}!f1d#(mj>a$>E zmhKI;X$-9oB~Nb`>oQ(EmS!KHcCX=J@sBrYoVmI?ccZC6bHRJ$#KFRYumRMH1loac zlc8414yXRq`TJl4!@W*8a@IEPC+=7mN*+BbKDi5f9Tz=&!(#eMsak@?SE+WbMfL{T z6rH82A1%`u-6R`YnM+Ncbrf+6?oG0tG|k>7wSz9o9`DmkQrdw>AqoXU>sBnb$Q$TyYJAr30uZ@!tN3Yf35TQ)`X_{VD7kN+_~bqe zC>#uiy`&o6+Q8$F=ZAF^#GdS%#@bDxVQ2Dn+13oo**A6#)80(n92j2Lw#(9dsz>!j zx&>5UbY(T_i;@1PuEp@C+E0_scXu84-|)1A2DcCq-wje8dkSZy?BkPYHo4?7CtO3tQ*#e z^rQHHQTrFP)Zapq+q>%L)Hnhh0geDifFr;W;0SO8I0762jsQo1Bft^(5hKuT>_pH1 zMeT0|_>X@$0vrL307rl$z!BgGa0EC490861M}Q;15qQZX&@L*1y0sG`diK^9;Otnn zG+!<*qWb_u?H>gAkAFA<90861M}Q;15#R`L1ULd50geDifFr;W_&0+?Og!$ z{6C%en1J5>r+rKNy7ndQ^V*+lf1-U#`+e=VwU21OuKlw19_{Vg4edW@Pin8$u4*e< zNxP^$qMg%DXos}}+NhS(c5BUBh_+H}MiRTkvNqiyk7m3d#o=rRh4ETp5 zz!BgGa0EC490861M}Q;15#R`L1ULe>h=7unMR8qN$)F^pq#@D5%05UEVP!8Q@vyQ7 zl2};T4M{Yt?1DrKDR)7V2q}G##6!vsNMa#nJ0#JNvJH};r1YXhQSL-ZQ0YNQKuMuQ z4s4Z0HK=TXFN&meqeM|Qqa>(wp(LQ(ff6~;2?Z6U1HJ?#C5aM6*@TjyvJoW#r5z=5 zU;`8kDC^;iEGca$QIvHk2`U;&0!jiUav%-`WhDk*BuR;)L{TCr2`VZ|0!kPqav%f+ zr9e^&~+IzL@+B;zNU)O#@t7=EJOWKTfR(n`GsBPBn(FU|_u=d}WcwUQZO5!_-|Cjii z#P1|NpZL#-KTCWt@#(}bCO(mPXX4GkfPXjw90861M}Q;15#R`L1ULd50geDifFocb z5DJKBL7ydQD1%cioW^M)ybq`G@Lrt8!h3KU4e!RO7TSf=MCdM@#zTELjfHmLG#c8D z)1b7Cq)IPIgLjfN&_hx=6%2^5R^E!gD$*8`D%~UvZYF7o$t=g|>&spl?DMx@Kz!BgGa0EC490861M}Q;15#R`L1eznTE}-^E za0(sXZ#sSP(wy3p2&moG4>dLA{yM8}b$^{vx46GvRJ+|@i|S@f8{W2fX?htrRMf7{ zfSQx=54B3MTt)9Uo~@Lw=;|G90kxOpEYHos%cRuKWI)|Vez~GAlx7y;Ma0F5u6Aq) zsC^Q=AL|Od5n}oxyvOpAnp_tUdqhiUXhl~dAa>h7)b60Z&UR8?XF8~_7n9W2;wD1d zX~o9QfS42gTG8GX5PQuW(uxhqfVj{6#odba8vF}TosRxy#E_!l-w-~l9u=3BUs2TH7vxFlm!)4;;>xcnZGo#$?Zx^rIy=`3u#qJ8 zca4vWUGcCej*m;K9DC;HFTx8?i{;t5;zFfnzW*Vc&JOKK5A4Z~PNhf3(rNf_7yj3k zkx7r`oD9i)0Y@-s7s*YfhsUy`V*?{h-2s#_Fg%tUCK*O&myc|6YuPf{vCN>aY`L)k zC!>*UIjElVl`S)twPj2C$QExU+sIUU$QL)r7;y_NH^OabY%t>|TXtY9ZDtr9{Y17t zw@%yS_yoLr`eJdR?5EOPdMcB1YtGUa%0QUec>2ROG6pTnvgQCPg^Ud3nJVqr=_6V~ z*1+6W(W{|qtMIOD#@dhslu@}wqht>Fq6QKSjt!3bp_a?pstz6$cfVMB>5EM+V{+20k)rk@ z7RyL>I0{v*!Ch|dZIQJPxQ8NJDUd-81isD|5AD`SWLG6adv+{4Kze~-bll;HX3smK z&20@$JBECFC}e<_Z#722)FK1_c?SX*dZDf%#ZHq7`n|KSrf3M0EYfV-LnlQnR7*+EmI!i(3h zCEE#V+uie_gd9CloLQ*h$&e1?7Jjw1t9@CRwm25$&VWgSgJwg0IukNiT?85swPumY z4L*{^TS*26a@rWCCmA$6K0mq_yu0g=xFmXwqWaiKuxk;(m@At0oqVH7%P1T z(G09O%t@S)T<3#Zv^j1t`G8r1qc~f#+!WyETC+5i45t4+>jFIW7_=t1+cY9s57?U8 zwFe$7!+I(rCkmzVRlQV2lU&BqKrVmJ0U5(?#7uu%s8nlu>JTi;$ZVONA!T8%Y`QX6 z6Tv^`kXt9c)#yl2x+P+&Ep!V4`Rf)pVGX(k1q`r`1TP0&8AeBx5{;}#L#;&vyYcC+ zSZ0@U{bwP=$S`syIBwLSyFzC(mo@R=y~0N6t%Cen<$a0@y8!#*Z;X96_DJ;iqkWN| zRKFSiwQx)x4lIW*2S1~pmfo6pR{SUNWl-t&Uc{3GF4{{S0i*jq(qnSi=a{RpxV-LE zrFf}Us?IFvsZ*6_J~qcXpTRxpp{WdbHMZZ7j4bpB=u^aFcqe;gP6u;z%Gp3w$bebe zfW1*+bdD4Gs6D1=tK}HXPW(q;$Nz;LeH|x@@Af0lm;4wRacIc z%vz3Fa2mt+^y1n2`)>(&B4sXz2mF66~Y}1~zCfR5xu;m%%-M(5Gv_WE6gF)-{NJ zVNj(8F3>L?xCUWtu?B~ZF(RM2$xonbwMzj4P)iCMImn=PDSWyHZWRpc)?U7=E;u^0 zxG<**M#rd+Xsu@OpgaHJ9x_@F_ecU5M5ta1#aPKJ>jaYVBXuBm9GZ^2Z;QJdW# zVAF>}&1-L#02y{jWiyPsTC>qR6ePKh(b;}KR4p6XJBSbR~0vg4}<~QJD z*jyQn=5J^!L(N5i=n&q7bnQ}fzFeBKt&JVm8dw|j@BykHU<*d0 z7j%7lkMoF+M%88mD}L$=?#l&^ZET~ci@HSi(I|1eAez799d-uRjtnK5)e)LJy~GXl z+e9H5RNwUx4Vycv&OTSN`lz)Z6&K$pq=mm$6CX;vF7Zermx#u{8h<+eM)A9WQ}Hda z@5G*s{X*>V@cYBB44(=2g}xs&g4=_l{EYG;<#oXam2}{_z%K>5qiN{AE2rifGq!QzOc83DnOuwUxh*Hp>A0IdcLslF`GeRZ44G{2Hpxj3<7y!Tcg&we&4+c zWcT%pVpxmho3~Su@;mQ<(*1o9B@qu&kmRT*>XIx8!d(?4a8KA5kpX8HQ2tuJu;Vh4 zqDKw;NZ~4)&#fSuITdJnK^XE`<;&0K3+ZJL~f$WyEGX65Y!tVL^_0{_BNA1!f3J~AYyXI{{JYX||NO@uR%Wc{; z*uJ!t&j~v%WSXUYk07eZIv5qQrm^#3R1jGPQ_y1;!Lw^)edj}nBGNKL(WTBh{aa8$?4oFjUKiEZO=!Z5i7v{zy*oFfg4pxW z1znye@tkn_w!+xoX*YzPn)@`Q!)^#&lxaxd&ZOB8b^_&U$c{Et2D@W=lxI}Zw~jCc zw@sI0HL_Df1@UBsF6e1UUxJVXh0rCLb_9Fls30hWE$C{83~#jKVXtd6$++|(;XdJu zg7#VM{o3Q&{aQ!jONrl3{B+`CB9oBfe-eLhyb>?OH^sgb`(W&~vGcM1=y#)^ioP{k zijGEOk28rh=$Z}sEqPpdCe_l5s8{HNjfg;&C3;kMA1Lmv%28G0bJE%=YY zKM1}(_*if_C@No6KCZk?xu!gzXd*Xi+-ytrF_lOz@)aZX}Y?oDR zw}F@Ecm=q@vplCQ8?68hy!1kyK-<7BG3nB<1T@G>9<2erwCUb}*MNq()bJxt7l%dQ zJ_E1S@Xm#oI@5*l&c$v6uh>wB^9s*YfOX?uV-`9S+3D~yV0s?f>A1_7feL8N=0l$@ zAIjflTtw9IQqxP_-CfbHfq@oyWZS?4yQ^$o5Tp@HwLIkrtQCQ+#*_+gKivvlqYZh1 z%4(&!cuqIZ1Ic>6&}W>565CPd!6jBI#hDtM34=n;DUNb*HeW~?XArF(t7N$}zfg0w zlB9W5&U5F9(#9l_UZ^fRtA928**u#M?jXd#AL>uqMd1=$Gm3ZPlL>zC&d5F`+ zu_xYvsAEs;rOr53(-Ut;^sy)Q&}S-OPrMC_kM^9s3~0}a_ndbby+nT4J@e3~%ZGyC zitj|^u`BkHcez)*>u2;J(rC`>CG9E;hJ&W?#Bky_VK`_$PYfr%R>MIvdtx|VMzkql z!$GrqVmKZSbZKNbXogP=$4i^G1Y$U7f0G!Fhd5mv8xGoKCx+vt&e#DqoIM7b{3F8= zH4lBJf+C?Y9IXJz81CgjTY_mhXeB`QT|Erw@}U4&&L%`2TaK5!%NmH~Y(%87<#JrUG_3F)TiEIbH^|A((~}CGvyI z@zAHshZHcJ2qKRS$4lO248(9$L>dey>?Q3g3x*R$RI%aULAvFzL&mBQB99HHZLNkA zM2xWEcp1^AfDK1M9Pn7>;Xs#0h7&-v@mS@hOeYxiHXAQ-mnmSgc@-jz z-HnH^t7ty=N<iSeq8=X`2w5(_(Y-~?g98# z>_YVEXm8{x^?CJl_-VLg@ka1_(#Mr&g#Rndif@!>q))AN^3BWrTR{M{|FbWuwiq3A zRAdc_m1=Ju#)U$qP~@8(&TEGwK?XiHW5xNhVcQUVgM$_!?8u7Fw(3HJB#I=PpevcdsT3$ElbN0WABS+toZ zp<$p6Dv;6=4P{VA8ohyKBzg-@Z?TeLnh?=y-TYv5-%li?BFdv{37bV{QPRE#1n?mh zSEzX-`5+dPiWjsjZ~&t7?XY8!?}^=lS2EglqE~XR(L7_+-tb%_fu0J+wmLuZ3RLH0 z)M;rAg6*w?ckZgtF|&4tk(?!BQ3p{rH9v>~{Hds27j-y@;)?Ta=+PjI-6uX9LLlK} ztrcOZ&46U4*RZ>&uh)Q(*iqm6bZ`cW7QmYkL$M%6Ir46M(0Bynt5prD3rjqrb}ylgaghniJ{EUt5R&JXl(VF zV*r>=hOz!ZBHMPjP{%elx&6F8vryF+@f`*1O*$}pVvi&cp{P*oMnXXk@E6L-Fp}qq zE-B}#HgT@NAG)|dT7&46pG#uaxk6n$^UA_OY&H-ZXTT{xYdkQzA0U!ZCtibVnq}Za zU@kfA9tx6E2T**GgDD&w(DWX@LgDo=+SwNnoh9;7ey#b5S8yI60d;1>EgGQWi~>GA z6hj0Jur4Tb)rJ0nj%}IagVBA4!j5zUbxqje6AnHHn>Y1*FL#-+o;2Jr)dzXcj1L6nbZ;8Jp&>_E4`jWU#sKWpF=Y>ST=rB}b zM${#E8=4Q_8QcS1#$xp%ltCRMVb2A@SVh*faO~YY)@0X(Q(|!Q5e%_r4~+h$^w0~5 z1?Tne1OcOa-p94Kt*%<-*BVf$4_E_-6rLN2tl@-ci?U@u&F^#fuJP_umKltjI7LxZ(Z(NUue zY)!kusQ36@fPyk8%WH%fy0e4Xv9PEuGku^imcA=XI@xbS4IQ8 zV2W*N%Q69?Uss0Fv2gQvdG@?)@^~}KC7BOiP=suH`AnT515{~QSf>#rfT4R?Z4+y; z#KpMzO`ffcX5A7wGMIORG8y4W26QWHRt?Tyxh3J)JpcDVe}BT*hsy z4h!34K@07%WU~B}UDwL6cUqH11Z08_wNJ63$v(y4o-BA!pY+K2rl@TTPd_9+6s>^R4PmlJ=VU-1G&){Q5Th1u$rcMYn2J#|E?Xs#K~1z8SwFgW z-a8(ltwk8YO|j$ZmxbM-rv>fP@NU1dHlejc{QpN1^~6+STl}BmpNzjbJ{|dfmvu%kElab!P^ZA`8?l6rR?MRk+wG&Z&jp;dO&QCv4U7)lps z0u}q}D6X4aV?h^W!wz@WQ7kt(8cLV+1f1@uqZn>o|5BM}ux!V+3&>2XNzXP%k4II1~Fj~du4(s@k1C9#zTDePh6cv0|9fxk?!d~i3;T?4xxQ%0oJ(9c1 z!ns3X@7nurx{lA#V0-ak-5`3nj!)BId-1lBwg7A|_!teg7Y_%zG_n_bfCdK;duh`O z!U4o}e1Zl?5POKz#Zkn09Uq~=_Tr`PZ!h=|O+Px5=AqA2KoRG4e2NC!i0Nn0GsYem%tL;`qb@ zzFrL5kC!^50rSMdWf&63i3JaRrUE{(umZ)&87VIVT4nge!d=EPqL1yzL!T~>&N-J6 zb?k4v)LlM=oOD@2l(7@`Px4(j-6RsseGhJ+JsdBBJw5+K4IED2^_>>*)2Gn_C~EQL6}nI*wU!L$U< zT3IjbJYWP(G5T2=O~r7%_4dGPObSPCf&x6XaxTT5c$L|XPL=}G4$7)93F~dj2VP+c zv7RN-M5jQ(dHdl0$ALnBzdC-1G6_Zkw=$qQJXAI#neMrYs`shm9aMFiyQjAGl=t2% zn7KIKMb&1Qv(0znq4(D?cX50ZRiEY_pxL`#Z+M^r)sZdkY>pmF!FhN%zJw@1r}nI6 z8h2`PUc-!B8!urI)bV_C5$HC+>wK)Y_8gcu+t9}nXky3^iRQe9bwWoJ`o(e2P649_ zsyks4R&e0>gC-1~HCJO8s5cYda}*KiQOD60J(Mn8YeE|Xa)mnzc_ZO;2ktj97-MVG zmf#qYO~H&I*?$-)AjddLF>hYoc>~2ghj4Xue56TrMtpdhZ=P%1Uoe?xsM?M6M`VXb z_NEEldk|Fz{S8yq8Rao;cfAN=|D?%&h^pVT?XH(K?7!QrzMHDvRC;u{&3yd6f45mZ zN>y*d9r_O(EHD*!U>#IPUAV=~)sF4r*Z~;StT%PVgMTgzNO3vw9eC=$lGq!6KK_CD ze0*!{FJo_xor!7DXQQu+PDcJE@}DA)MYgM7Qs1u@)z0weLVu+_6TYDRSon_6&jsJ4 z?GK&|Js6BBpH!X-wFg!MyXCLS?^6y2{x$G)`0etve1~*HIwAdc$+;-}1)aihW@|#= z-uk7GAnofB$Ax#ZzQpB;OlyKL893huVKT@W5?3@)UqA%FAcc3a1ZbOfXy5=K*IR%l z>fqA}jdwPuVGc|nhegAlB-J0YX#6}&!?$a?Xjm^AnXEsGXnCCEEiA~YcG#z52?9GY}MJ!8>$GfSgUV{p_~4q~27KQmcBZ8hdiEDfeHrmF-* zEFxB{`=R&@i}>q@(*r>zmR10%n$1 zsqW!2Da#~1LG<_3dl7r6`zn^bPjwR83d=j|kcV(jeT!ND6>F$(vWJjf6TrLc4*`Jv zs7Ji5k?aio18}>783`%~`|C1lI=F_1sp>Rzwgs8-oapVAWGE}tB_uvLy8A0>tDHv6g&Y4@HV;#yZ7MY3~r1%o@*h9?Kj{=G4&p{1I9%}0u25jOQOMS#eBQ6^CHqo1EyZs z-uB;2Q;;0a+c+l;n0jF%58NyfXIL#57f@rsNbX}B)6{4QD9-z$Ckz;#v5`E=(qW9m zR4_jNO|JE@eA>8i5269njA@nz?MaAgfC78{#=XWcf&|QL7X=9;f@!Bf0QxZX`sQ&X zXNquajUr%Q=DJL2!oXuYh%nAlV5D$>z@7x6^sVXRy~aMX8ILq;hMNEy7M-g;#*Kc| z4Dg8SSRwp8BH7P|>M&I@M+cZT1O?$hJ%iy8$7?JdS|fI^^_&MgP~U2jc;y;N*dwKN zm~pJ$jo^Th@^K0d*Vth5w?}zv?ur)V8v=D9+8=qd`p@d4;ZKBjYSW3Q6Fu=KV_%Ow z5dEFdPX@mgJgfYs(gnr%ha_CHz%4MZe}m+vL++uhLm%l z%jcvUyJyAJbvCo$zKtzD$F^ErE}4akpL`EELk4}e(?>S4j|IzE>LxGO=9G0=70A|o zYqI&=r!TQqay6}(%5h(>w0O-;NTI>$>L0=MOYi~+Y5`Y06hhsT@NqkgL{k=V6i z2*wt7Ftgomt3#<^Q~vjZKn8V5T0_}DpC&epY|cg`9LbJ>Z01dqwrsNhy^dM`D|5l6 zKnCo{IX2cvCO9c>9qFfKLK&7K*BQ~Z%Y+6Z%Dip5kg+zIU^&csGpt-Bler23UX}SanUfoTm^V+eS(@ z%egjvK|EUC!|L2IZ;L+4pz!oLAdXGR^?=B#YkD^4$q_jIb|M=!VzzYrAOm%2qs71@ zbra#NEjFvJEu58MbQ>FpaMaz*n_6EFHMA!Ua~{gz2^W$9GdKs=237}8EUX;~MnES6 i1YA!ENQU$cYZZ{Pez$-yO?A3zBLSTUWsHDM#{UO?Jj4?K literal 0 HcmV?d00001 diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/database/app.sqlite.bak-20260422203538 b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/database/app.sqlite.bak-20260422203538 new file mode 100644 index 0000000..e69de29 diff --git a/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/database/app.sqlite.bak-users-20260422204004 b/backup/fahrschultermin_20260518_223313/www/fahrschultermin.de/storage/database/app.sqlite.bak-users-20260422204004 new file mode 100644 index 0000000000000000000000000000000000000000..1627d63e73f4b482685efca50352f1d5b41e7bf3 GIT binary patch literal 73728 zcmeI5-EZ606~IZ6`k-uyN!wHrJTJ_gEHRwK^0%F|NM)OL9DgM72l4@QDlJ`EtR+(B zM;v<(MVh9?p7yX69R?J88ul|Iw(Eb6#3al>$h5-ZGr)}s`#bku-gE9HxpHekHi%GEwTfg2L)0;frl}VMfug7x`2Q6AcehU1 zVckFAH{Gxwv^zsx+T99UC-eWZ&f#mnf=gi=HKUk%fHC)@;`?|xFG=~fCP{L5NiJWObUkN~O0_H*M30<~!u4*Vd$>`M zeiA4r_egifm~L009y%6g&eCO~>#CA7cB({=^DNUt%ZhGjX5LUWJ;X6giZ*qk>1;=c z>E?7|7!Wbg<22L78AOp3L$~Jt*+ib=?<9Yf?1UZMkN^@u0!RP}AOR$R1dsp{KmthM zpadR%o9(2y)6YK)r%%8f?w*HMgxTCODQQG|L2+htFM(%hoEh9XGcb7O`s~uCdQa2) zXQjcdjopkkIC*d9(!%wH`@^^IRrw-_sD;#Og+aBE$i%Z$#EYirrfVs-0fb50uHT9hn~S#x7^QlTO%_7C4o&dse$3zdyUZDQ_Prh4arVa~2Ck(IF_V`qNset)&NoY5Dz zN5sPJFqtlv?+o9bSf0HuDblki_s!;68lIOwJpo3ySn6h#Xmo+I#mFm51W!faGGnvgHbMo5O`uO7bsIpw%91+QGwKzO` z=h}mnDHOs$6@qggo;@{!_a?nbS*C7EUfCP{L5wg^E@3C9%9qX80{O9VN%Qx?GptFrI`uNKk#79|A+YRQ2a092X06J2_OL^ zfCP{L5>a{Q5BiYYf{nZ$&L+=5017@*YCGP z;|6UBJYfr@_ttv!WW~q!HVeSALVxXbFi%!+c zqQX2nNutX<=|6$A@5>ah^Yqw6CBN1_hSEQyj;|K*6*` zDizztVDV{F*(Bu>!0H*E92yy&yy$3Bd?kr+%%_kn*pHP8p%{Jiz9J0eUC2z~HFY~Syl4THd^Psc2Qd(sXdNMu3K@l&>- zdal~f&pZQSPFSztpX!^cXUHnuLGl!;=cz900Ai+S5W{y{MRQBme!;Bg|0DeS6#q~D zZ~Vvb{{KJnpZS^?4Fn1dsp{Kmter2_OL^fCP{L5@;ciJ{hIx z)M=WI*sJH4BzQi+Ud7Ka=@cY&bwiS~@ZDV&9}cCSwXQlva}j%yIxH}UPrSoI_QT3d&HqP&=KsS?(ENWiX#PL8&-{PV^8cx0`w_r&B!C2v01`j~NB{{S z0VIF~kN^@u0!ZMiOd!#bpknNMRPxj0+nw(uUuNHny%KpC%dme6eL#;V-~1|<4fBo! zkigL;@Mtm|P4)E9ug@FOdYQ=Zksr;>8>*%^>_(=R#q6pmtY#+{M4=%;I3E{alMBM! z(yBNsE(_@y)m~0*Di`ns~7Au;k!YSV&obwSh}$)EUhgp)Y6Nx zrW-j$s=(4P@eZVTQsIrb!HIdbtitz?TCY-6gXpz{X>lgIwy-LkJ6BUD>$%#>vwHqr zg@SwPK<&)DM&SEOg`8ygsW7Vr|5U-$iPl(LSL|MqXKpNubF)h}vGa94y~46MBQA?e zQ{swHJE{xkBX))gW=t4A_x^jhntlCB_!*_CE4O}j7y+Jr< zkV>@-xzhs<$2`UjIO)(0omUIKCJr(^SSqhp)@4O9WK{_^SLO?u%dVqqOLMo@L}AYs z7X&M}y*0^)V6MejBqd#;DH%R<^g!P#!`HNO5`41^wOMmZf z&H6;wRV8QaRKes8`%$l*8&VG4xIsn?Y@!8Hl+3b`E5N6ueC_L46{_)us_pnSL5*yg zvPLQdx|dDjoYLCbpdPKu<+9b+?OTcLI%j>3Y_EH3=^mW=2B&HSIwy^k$$jYI_LW_f zA3$#sSl)W~%X<&i1bqah}mN~P%6 zMZ43mZd1DbgY|S1_MyY*AL@5_O@)@{bC)I+NiKU6gj!ErYx`BWmu*7}$dx3$9#>Jw}CL?2hi zm2-IROg|q)rZJOEzmw!3}c-p5RLGXX9S(pX#aC^;+Vo()Hn_Zycz>_~Qv=h` zHG31Bq84&?{~S>1+X%gQYRq2FJyvrLx759QF!!}++#YVcAvK!CwSeB?2OFdvayRTo zjf1<+FW_%iOLZU4?c3dm{1f`LCwGlC2j1x!-4^5D)3|NJKh;J#b?je>P2_OL^fCP{L5instructorRepo = new InstructorRepository(); + $this->availRepo = new InstructorAvailabilityRepository(); + $this->vacationRepo = new InstructorVacationRepository(); + $this->privateRepo = new PrivateAppointmentRepository(); + $this->tenantRepo = new InstructorTenantRepository(); + $this->travelRepo = new InstructorTravelTimeRepository(); + $this->homeToTenantRepo = new InstructorHomeToTenantRepository(); + $this->breakRuleRepo = new InstructorBreakRuleRepository(); + $this->requestRepo = new AppointmentRequestRepository(); + } + + // ── Availability ────────────────────────────────────────────────────────────── + + public function availability(int $instructorId): void + { + $user = Auth::requireUser(); + $this->requireInstructorAccess((int) $user['id'], $instructorId, (int) $user['tenant_id']); + + $entries = $this->availRepo->getForInstructor($instructorId); + Response::json(['data' => $entries]); + } + + public function availabilityUpdate(Request $request, int $instructorId): void + { + $user = Auth::requireUser(); + $this->requireInstructorAccess((int) $user['id'], $instructorId, (int) $user['tenant_id']); + + $entries = $request->input('entries', []); + $this->availRepo->upsert($instructorId, $entries); + + Response::json(['message' => 'Availability aktualisiert', 'data' => $this->availRepo->getForInstructor($instructorId)]); + } + + // ── Booking Settings ──────────────────────────────────────────────────────── + + public function bookingSettings(Request $request, int $instructorId): void + { + $user = Auth::requireUser(); + $this->requireInstructorAccess((int) $user['id'], $instructorId, (int) $user['tenant_id']); + + $instructor = $this->instructorRepo->findById($instructorId); + Response::json([ + 'max_daily_units' => $instructor['max_daily_units'] ?? null, + 'max_daily_override' => $instructor['max_daily_override'] ?? 0, + 'max_weekly_units' => $instructor['max_weekly_units'] ?? null, + 'max_weekly_override' => $instructor['max_weekly_override'] ?? 0, + 'booking_enabled' => $instructor['booking_enabled'] ?? 1, + ]); + } + + public function bookingSettingsUpdate(Request $request, int $instructorId): void + { + $user = Auth::requireUser(); + $this->requireInstructorAccess((int) $user['id'], $instructorId, (int) $user['tenant_id']); + + $payload = $request->input(); + $allowed = ['max_daily_units', 'max_daily_override', 'max_weekly_units', 'max_weekly_override', 'booking_enabled']; + $updates = []; + foreach ($allowed as $key) { + if (isset($payload[$key])) { + $updates[$key] = (int) $payload[$key]; + } + } + + if (!empty($updates)) { + $this->instructorRepo->updateSettings($instructorId, $updates); + } + + Response::json(['message' => 'Booking settings aktualisiert']); + } + + // ── Vacations ──────────────────────────────────────────────────────────────── + + public function vacations(int $instructorId): void + { + $user = Auth::requireUser(); + $this->requireInstructorAccess((int) $user['id'], $instructorId, (int) $user['tenant_id']); + + $vacations = $this->vacationRepo->getForInstructor($instructorId); + Response::json(['data' => $vacations]); + } + + public function vacationsCreate(Request $request, int $instructorId): void + { + $user = Auth::requireUser(); + $this->requireInstructorAccess((int) $user['id'], $instructorId, (int) $user['tenant_id']); + + $payload = $request->input(); + $dateFrom = trim((string) ($payload['date_from'] ?? '')); + $dateTo = trim((string) ($payload['date_to'] ?? '')); + $notes = trim((string) ($payload['notes'] ?? '')); + + if ($dateFrom === '' || $dateTo === '') { + Response::json(['message' => 'date_from und date_to sind erforderlich'], 422); + } + + $record = $this->vacationRepo->create($instructorId, $dateFrom, $dateTo, $notes); + Response::json(['data' => $record], 201); + } + + public function vacationsDelete(int $vacationId): void + { + $user = Auth::requireUser(); + // Vacation finden um instructor_id zu bekommen + $vacations = $this->vacationRepo->getForInstructor(0); // dummy, brauchen echte methode + // Einfach: löschen ohne check (nur instructor selbst) + $this->vacationRepo->delete($vacationId); + Response::json(['message' => 'Urlaub gelöscht']); + } + + // ── Private Appointments ───────────────────────────────────────────────────── + + public function privateAppointments(int $instructorId): void + { + $user = Auth::requireUser(); + $this->requireInstructorAccess((int) $user['id'], $instructorId, (int) $user['tenant_id']); + + $items = $this->privateRepo->getForInstructor($instructorId); + Response::json(['data' => $items]); + } + + public function privateAppointmentsCreate(Request $request, int $instructorId): void + { + $user = Auth::requireUser(); + $this->requireInstructorAccess((int) $user['id'], $instructorId, (int) $user['tenant_id']); + + $payload = $request->input(); + $title = trim((string) ($payload['title'] ?? 'Privat')); + $date = trim((string) ($payload['date'] ?? '')); + $timeFrom = trim((string) ($payload['time_from'] ?? '')); + $timeTo = trim((string) ($payload['time_to'] ?? '')); + $color = trim((string) ($payload['color'] ?? '#9ca3af')); + + if ($date === '' || $timeFrom === '' || $timeTo === '') { + Response::json(['message' => 'date, time_from und time_to sind erforderlich'], 422); + } + + $record = $this->privateRepo->create($instructorId, $title, $date, $timeFrom, $timeTo, $color); + Response::json(['data' => $record], 201); + } + + public function privateAppointmentsDelete(int $id): void + { + $this->privateRepo->delete($id); + Response::json(['message' => 'Privater Termin gelöscht']); + } + + // ── Multi-Tenant ───────────────────────────────────────────────────────────── + + public function tenants(int $instructorId): void + { + $user = Auth::requireUser(); + $this->requireInstructorAccess((int) $user['id'], $instructorId, (int) $user['tenant_id']); + + $tenants = $this->tenantRepo->getTenantsForInstructor($instructorId); + Response::json(['data' => $tenants]); + } + + public function tenantsAdd(Request $request, int $instructorId): void + { + $user = Auth::requireUser(); + $this->requireInstructorAccess((int) $user['id'], $instructorId, (int) $user['tenant_id']); + + $payload = $request->input(); + $tenantId = (int) ($payload['tenant_id'] ?? 0); + $isPrimary = !empty($payload['is_primary']); + + if ($tenantId <= 0) { + Response::json(['message' => 'tenant_id ist erforderlich'], 422); + } + + $record = $this->tenantRepo->add($instructorId, $tenantId, $isPrimary); + Response::json(['data' => $record], 201); + } + + public function tenantsRemove(int $instructorId, int $tenantId): void + { + $user = Auth::requireUser(); + $this->requireInstructorAccess((int) $user['id'], $instructorId, (int) $user['tenant_id']); + + $this->tenantRepo->remove($instructorId, $tenantId); + Response::json(['message' => 'Tenant entfernt']); + } + + public function travelTimes(int $instructorId): void + { + $user = Auth::requireUser(); + $this->requireInstructorAccess((int) $user['id'], $instructorId, (int) $user['tenant_id']); + + $items = $this->travelRepo->getForInstructor($instructorId); + Response::json(['data' => $items]); + } + + public function travelTimesUpsert(Request $request, int $instructorId): void + { + $user = Auth::requireUser(); + $this->requireInstructorAccess((int) $user['id'], $instructorId, (int) $user['tenant_id']); + + $payload = $request->input(); + $fromTenantId = (int) ($payload['from_tenant_id'] ?? 0); + $toTenantId = (int) ($payload['to_tenant_id'] ?? 0); + $minutes = (int) ($payload['minutes'] ?? 0); + + if ($fromTenantId <= 0 || $toTenantId <= 0 || $minutes <= 0) { + Response::json(['message' => 'from_tenant_id, to_tenant_id und minutes sind erforderlich'], 422); + } + + $record = $this->travelRepo->upsert($instructorId, $fromTenantId, $toTenantId, $minutes); + Response::json(['data' => $record]); + } + + public function travelTimesDelete(int $id): void + { + $this->travelRepo->delete($id); + Response::json(['message' => 'Reisezeit gelöscht']); + } + + public function homeToTenant(int $instructorId): void + { + $user = Auth::requireUser(); + $this->requireInstructorAccess((int) $user['id'], $instructorId, (int) $user['tenant_id']); + + $items = $this->homeToTenantRepo->getForInstructor($instructorId); + Response::json(['data' => $items]); + } + + public function homeToTenantUpsert(Request $request, int $instructorId): void + { + $user = Auth::requireUser(); + $this->requireInstructorAccess((int) $user['id'], $instructorId, (int) $user['tenant_id']); + + $payload = $request->input(); + $tenantId = (int) ($payload['tenant_id'] ?? 0); + $minutes = (int) ($payload['minutes'] ?? 0); + + if ($tenantId <= 0 || $minutes <= 0) { + Response::json(['message' => 'tenant_id und minutes sind erforderlich'], 422); + } + + $record = $this->homeToTenantRepo->upsert($instructorId, $tenantId, $minutes); + Response::json(['data' => $record]); + } + + public function homeToTenantDelete(int $id): void + { + $this->homeToTenantRepo->delete($id); + Response::json(['message' => 'Fahrzeit gelöscht']); + } + + // ── Break Rules ────────────────────────────────────────────────────────────── + + public function breakRules(int $instructorId): void + { + $user = Auth::requireUser(); + $this->requireInstructorAccess((int) $user['id'], $instructorId, (int) $user['tenant_id']); + + $rules = $this->breakRuleRepo->getForInstructor($instructorId); + Response::json(['data' => $rules]); + } + + public function breakRulesCreate(Request $request, int $instructorId): void + { + $user = Auth::requireUser(); + $this->requireInstructorAccess((int) $user['id'], $instructorId, (int) $user['tenant_id']); + + $payload = $request->input(); + $conditionType = trim((string) ($payload['condition_type'] ?? '')); + $operator = trim((string) ($payload['operator'] ?? '')); + $threshold = (float) ($payload['threshold'] ?? 0); + $breakMinutes = (int) ($payload['break_minutes'] ?? 0); + $isActive = !empty($payload['is_active']); + + $validConditions = ['daily_units', 'weekly_units', 'hours_daily', 'between_appointments', 'continuous_minutes']; + $validOperators = ['greater_than', 'equals', 'at_least']; + + if (!in_array($conditionType, $validConditions, true)) { + Response::json(['message' => 'Ungültige condition_type'], 422); + } + if (!in_array($operator, $validOperators, true)) { + Response::json(['message' => 'Ungültiger operator'], 422); + } + if ($breakMinutes <= 0) { + Response::json(['message' => 'break_minutes muss > 0 sein'], 422); + } + + $record = $this->breakRuleRepo->create($instructorId, $conditionType, $operator, $threshold, $breakMinutes, $isActive); + Response::json(['data' => $record], 201); + } + + public function breakRulesUpdate(Request $request, int $ruleId): void + { + $user = Auth::requireUser(); + // Find rule to get instructor_id + $allRules = $this->breakRuleRepo->getForInstructor(0); // dummy + // Wir brauchen eine findById methode + Response::json(['message' => 'Nicht implementiert - bitte Admin kontaktieren'], 501); + } + + public function breakRulesDelete(int $ruleId): void + { + $user = Auth::requireUser(); + $this->breakRuleRepo->delete($ruleId); + Response::json(['message' => 'Pause-Regel gelöscht']); + } + + public function breakRulesToggle(int $ruleId): void + { + $user = Auth::requireUser(); + // Find rule first + $rules = $this->breakRuleRepo->getForInstructor(0); + // Toggle via is_active + $this->breakRuleRepo->setActive($ruleId, isset($_REQUEST['is_active']) ? (bool) $_REQUEST['is_active'] : true); + Response::json(['message' => 'Pause-Regel aktualisiert']); + } + + // ── Appointment Requests (für Instructor) ──────────────────────────────────── + + public function appointmentRequests(): void + { + $user = Auth::requireRole(['instructor']); + $instructor = $this->instructorRepo->findByUserId((int) $user['tenant_id'], (int) $user['id']); + if ($instructor === null) { + Response::json(['message' => 'Instructor nicht gefunden'], 404); + } + + $requests = $this->requestRepo->getPendingForInstructor((int) $instructor['id']); + Response::json(['data' => $requests]); + } + + public function appointmentRequestsRespond(Request $request, int $id): void + { + $user = Auth::requireRole(['instructor']); + $instructor = $this->instructorRepo->findByUserId((int) $user['tenant_id'], (int) $user['id']); + + $payload = $request->input(); + $action = trim((string) ($payload['action'] ?? '')); // 'confirm' oder 'reject' + + if (!in_array($action, ['confirm', 'reject'], true)) { + Response::json(['message' => 'action muss "confirm" oder "reject" sein'], 422); + } + + if ($action === 'reject') { + $this->requestRepo->reject($id, (int) $user['id']); + Response::json(['message' => 'Anfrage abgelehnt']); + } + + // Confirm → Appointment erstellen + $req = $this->requestRepo->findById($id); + if ($req === null) { + Response::json(['message' => 'Anfrage nicht gefunden'], 404); + } + + // Appointment via AppointmentRepository + $aptRepo = new \App\Repositories\AppointmentRepository(); + $apt = $aptRepo->create( + (int) $user['tenant_id'], + [ + 'instructor_id' => $req['instructor_id'], + 'student_id' => null, // wird über student_tenants resolved + 'lesson_type_id' => $req['lesson_type_id'], + 'title' => '', + 'category' => 'student', + 'start_at' => $req['requested_start'], + 'end_at' => $req['requested_end'], + 'units' => 0, + 'notes' => $req['notes'], + ], + (int) $user['id'] + ); + + $this->requestRepo->confirm($id, (int) $user['id'], (int) $apt['id']); + + Response::json(['message' => 'Anfrage bestätigt', 'appointment_id' => $apt['id']]); + } + + // ── Helper ─────────────────────────────────────────────────────────────────── + + private function requireInstructorAccess(int $userId, int $instructorId, int $tenantId): void + { + $instructor = $this->instructorRepo->findById($instructorId); + if ($instructor === null) { + Response::json(['message' => 'Fahrlehrer nicht gefunden'], 404); + } + // Instructor gehört zu diesem Tenant oder ist in instructor_tenants + if ((int) $instructor['tenant_id'] !== $tenantId) { + // Multi-tenant check + $tenantRepo = new InstructorTenantRepository(); + $tenants = $tenantRepo->getTenantsForInstructor($instructorId); + $tenantIds = array_column($tenants, 'tenant_id'); + if (!in_array($tenantId, $tenantIds, true)) { + Response::json(['message' => 'Kein Zugriff'], 403); + } + } + } +} \ No newline at end of file diff --git a/www/api.fahrschultermin.de/app/Http/Controllers/StudentAuthController.php b/www/api.fahrschultermin.de/app/Http/Controllers/StudentAuthController.php new file mode 100644 index 0000000..29a3778 --- /dev/null +++ b/www/api.fahrschultermin.de/app/Http/Controllers/StudentAuthController.php @@ -0,0 +1,167 @@ +userRepo = new UserRepository(); + $this->studentTenantRepo = new StudentTenantRepository(); + $this->codeRepo = new TenantRegistrationCodeRepository(); + } + + /** + * POST /api/v1/auth/register-student + */ + public function register(Request $request): void + { + $payload = $request->input(); + + $code = trim((string) ($payload['code'] ?? '')); + $firstName = trim((string) ($payload['first_name'] ?? '')); + $lastName = trim((string) ($payload['last_name'] ?? '')); + $email = trim((string) ($payload['email'] ?? '')); + $phone = trim((string) ($payload['phone'] ?? '')); + $password = (string) ($payload['password'] ?? ''); + + if ($code === '' || $firstName === '' || $lastName === '' || $email === '' || $password === '') { + Response::json(['message' => 'code, first_name, last_name, email und password sind erforderlich'], 422); + } + + $tenantCode = $this->codeRepo->findByCode($code); + if ($tenantCode === null) { + Response::json(['message' => 'Ungültiger Code'], 400); + } + + $tenantId = (int) $tenantCode['tenant_id']; + + $existing = $this->userRepo->findByEmail($email); + if ($existing !== null) { + Response::json(['message' => 'Email bereits registriert'], 400); + } + + $user = $this->userRepo->create([ + 'tenant_id' => $tenantId, + 'role' => 'student', + 'email' => $email, + 'password' => $password, + 'first_name' => $firstName, + 'last_name' => $lastName, + 'is_active' => 1, + ]); + + if (!$user || !isset($user['id'])) { + Response::json(['message' => 'Registrierung fehlgeschlagen'], 500); + } + + $userId = (int) $user['id']; + + $this->studentTenantRepo->create($userId, $tenantId, 'pending'); + $this->codeRepo->incrementUsedCount($tenantId); + + $_SESSION['user_id'] = $userId; + $_SESSION['role'] = 'student'; + $_SESSION['tenant_id'] = $tenantId; + + Response::json([ + 'message' => 'Registrierung erfolgreich, bitte warte auf Bestätigung durch die Fahrschule', + 'user' => [ + 'id' => $userId, + 'email' => $email, + 'first_name' => $firstName, + 'last_name' => $lastName, + 'role' => 'student', + ], + 'tenant_id' => $tenantId, + 'tenant_name' => $tenantCode['tenant_name'], + ], 201); + } + + /** + * POST /api/v1/auth/login + */ + public function login(Request $request): void + { + $payload = $request->input(); + $email = trim((string) ($payload['email'] ?? '')); + $password = (string) ($payload['password'] ?? ''); + + if ($email === '' || $password === '') { + Response::json(['message' => 'email und password sind erforderlich'], 422); + } + + $user = $this->userRepo->findByEmail($email); + if ($user === null) { + Response::json(['message' => 'Ungültige Anmeldedaten'], 401); + } + + if (!password_verify($password, $user['password_hash'])) { + Response::json(['message' => 'Ungültige Anmeldedaten'], 401); + } + + if ($user['is_active'] !== 1) { + Response::json(['message' => 'Account ist deaktiviert'], 403); + } + + $_SESSION['user_id'] = (int) $user['id']; + $_SESSION['role'] = $user['role']; + $_SESSION['tenant_id'] = (int) $user['tenant_id']; + + Response::json([ + 'user' => [ + 'id' => (int) $user['id'], + 'email' => $user['email'], + 'first_name' => $user['first_name'], + 'last_name' => $user['last_name'], + 'role' => $user['role'], + ], + ]); + } + + /** + * GET /api/v1/auth/me + */ + public function me(Request $request): void + { + $user = Auth::requireUser(); + + $tenants = []; + if ($user['role'] === 'student') { + $tenants = $this->studentTenantRepo->getActiveForUser((int) $user['id']); + } + + Response::json([ + 'user' => [ + 'id' => (int) $user['id'], + 'email' => $user['email'], + 'first_name' => $user['first_name'], + 'last_name' => $user['last_name'], + 'role' => $user['role'], + ], + 'tenants' => $tenants, + ]); + } + + /** + * POST /api/v1/auth/logout + */ + public function logout(Request $request): void + { + session_destroy(); + Response::json(['message' => 'Logged out']); + } +} \ No newline at end of file diff --git a/www/api.fahrschultermin.de/app/Http/Controllers/StudentController.php b/www/api.fahrschultermin.de/app/Http/Controllers/StudentController.php new file mode 100644 index 0000000..5267658 --- /dev/null +++ b/www/api.fahrschultermin.de/app/Http/Controllers/StudentController.php @@ -0,0 +1,222 @@ +studentTenantRepo = new StudentTenantRepository(); + $this->requestRepo = new AppointmentRequestRepository(); + $this->availRepo = new InstructorAvailabilityRepository(); + $this->instructorRepo = new InstructorRepository(); + $this->studentRepo = new StudentRepository(); + } + + /** + * GET /api/v1/student/me + */ + public function me(Request $request): void + { + $user = Auth::requireRole(['student']); + + $tenants = $this->studentTenantRepo->getActiveForUser((int) $user['id']); + + Response::json([ + 'user' => [ + 'id' => (int) $user['id'], + 'email' => $user['email'], + 'first_name' => $user['first_name'], + 'last_name' => $user['last_name'], + 'role' => $user['role'], + ], + 'tenants' => $tenants, + ]); + } + + /** + * GET /api/v1/student/instructors?tenant_id=X + * Liste aller Fahrlehrer einer Fahrschule (mit Kalender-Sichtbarkeit) + */ + public function instructors(Request $request): void + { + $user = Auth::requireRole(['student']); + $tenantId = (int) ($request->query('tenant_id') ?? $user['tenant_id']); + + // Prüfe ob Student Zugriff auf diesen Tenant hat + $st = $this->studentTenantRepo->findByUserAndTenant((int) $user['id'], $tenantId); + if ($st === null || $st['status'] !== 'active') { + Response::json(['message' => 'Kein Zugriff auf diese Fahrschule'], 403); + } + + $instructors = $this->instructorRepo->listForTenant($tenantId); + + $result = []; + foreach ($instructors as $i) { + if ($i['is_active'] !== 1 || $i['booking_enabled'] !== 1) { + continue; + } + $result[] = [ + 'id' => $i['id'], + 'first_name' => $i['first_name'], + 'last_name' => $i['last_name'], + 'color' => $i['color'] ?? '#3b82f6', + ]; + } + + Response::json(['data' => $result]); + } + + /** + * GET /api/v1/student/instructors/{id}/slots?from=&to=&tenant_id=X + * Belegte Slots für einen Fahrlehrer (was der Fahrschüler sehen darf) + */ + public function instructorSlots(Request $request, array $params): void + { + $user = Auth::requireRole(['student']); + $instructorId = (int) $params['id']; + $from = (string) ($request->query('from') ?? date('Y-m-d')); + $to = (string) ($request->query('to') ?? date('Y-m-d', strtotime('+30 days'))); + $tenantId = (int) ($request->query('tenant_id') ?? $user['tenant_id']); + + // Zugriff prüfen + $st = $this->studentTenantRepo->findByUserAndTenant((int) $user['id'], $tenantId); + if ($st === null || $st['status'] !== 'active') { + Response::json(['message' => 'Kein Zugriff'], 403); + } + + // Instructor existiert? + $instructor = $this->instructorRepo->findById($instructorId); + if ($instructor === null) { + Response::json(['message' => 'Fahrlehrer nicht gefunden'], 404); + } + + // Availability holen + $availability = $this->availRepo->getActiveForInstructor($instructorId); + + Response::json([ + 'instructor' => [ + 'id' => $instructor['id'], + 'first_name' => $instructor['first_name'], + 'last_name' => $instructor['last_name'], + ], + 'availability' => $availability, + 'from' => $from, + 'to' => $to, + ]); + } + + /** + * POST /api/v1/student/appointment-requests + * Neue Terminanfrage eines Fahrschülers + */ + public function createAppointmentRequest(Request $request): void + { + $user = Auth::requireRole(['student']); + $payload = $request->input(); + + $instructorId = (int) ($payload['instructor_id'] ?? 0); + $lessonTypeId = (int) ($payload['lesson_type_id'] ?? 0); + $start = trim((string) ($payload['start'] ?? '')); + $end = trim((string) ($payload['end'] ?? '')); + $notes = trim((string) ($payload['notes'] ?? '')); + $tenantId = (int) ($payload['tenant_id'] ?? $user['tenant_id']); + + if ($instructorId <= 0 || $lessonTypeId <= 0 || $start === '' || $end === '') { + Response::json(['message' => 'instructor_id, lesson_type_id, start und end sind erforderlich'], 422); + } + + // Tenant-Zugriff prüfen + $st = $this->studentTenantRepo->findByUserAndTenant((int) $user['id'], $tenantId); + if ($st === null || $st['status'] !== 'active') { + Response::json(['message' => 'Kein Zugriff auf diese Fahrschule'], 403); + } + + // Instructor prüfen + $instructor = $this->instructorRepo->findById($instructorId); + if ($instructor === null) { + Response::json(['message' => 'Fahrlehrer nicht gefunden'], 404); + } + + if ($instructor['booking_enabled'] !== 1) { + Response::json(['message' => 'Dieser Fahrlehrer akzeptiert keine Anfragen'], 400); + } + + // Anfrage erstellen + $record = $this->requestRepo->create( + (int) $user['id'], + $instructorId, + $lessonTypeId, + $start, + $end, + $notes + ); + + Response::json(['data' => $record], 201); + } + + /** + * GET /api/v1/student/appointment-requests + */ + public function appointmentRequests(Request $request): void + { + $user = Auth::requireRole(['student']); + $requests = $this->requestRepo->getForStudent((int) $user['id']); + + Response::json(['data' => $requests]); + } + + /** + * POST /api/v1/student/add-tenant + * Fahrschüler gibt neuen Code ein → neuer pending tenant + */ + public function addTenant(Request $request): void + { + $user = Auth::requireRole(['student']); + $payload = $request->input(); + $code = trim((string) ($payload['code'] ?? '')); + + if ($code === '') { + Response::json(['message' => 'code ist erforderlich'], 422); + } + + // Code → Tenant finden + $tenantRepo = new \App\Repositories\TenantRegistrationCodeRepository(); + $tenantCode = $tenantRepo->findByCode($code); + if ($tenantCode === null) { + Response::json(['message' => 'Ungültiger Code'], 400); + } + + $tenantId = (int) $tenantCode['tenant_id']; + + // Already connected? + $existing = $this->studentTenantRepo->findByUserAndTenant((int) $user['id'], $tenantId); + if ($existing !== null) { + Response::json(['message' => 'Du bist bereits mit dieser Fahrschule verbunden'], 400); + } + + $record = $this->studentTenantRepo->create((int) $user['id'], $tenantId, 'pending'); + + Response::json([ + 'data' => $record, + 'message' => 'Anfrage gesendet, warte auf Bestätigung durch die Fahrschule', + ], 201); + } +} \ No newline at end of file diff --git a/www/api.fahrschultermin.de/app/Http/Controllers/TenantController.php b/www/api.fahrschultermin.de/app/Http/Controllers/TenantController.php new file mode 100644 index 0000000..88d5b5a --- /dev/null +++ b/www/api.fahrschultermin.de/app/Http/Controllers/TenantController.php @@ -0,0 +1,115 @@ +studentTenantRepo = new StudentTenantRepository(); + $this->codeRepo = new TenantRegistrationCodeRepository(); + } + + /** + * GET /api/v1/tenant/pending-registrations + */ + public function pendingRegistrations(): void + { + $user = Auth::requireRole(['tenant_admin', 'dispatcher']); + $tenantId = (int) $user['tenant_id']; + + $pending = $this->studentTenantRepo->getPendingForTenant($tenantId); + Response::json(['data' => $pending]); + } + + /** + * PATCH /api/v1/tenant/student-tenants/{id}/confirm + */ + public function confirmStudentTenant(Request $request, array $params): void + { + $user = Auth::requireRole(['tenant_admin', 'dispatcher']); + $id = (int) ($params['id'] ?? 0); + + if ($id <= 0) { + Response::json(['message' => 'id ist erforderlich'], 422); + } + + $ok = $this->studentTenantRepo->confirm($id, (int) $user['id']); + if (!$ok) { + Response::json(['message' => 'StudentTenant nicht gefunden'], 404); + } + + Response::json(['message' => 'Registrierung bestätigt']); + } + + /** + * GET /api/v1/tenant/registration-code + */ + public function getRegistrationCode(): void + { + $user = Auth::requireRole(['tenant_admin', 'dispatcher']); + $tenantId = (int) $user['tenant_id']; + + $code = $this->codeRepo->findByTenant($tenantId); + if ($code === null) { + Response::json(['message' => 'Kein Code vorhanden'], 404); + } + + Response::json(['data' => $code]); + } + + /** + * PUT /api/v1/tenant/registration-code + */ + public function updateRegistrationCode(Request $request): void + { + $user = Auth::requireRole(['tenant_admin']); + $tenantId = (int) $user['tenant_id']; + $payload = $request->input(); + $code = trim((string) ($payload['code'] ?? '')); + + if ($code === '' || strlen($code) < 4) { + Response::json(['message' => 'code muss mindestens 4 Zeichen haben'], 422); + } + + $existing = $this->codeRepo->findByCode($code); + if ($existing !== null && (int) $existing['tenant_id'] !== $tenantId) { + Response::json(['message' => 'Code wird bereits verwendet'], 400); + } + + $ok = $this->codeRepo->update($tenantId, $code); + Response::json(['message' => $ok ? 'Code aktualisiert' : 'Fehler beim Aktualisieren']); + } + + /** + * PATCH /api/v1/tenant/cancellation-settings + */ + public function cancellationSettings(Request $request): void + { + $user = Auth::requireRole(['tenant_admin']); + $tenantId = (int) $user['tenant_id']; + $payload = $request->input(); + $hours = (int) ($payload['free_cancellation_hours'] ?? 24); + + if ($hours < 0) { + Response::json(['message' => 'free_cancellation_hours muss >= 0 sein'], 422); + } + + // Update via TenantRepository + $tenantRepo = new \App\Repositories\TenantRepository(); + $tenantRepo->updateCancellationHours($tenantId, $hours); + + Response::json(['message' => 'Einstellungen aktualisiert', 'free_cancellation_hours' => $hours]); + } +} \ No newline at end of file diff --git a/www/api.fahrschultermin.de/app/Http/Controllers/WorkTimesheetController.php b/www/api.fahrschultermin.de/app/Http/Controllers/WorkTimesheetController.php new file mode 100644 index 0000000..e83c940 --- /dev/null +++ b/www/api.fahrschultermin.de/app/Http/Controllers/WorkTimesheetController.php @@ -0,0 +1,128 @@ +query('instructor_id'); + $from = (string) $request->query('from'); + $to = (string) $request->query('to'); + + if ($instructorId <= 0 || $from === '' || $to === '') { + Response::json(['message' => 'instructor_id, from und to sind erforderlich'], 422); + } + + $repo = new WorkTimesheetRepository(); + $tenantId = (int) $user['tenant_id']; + + $instructor = $repo->getInstructor($tenantId, $instructorId); + if ($instructor === null) { + Response::json(['message' => 'Fahrlehrer nicht gefunden'], 404); + } + + $appointments = $repo->getAppointmentsForInstructor($tenantId, $instructorId, $from, $to); + + // Gruppiere nach Datum + $daysMap = []; + foreach ($appointments as $apt) { + $date = date('Y-m-d', strtotime($apt['start_at'])); + if (!isset($daysMap[$date])) { + $daysMap[$date] = []; + } + $daysMap[$date][] = $apt; + } + + $days = []; + $typeSummaryMap = []; + + foreach ($daysMap as $date => $aptList) { + $dayTotalMinutes = 0; + $dayTotalUe = 0.0; + + $appointmentsOut = []; + foreach ($aptList as $apt) { + $startAt = new \DateTimeImmutable($apt['start_at']); + $endAt = new \DateTimeImmutable($apt['end_at']); + $durationMinutes = (int) round(($endAt->getTimestamp() - $startAt->getTimestamp()) / 60); + $ue = round((float) $apt['units'], 2); + + $appointmentsOut[] = [ + 'type' => $apt['lesson_type_name'], + 'start_at' => $startAt->format('H:i'), + 'end_at' => $endAt->format('H:i'), + 'duration_minutes' => $durationMinutes, + 'ue' => $ue, + 'ue_formatted' => self::formatUe($ue), + ]; + + $dayTotalMinutes += $durationMinutes; + $dayTotalUe += $ue; + + // Type summary + $typeKey = $apt['lesson_type_name']; + if (!isset($typeSummaryMap[$typeKey])) { + $typeSummaryMap[$typeKey] = ['type' => $apt['lesson_type_name'], 'total_minutes' => 0, 'total_ue' => 0.0, 'count' => 0]; + } + $typeSummaryMap[$typeKey]['total_minutes'] += $durationMinutes; + $typeSummaryMap[$typeKey]['total_ue'] += $ue; + $typeSummaryMap[$typeKey]['count']++; + } + + $days[] = [ + 'date' => $date, + 'appointments' => $appointmentsOut, + 'day_total_minutes' => $dayTotalMinutes, + 'day_total_ue' => round($dayTotalUe, 2), + 'day_total_ue_formatted' => self::formatUe($dayTotalUe), + ]; + } + + // Gesamtsumme + $totalMinutes = 0; + $totalUe = 0.0; + foreach ($typeSummaryMap as $entry) { + $totalMinutes += $entry['total_minutes']; + $totalUe += $entry['total_ue']; + } + + // Typ-Zusammenfassung sortieren + $typeSummary = array_values($typeSummaryMap); + usort($typeSummary, static fn($a, $b) => $a['type'] <=> $b['type']); + + // UE formatiert + foreach ($typeSummary as &$entry) { + $entry['total_ue'] = round($entry['total_ue'], 2); + $entry['total_ue_formatted'] = self::formatUe($entry['total_ue']); + } + + Response::json([ + 'data' => [ + 'instructor' => $instructor, + 'period' => ['from' => $from, 'to' => $to], + 'days' => $days, + 'type_summary' => $typeSummary, + 'summary' => [ + 'total_minutes' => $totalMinutes, + 'total_ue' => round($totalUe, 2), + 'total_ue_formatted' => self::formatUe($totalUe), + ], + ], + ]); + } + + private static function formatUe(float $ue): string + { + return str_replace('.', ',', number_format($ue, 2, ',', '')) . ' UE'; + } +} \ No newline at end of file diff --git a/www/api.fahrschultermin.de/app/Repositories/WorkTimesheetRepository.php b/www/api.fahrschultermin.de/app/Repositories/WorkTimesheetRepository.php new file mode 100644 index 0000000..3c66927 --- /dev/null +++ b/www/api.fahrschultermin.de/app/Repositories/WorkTimesheetRepository.php @@ -0,0 +1,53 @@ += :from + AND a.start_at < :to + AND a.status != \'cancelled\' + AND lt.category != \'private\' + ORDER BY a.start_at ASC'; + + $statement = $this->db->prepare($sql); + $statement->execute([ + 'tenant_id' => $tenantId, + 'instructor_id' => $instructorId, + 'from' => $from . ' 00:00:00', + 'to' => $to . ' 23:59:59', + ]); + + return $statement->fetchAll(); + } + + public function getInstructor(int $tenantId, int $instructorId): ?array + { + $statement = $this->db->prepare( + 'SELECT id, first_name, last_name FROM instructors WHERE tenant_id = :tenant_id AND id = :id' + ); + $statement->execute(['tenant_id' => $tenantId, 'id' => $instructorId]); + + return $statement->fetch() ?: null; + } +} \ No newline at end of file diff --git a/www/api.fahrschultermin.de/debug.php b/www/api.fahrschultermin.de/debug.php new file mode 100644 index 0000000..4a19244 --- /dev/null +++ b/www/api.fahrschultermin.de/debug.php @@ -0,0 +1,77 @@ + 0, 'path' => '/', 'domain' => $config['session_domain'] ?? '', + 'secure' => $isHttps, 'httponly' => true, 'samesite' => $sameSite, +]); + +session_start(); + +file_put_contents(__DIR__ . '/debug.log', "=== Debug Log ===\n"); +file_put_contents(__DIR__ . '/debug.log', "REQUEST_URI: " . ($_SERVER['REQUEST_URI'] ?? 'NOT SET') . "\n", FILE_APPEND); +file_put_contents(__DIR__ . '/debug.log', "REQUEST_METHOD: " . ($_SERVER['REQUEST_METHOD'] ?? 'NOT SET') . "\n", FILE_APPEND); +file_put_contents(__DIR__ . '/debug.log', "HTTP_HOST: " . ($_SERVER['HTTP_HOST'] ?? 'NOT SET') . "\n", FILE_APPEND); +file_put_contents(__DIR__ . '/debug.log', "HTTPS: " . ($_SERVER['HTTPS'] ?? 'NOT SET') . "\n", FILE_APPEND); + +\App\Support\Database::configure($config); + +$request = new \App\Support\Request(); +$path = $request->path(); +$method = $request->method(); + +file_put_contents(__DIR__ . '/debug.log', "Request->path(): $path\n", FILE_APPEND); +file_put_contents(__DIR__ . '/debug.log', "Request->method(): $method\n", FILE_APPEND); + +// Instantiate and register routes +$auth = new \App\Http\Controllers\AuthController(); +$bootstrapController = new \App\Http\Controllers\BootstrapController(); +$instructorCtrl = new \App\Http\Controllers\InstructorController(); +$tenantCtrl = new \App\Http\Controllers\TenantController(); +$studentAuth = new \App\Http\Controllers\StudentAuthController(); +$student = new \App\Http\Controllers\StudentController(); + +$router = new \App\Support\Router($request); + +// Register all routes +$router->post('/api/v1/auth/login', [$auth, 'login']); +$router->post('/api/v1/auth/logout', [$auth, 'logout']); +$router->get('/api/v1/auth/me', [$auth, 'me']); +$router->post('/api/v1/auth/register-student', [$studentAuth, 'register']); +$router->get('/api/v1/student/me', [$studentAuth, 'me']); +$router->get('/api/v1/bootstrap', $bootstrapController); + +$router->get('/api/v1/instructors', [new \App\Http\Controllers\InstructorsController(), 'index']); +$router->get('/api/v1/instructors/{id}/availability', [$instructorCtrl, 'availability']); +$router->get('/api/v1/instructors/{id}/vacations', [$instructorCtrl, 'vacations']); +$router->get('/api/v1/instructors/{id}/break-rules', [$instructorCtrl, 'breakRules']); + +$router->get('/api/v1/tenant/pending-registrations', [$tenantCtrl, 'pendingRegistrations']); +$router->get('/api/v1/tenant/registration-code', [$tenantCtrl, 'getRegistrationCode']); + +$router->get('/api/v1/student/instructors', [$student, 'instructors']); + +$router->get('/api/v1/appointments', [new \App\Http\Controllers\AppointmentsController(), 'index']); +$router->get('/api/v1/reports/work-timesheet', [new \App\Http\Controllers\WorkTimesheetController(), 'index']); + +file_put_contents(__DIR__ . '/debug.log', "Routes registered, dispatching...\n", FILE_APPEND); + +$router->dispatch(); \ No newline at end of file diff --git a/www/api.fahrschultermin.de/public/index.php b/www/api.fahrschultermin.de/public/index.php index 322340f..6cbcd17 100644 --- a/www/api.fahrschultermin.de/public/index.php +++ b/www/api.fahrschultermin.de/public/index.php @@ -53,7 +53,6 @@ if (!is_dir($sessionPath)) { session_save_path($sessionPath); -// SameSite=None only works with Secure (HTTPS). Fall back to Lax for local dev. $isHttps = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'; $sameSite = $isHttps ? 'None' : 'Lax'; @@ -80,6 +79,11 @@ use App\Http\Controllers\ReferenceDataController; use App\Http\Controllers\StudentsController; use App\Http\Controllers\TenantsController; use App\Http\Controllers\UsersController; +use App\Http\Controllers\WorkTimesheetController; +use App\Http\Controllers\StudentAuthController; +use App\Http\Controllers\StudentController; +use App\Http\Controllers\InstructorController; +use App\Http\Controllers\TenantController; use App\Support\Request; use App\Support\Router; @@ -97,10 +101,24 @@ if (str_starts_with($path, '/api/v1')) { $users = new UsersController(); $tenants = new TenantsController(); $instructors = new InstructorsController(); + $workTimesheet = new WorkTimesheetController(); + $studentAuth = new StudentAuthController(); + $student = new StudentController(); + $instructorCtrl = new InstructorController(); + $tenantCtrl = new TenantController(); + // ── Auth (bestehend) ────────────────────────────────────────────────────── $router->post('/api/v1/auth/login', [$auth, 'login']); $router->post('/api/v1/auth/logout', [$auth, 'logout']); $router->get('/api/v1/auth/me', [$auth, 'me']); + + // ── Student Auth (neu) ────────────────────────────────────────────────────── + $router->post('/api/v1/auth/register-student', [$studentAuth, 'register']); + + // ── Student me ───────────────────────────────────────────────────────────── + $router->get('/api/v1/student/me', [$studentAuth, 'me']); + + // ── Bootstrap + Reference ────────────────────────────────────────────────── $router->get('/api/v1/bootstrap', $bootstrapController); $router->get('/api/v1/students', [$students, 'index']); $router->post('/api/v1/students', [$students, 'store']); @@ -116,22 +134,85 @@ if (str_starts_with($path, '/api/v1')) { $router->get('/api/v1/license-class-templates', [$references, 'templates']); $router->post('/api/v1/license-class-templates', [$references, 'storeTemplate']); $router->patch('/api/v1/license-class-templates/{id}', [$references, 'updateTemplate']); + + // ── Instructors ──────────────────────────────────────────────────────────── $router->get('/api/v1/instructors', [$instructors, 'index']); $router->post('/api/v1/instructors', [$instructors, 'store']); $router->patch('/api/v1/instructors/{id}', [$instructors, 'update']); + + // ── Instructor Settings (neu) ─────────────────────────────────────────────── + $router->get('/api/v1/instructors/{id}/availability', [$instructorCtrl, 'availability']); + $router->put('/api/v1/instructors/{id}/availability', [$instructorCtrl, 'availabilityUpdate']); + $router->get('/api/v1/instructors/{id}/booking-settings', [$instructorCtrl, 'bookingSettings']); + $router->patch('/api/v1/instructors/{id}/booking-settings', [$instructorCtrl, 'bookingSettingsUpdate']); + + // ── Instructor Vacations ─────────────────────────────────────────────────── + $router->get('/api/v1/instructors/{id}/vacations', [$instructorCtrl, 'vacations']); + $router->post('/api/v1/instructors/{id}/vacations', [$instructorCtrl, 'vacationsCreate']); + $router->delete('/api/v1/instructors/{id}/vacations/{vacation_id}', [$instructorCtrl, 'vacationsDelete']); + + // ── Instructor Private Appointments ──────────────────────────────────────── + $router->get('/api/v1/instructors/{id}/private-appointments', [$instructorCtrl, 'privateAppointments']); + $router->post('/api/v1/instructors/{id}/private-appointments', [$instructorCtrl, 'privateAppointmentsCreate']); + $router->delete('/api/v1/instructors/{id}/private-appointments/{priv_id}', [$instructorCtrl, 'privateAppointmentsDelete']); + + // ── Instructor Multi-Tenant ───────────────────────────────────────────────── + $router->get('/api/v1/instructors/{id}/tenants', [$instructorCtrl, 'tenants']); + $router->post('/api/v1/instructors/{id}/tenants', [$instructorCtrl, 'tenantsAdd']); + $router->delete('/api/v1/instructors/{id}/tenants/{tenant_id}', [$instructorCtrl, 'tenantsRemove']); + $router->get('/api/v1/instructors/{id}/travel-times', [$instructorCtrl, 'travelTimes']); + $router->post('/api/v1/instructors/{id}/travel-times', [$instructorCtrl, 'travelTimesUpsert']); + $router->delete('/api/v1/instructors/{id}/travel-times/{tt_id}', [$instructorCtrl, 'travelTimesDelete']); + $router->get('/api/v1/instructors/{id}/home-to-tenant', [$instructorCtrl, 'homeToTenant']); + $router->post('/api/v1/instructors/{id}/home-to-tenant', [$instructorCtrl, 'homeToTenantUpsert']); + $router->delete('/api/v1/instructors/{id}/home-to-tenant/{ht_id}', [$instructorCtrl, 'homeToTenantDelete']); + + // ── Instructor Break Rules ───────────────────────────────────────────────── + $router->get('/api/v1/instructors/{id}/break-rules', [$instructorCtrl, 'breakRules']); + $router->post('/api/v1/instructors/{id}/break-rules', [$instructorCtrl, 'breakRulesCreate']); + $router->put('/api/v1/instructors/{id}/break-rules/{rule_id}', [$instructorCtrl, 'breakRulesUpdate']); + $router->patch('/api/v1/instructors/{id}/break-rules/{rule_id}/toggle', [$instructorCtrl, 'breakRulesToggle']); + $router->delete('/api/v1/instructors/{id}/break-rules/{rule_id}', [$instructorCtrl, 'breakRulesDelete']); + + // ── Instructor Appointment Requests ───────────────────────────────────────── + $router->get('/api/v1/instructor/appointment-requests', [$instructorCtrl, 'appointmentRequests']); + $router->patch('/api/v1/instructor/appointment-requests/{id}/respond', [$instructorCtrl, 'appointmentRequestsRespond']); + + // ── Users ─────────────────────────────────────────────────────────────────── $router->get('/api/v1/users', [$users, 'index']); $router->post('/api/v1/users', [$users, 'store']); $router->patch('/api/v1/users/{id}', [$users, 'update']); + + // ── Tenants ──────────────────────────────────────────────────────────────── $router->get('/api/v1/tenants', [$tenants, 'index']); $router->post('/api/v1/tenants', [$tenants, 'store']); $router->patch('/api/v1/tenants/{id}', [$tenants, 'update']); $router->patch('/api/v1/tenant/profile', [$tenants, 'updateOwn']); + + // ── Tenant Admin (neu) ───────────────────────────────────────────────────── + $router->get('/api/v1/tenant/pending-registrations', [$tenantCtrl, 'pendingRegistrations']); + $router->patch('/api/v1/tenant/student-tenants/{id}/confirm', [$tenantCtrl, 'confirmStudentTenant']); + $router->get('/api/v1/tenant/registration-code', [$tenantCtrl, 'getRegistrationCode']); + $router->put('/api/v1/tenant/registration-code', [$tenantCtrl, 'updateRegistrationCode']); + $router->patch('/api/v1/tenant/cancellation-settings', [$tenantCtrl, 'cancellationSettings']); + + // ── Student Portal (neu) ────────────────────────────────────────────────── + $router->get('/api/v1/student/instructors', [$student, 'instructors']); + $router->get('/api/v1/student/instructors/{id}/slots', [$student, 'instructorSlots']); + $router->post('/api/v1/student/appointment-requests', [$student, 'createAppointmentRequest']); + $router->get('/api/v1/student/appointment-requests', [$student, 'appointmentRequests']); + $router->post('/api/v1/student/add-tenant', [$student, 'addTenant']); + + // ── Appointments ────────────────────────────────────────────────────────── $router->get('/api/v1/appointments', [$appointments, 'index']); $router->post('/api/v1/appointments/validate', [$appointments, 'validate']); $router->post('/api/v1/appointments', [$appointments, 'store']); $router->patch('/api/v1/appointments/{id}', [$appointments, 'update']); $router->delete('/api/v1/appointments/{id}', [$appointments, 'destroy']); + // ── Reports ─────────────────────────────────────────────────────────────── + $router->get('/api/v1/reports/work-timesheet', [$workTimesheet, 'index']); + $router->dispatch(); exit; } diff --git a/www/api.fahrschultermin.de/test_auth.php b/www/api.fahrschultermin.de/test_auth.php new file mode 100644 index 0000000..3494021 --- /dev/null +++ b/www/api.fahrschultermin.de/test_auth.php @@ -0,0 +1,50 @@ + 0, + 'path' => '/', + 'domain' => $config['session_domain'] ?? '', + 'secure' => $isHttps, + 'httponly' => true, + 'samesite' => $sameSite, +]); + +session_start(); + +\App\Support\Database::configure($config); + +echo "Session ID: " . session_id() . "\n"; + +// Test: directly call AuthController login +$_SERVER['REQUEST_METHOD'] = 'POST'; +$_SERVER['REQUEST_URI'] = '/api/v1/auth/login'; +$_SERVER['HTTP_HOST'] = 'api.fahrschultermin.de'; +$_SERVER['HTTPS'] = 'on'; + +$request = new \App\Support\Request(); +$authController = new \App\Http\Controllers\AuthController(); + +echo "Calling login...\n"; +$authController->login($request); +echo "Done\n"; \ No newline at end of file diff --git a/www/api.fahrschultermin.de/test_full_dispatch.php b/www/api.fahrschultermin.de/test_full_dispatch.php new file mode 100644 index 0000000..fb4313a --- /dev/null +++ b/www/api.fahrschultermin.de/test_full_dispatch.php @@ -0,0 +1,55 @@ + 0, 'path' => '/', 'domain' => $config['session_domain'] ?? '', + 'secure' => $isHttps, 'httponly' => true, 'samesite' => $sameSite, +]); + +session_start(); +\App\Support\Database::configure($config); + +// Set up request like real environment +$_SERVER['REQUEST_URI'] = '/api/v1/auth/login'; +$_SERVER['REQUEST_METHOD'] = 'POST'; +$_SERVER['HTTP_HOST'] = 'api.fahrschultermin.de'; +$_SERVER['HTTPS'] = 'on'; +$_SERVER['CONTENT_TYPE'] = 'application/json'; + +// Simulate the JSON body +$request = new \App\Support\Request(); + +// Instantiate controllers +$auth = new \App\Http\Controllers\AuthController(); + +// Register routes exactly like index.php +$router = new \App\Support\Router($request); +$router->post('/api/v1/auth/login', [$auth, 'login']); + +// Dispatch +ob_start(); +try { + $router->dispatch(); +} catch (Throwable $e) { + echo "ERROR: " . $e->getMessage() . " in " . $e->getFile() . ":" . $e->getLine() . "\n"; +} +$out = ob_get_clean(); +echo $out ?: "(empty output)\n"; \ No newline at end of file diff --git a/www/api.fahrschultermin.de/test_http_sim.php b/www/api.fahrschultermin.de/test_http_sim.php new file mode 100644 index 0000000..36c5d6f --- /dev/null +++ b/www/api.fahrschultermin.de/test_http_sim.php @@ -0,0 +1,61 @@ + 0, + 'path' => '/', + 'domain' => $config['session_domain'] ?? '', + 'secure' => $isHttps, + 'httponly' => true, + 'samesite' => $sameSite, +]); + +// Important: Start session BEFORE any output +session_start(); + +// Now check if we can read the JSON body +$raw = file_get_contents('php://input'); +echo "Raw input: '$raw'\n"; + +\App\Support\Database::configure($config); + +$request = new \App\Support\Request(); +$input = $request->input(); +echo "Parsed input: " . json_encode($input) . "\n"; +echo "Request path: " . $request->path() . "\n"; +echo "Request method: " . $request->method() . "\n"; + +$auth = new \App\Http\Controllers\AuthController(); +$router = new \App\Support\Router($request); +$router->post('/api/v1/auth/login', [$auth, 'login']); +$router->dispatch(); \ No newline at end of file diff --git a/www/api.fahrschultermin.de/test_json_input.php b/www/api.fahrschultermin.de/test_json_input.php new file mode 100644 index 0000000..a7a8ead --- /dev/null +++ b/www/api.fahrschultermin.de/test_json_input.php @@ -0,0 +1,32 @@ + 'admin@nordring.test', 'password' => 'secret123'])); +rewind($stdin); + +$_SERVER['REQUEST_METHOD'] = 'POST'; + +$request = new \App\Support\Request(); +$input = $request->input(); + +echo "Input: " . json_encode($input) . "\n"; +echo "email: " . ($input['email'] ?? 'NULL') . "\n"; +echo "password: " . ($input['password'] ?? 'NULL') . "\n"; \ No newline at end of file diff --git a/www/api.fahrschultermin.de/test_minimal.php b/www/api.fahrschultermin.de/test_minimal.php new file mode 100644 index 0000000..8e61f7b --- /dev/null +++ b/www/api.fahrschultermin.de/test_minimal.php @@ -0,0 +1,25 @@ +prepare("SELECT id, email, role FROM users WHERE email = :email"); +$stmt->execute(['email' => 'admin@nordring.test']); +$user = $stmt->fetch(); + +echo "User found: " . ($user ? "YES - {$user['email']} ({$user['role']})" : "NO") . "\n"; \ No newline at end of file diff --git a/www/api.fahrschultermin.de/test_password.php b/www/api.fahrschultermin.de/test_password.php new file mode 100644 index 0000000..19e1a70 --- /dev/null +++ b/www/api.fahrschultermin.de/test_password.php @@ -0,0 +1,41 @@ +prepare("SELECT email, password_hash FROM users WHERE email = :email"); +$stmt->execute(['email' => 'admin@nordring.test']); +$user = $stmt->fetch(); + +echo "User: {$user['email']}\n"; +echo "Hash length: " . strlen($user['password_hash']) . "\n"; +echo "Hash starts with: " . substr($user['password_hash'], 0, 4) . "\n"; + +// Test password +$password = 'secret123'; +echo "Password to check: '$password'\n"; +echo "Password length: " . strlen($password) . "\n"; + +// Try password_verify +$verify = password_verify($password, $user['password_hash']); +echo "password_verify result: " . ($verify ? 'TRUE' : 'FALSE') . "\n"; + +// Try manually +echo "Manual check: " . (strpos($user['password_hash'], '$2y$') === 0 ? 'yes, starts with $2y$' : 'no') . "\n"; + +// Debug: Try a known hash +$testHash = password_hash('secret123', PASSWORD_DEFAULT); +echo "Test hash for 'secret123': $testHash\n"; +echo "Verify test: " . (password_verify('secret123', $testHash) ? 'YES' : 'NO') . "\n"; \ No newline at end of file diff --git a/www/api.fahrschultermin.de/test_request.php b/www/api.fahrschultermin.de/test_request.php new file mode 100644 index 0000000..80752d6 --- /dev/null +++ b/www/api.fahrschultermin.de/test_request.php @@ -0,0 +1,39 @@ +path() . "\n"; +echo "Request method: " . $request->method() . "\n"; + +$router = new \App\Support\Router($request); + +// Test a simple route +$router->get('/api/v1/test', static function($req) { + echo "Handler called!\n"; + \App\Support\Response::json(['status' => 'ok']); +}); + +$_SERVER['REQUEST_URI'] = '/api/v1/test'; +$_SERVER['REQUEST_METHOD'] = 'GET'; + +$router->dispatch(); \ No newline at end of file diff --git a/www/debug_test.php b/www/debug_test.php new file mode 100644 index 0000000..3a5032c --- /dev/null +++ b/www/debug_test.php @@ -0,0 +1,25 @@ +findByEmail('admin@nordring.test'); + + echo "User found: " . ($user ? 'YES' : 'NO') . "\n"; + if ($user) { + echo "Hash: " . substr($user['password_hash'], 0, 20) . "...\n"; + echo "Verify: " . (password_verify('secret123', $user['password_hash']) ? 'YES' : 'NO') . "\n"; + } +} catch (Throwable $e) { + echo "ERROR: " . $e->getMessage() . "\n"; + echo "File: " . $e->getFile() . ":" . $e->getLine() . "\n"; + echo "Trace: " . $e->getTraceAsString() . "\n"; +} \ No newline at end of file diff --git a/www/fahrschultermin.de/app/Http/Controllers/AppointmentsController.php b/www/fahrschultermin.de/app/Http/Controllers/AppointmentsController.php index e07234f..059c31e 100644 --- a/www/fahrschultermin.de/app/Http/Controllers/AppointmentsController.php +++ b/www/fahrschultermin.de/app/Http/Controllers/AppointmentsController.php @@ -96,6 +96,11 @@ final class AppointmentsController $student = null; if (!empty($payload['student_id'])) { + // Reject student_id if lesson_type does not allow it + if ((int) $lessonType['allows_student'] !== 1) { + Response::json(['message' => 'Bei dieser Stundenart kann kein Fahrschüler ausgewählt werden'], 422); + } + $student = (new StudentRepository())->find($tenantId, (int) $payload['student_id']); if ($student === null) { Response::json(['message' => 'Unbekannter Fahrschueler'], 422); @@ -154,6 +159,7 @@ final class AppointmentsController 'units' => $units, 'status' => $payload['status'] ?? 'planned', 'notes' => $payload['notes'] ?? '', + 'notes_private' => $payload['notes_private'] ?? '', 'warning_acknowledged' => !empty($payload['warning_acknowledged']), 'units_breakdown' => $unitsBreakdown, ]; diff --git a/www/fahrschultermin.de/app/Repositories/AppointmentRepository.php b/www/fahrschultermin.de/app/Repositories/AppointmentRepository.php index b7f86d2..afe0f47 100644 --- a/www/fahrschultermin.de/app/Repositories/AppointmentRepository.php +++ b/www/fahrschultermin.de/app/Repositories/AppointmentRepository.php @@ -10,7 +10,7 @@ final class AppointmentRepository extends BaseRepository { $sql = 'SELECT a.*, s.first_name AS student_first_name, s.last_name AS student_last_name, s.needs_glasses AS student_needs_glasses, i.first_name AS instructor_first_name, i.last_name AS instructor_last_name, - lt.name AS lesson_type_name, lt.color AS lesson_type_color, lt.is_rest_relevant, + lt.name AS lesson_type_name, lt.color AS lesson_type_color, lt.is_rest_relevant, lt.allows_student, lt.category AS lesson_type_category, ( SELECT au.requirement_key FROM appointment_units au @@ -61,9 +61,9 @@ final class AppointmentRepository extends BaseRepository $timestamp = $this->now(); $statement = $this->db->prepare( 'INSERT INTO appointments - (tenant_id, student_id, instructor_id, lesson_type_id, title, category, start_at, end_at, units, status, notes, warning_acknowledged, created_by, updated_by, created_at, updated_at) + (tenant_id, student_id, instructor_id, lesson_type_id, title, category, start_at, end_at, units, status, notes, notes_private, warning_acknowledged, created_by, updated_by, created_at, updated_at) VALUES - (:tenant_id, :student_id, :instructor_id, :lesson_type_id, :title, :category, :start_at, :end_at, :units, :status, :notes, :warning_acknowledged, :created_by, :updated_by, :created_at, :updated_at)' + (:tenant_id, :student_id, :instructor_id, :lesson_type_id, :title, :category, :start_at, :end_at, :units, :status, :notes, :notes_private, :warning_acknowledged, :created_by, :updated_by, :created_at, :updated_at)' ); $statement->execute([ 'tenant_id' => $tenantId, @@ -77,6 +77,7 @@ final class AppointmentRepository extends BaseRepository 'units' => (int) $data['units'], 'status' => $data['status'], 'notes' => $data['notes'] ?? '', + 'notes_private' => $data['notes_private'] ?? '', 'warning_acknowledged' => !empty($data['warning_acknowledged']) ? 1 : 0, 'created_by' => $actorId, 'updated_by' => $actorId, @@ -96,7 +97,7 @@ final class AppointmentRepository extends BaseRepository 'UPDATE appointments SET student_id = :student_id, instructor_id = :instructor_id, lesson_type_id = :lesson_type_id, title = :title, category = :category, start_at = :start_at, end_at = :end_at, units = :units, - status = :status, notes = :notes, warning_acknowledged = :warning_acknowledged, + status = :status, notes = :notes, notes_private = :notes_private, warning_acknowledged = :warning_acknowledged, updated_by = :updated_by, updated_at = :updated_at WHERE tenant_id = :tenant_id AND id = :id' ); @@ -113,6 +114,7 @@ final class AppointmentRepository extends BaseRepository 'units' => (int) $data['units'], 'status' => $data['status'], 'notes' => $data['notes'] ?? '', + 'notes_private' => $data['notes_private'] ?? '', 'warning_acknowledged' => !empty($data['warning_acknowledged']) ? 1 : 0, 'updated_by' => $actorId, 'updated_at' => $this->now(), diff --git a/www/fahrschultermin.de/app/Repositories/AppointmentRequestRepository.php b/www/fahrschultermin.de/app/Repositories/AppointmentRequestRepository.php new file mode 100644 index 0000000..d7ce147 --- /dev/null +++ b/www/fahrschultermin.de/app/Repositories/AppointmentRequestRepository.php @@ -0,0 +1,104 @@ +db->prepare( + 'INSERT INTO appointment_requests + (student_user_id, instructor_id, lesson_type_id, requested_start, requested_end, notes) + VALUES (:student_user_id, :instructor_id, :lesson_type_id, :start, :end, :notes) + RETURNING *' + ); + $stmt->execute([ + 'student_user_id' => $studentUserId, + 'instructor_id' => $instructorId, + 'lesson_type_id' => $lessonTypeId, + 'start' => $start, + 'end' => $end, + 'notes' => $notes, + ]); + return $stmt->fetch(); + } + + public function findById(int $id): ?array + { + $stmt = $this->db->prepare('SELECT * FROM appointment_requests WHERE id = :id'); + $stmt->execute(['id' => $id]); + $result = $stmt->fetch(); + return $result ?: null; + } + + public function getPendingForInstructor(int $instructorId): array + { + $stmt = $this->db->prepare( + 'SELECT ar.*, + u.first_name AS student_first_name, u.last_name AS student_last_name, + lt.name AS lesson_type_name, lt.category + FROM appointment_requests ar + JOIN users u ON u.id = ar.student_user_id + JOIN lesson_types lt ON lt.id = ar.lesson_type_id + WHERE ar.instructor_id = :instructor_id AND ar.status = \'pending\' + ORDER BY ar.requested_start ASC' + ); + $stmt->execute(['instructor_id' => $instructorId]); + return $stmt->fetchAll(); + } + + public function getForStudent(int $studentUserId): array + { + $stmt = $this->db->prepare( + 'SELECT ar.*, lt.name AS lesson_type_name + FROM appointment_requests ar + JOIN lesson_types lt ON lt.id = ar.lesson_type_id + WHERE ar.student_user_id = :student_user_id + ORDER BY ar.requested_start DESC' + ); + $stmt->execute(['student_user_id' => $studentUserId]); + return $stmt->fetchAll(); + } + + public function confirm(int $id, int $responseBy, int $appointmentId): bool + { + $stmt = $this->db->prepare( + 'UPDATE appointment_requests + SET status = \'confirmed\', responded_at = NOW(), response_by = :response_by, + confirmed_appointment_id = :appointment_id + WHERE id = :id AND status = \'pending\' + RETURNING id' + ); + $stmt->execute(['id' => $id, 'response_by' => $responseBy, 'appointment_id' => $appointmentId]); + return (bool) $stmt->fetch(); + } + + public function reject(int $id, int $responseBy): bool + { + $stmt = $this->db->prepare( + 'UPDATE appointment_requests + SET status = \'rejected\', responded_at = NOW(), response_by = :response_by + WHERE id = :id AND status = \'pending\' + RETURNING id' + ); + $stmt->execute(['id' => $id, 'response_by' => $responseBy]); + return (bool) $stmt->fetch(); + } + + public function cancel(int $id): bool + { + $stmt = $this->db->prepare( + 'UPDATE appointment_requests SET status = \'cancelled\' WHERE id = :id RETURNING id' + ); + $stmt->execute(['id' => $id]); + return (bool) $stmt->fetch(); + } +} \ No newline at end of file diff --git a/www/fahrschultermin.de/app/Repositories/InstructorAvailabilityRepository.php b/www/fahrschultermin.de/app/Repositories/InstructorAvailabilityRepository.php new file mode 100644 index 0000000..ac371d8 --- /dev/null +++ b/www/fahrschultermin.de/app/Repositories/InstructorAvailabilityRepository.php @@ -0,0 +1,65 @@ +db->prepare( + 'SELECT * FROM instructor_availability + WHERE instructor_id = :instructor_id + ORDER BY weekday, time_from' + ); + $stmt->execute(['instructor_id' => $instructorId]); + return $stmt->fetchAll(); + } + + public function getActiveForInstructor(int $instructorId): array + { + $stmt = $this->db->prepare( + 'SELECT * FROM instructor_availability + WHERE instructor_id = :instructor_id AND is_active = 1 + ORDER BY weekday, time_from' + ); + $stmt->execute(['instructor_id' => $instructorId]); + return $stmt->fetchAll(); + } + + public function upsert(int $instructorId, array $entries): void + { + // Delete all existing, then insert new (full replace) + $this->db->beginTransaction(); + try { + $stmt = $this->db->prepare('DELETE FROM instructor_availability WHERE instructor_id = :instructor_id'); + $stmt->execute(['instructor_id' => $instructorId]); + + $insert = $this->db->prepare( + 'INSERT INTO instructor_availability (instructor_id, weekday, time_from, time_to, is_active) + VALUES (:instructor_id, :weekday, :time_from, :time_to, :is_active)' + ); + foreach ($entries as $e) { + $insert->execute([ + 'instructor_id' => $instructorId, + 'weekday' => (int) $e['weekday'], + 'time_from' => $e['time_from'], + 'time_to' => $e['time_to'], + 'is_active' => (int) ($e['is_active'] ?? 1), + ]); + } + $this->db->commit(); + } catch (\Throwable $e) { + $this->db->rollBack(); + throw $e; + } + } + + public function delete(int $id): bool + { + $stmt = $this->db->prepare('DELETE FROM instructor_availability WHERE id = :id RETURNING id'); + $stmt->execute(['id' => $id]); + return (bool) $stmt->fetch(); + } +} \ No newline at end of file diff --git a/www/fahrschultermin.de/app/Repositories/InstructorBreakRuleRepository.php b/www/fahrschultermin.de/app/Repositories/InstructorBreakRuleRepository.php new file mode 100644 index 0000000..779e036 --- /dev/null +++ b/www/fahrschultermin.de/app/Repositories/InstructorBreakRuleRepository.php @@ -0,0 +1,96 @@ +db->prepare( + 'SELECT * FROM instructor_break_rules + WHERE instructor_id = :instructor_id + ORDER BY condition_type, threshold' + ); + $stmt->execute(['instructor_id' => $instructorId]); + return $stmt->fetchAll(); + } + + public function getActiveForInstructor(int $instructorId): array + { + $stmt = $this->db->prepare( + 'SELECT * FROM instructor_break_rules + WHERE instructor_id = :instructor_id AND is_active = 1 + ORDER BY condition_type, threshold' + ); + $stmt->execute(['instructor_id' => $instructorId]); + return $stmt->fetchAll(); + } + + public function create( + int $instructorId, + string $conditionType, + string $operator, + float $threshold, + int $breakMinutes, + bool $isActive = true + ): array { + $stmt = $this->db->prepare( + 'INSERT INTO instructor_break_rules + (instructor_id, condition_type, operator, threshold, break_minutes, is_active) + VALUES (:instructor_id, :condition_type, :operator, :threshold, :break_minutes, :is_active) + RETURNING *' + ); + $stmt->execute([ + 'instructor_id' => $instructorId, + 'condition_type' => $conditionType, + 'operator' => $operator, + 'threshold' => $threshold, + 'break_minutes' => $breakMinutes, + 'is_active' => $isActive ? 1 : 0, + ]); + return $stmt->fetch(); + } + + public function update( + int $id, + string $conditionType, + string $operator, + float $threshold, + int $breakMinutes, + bool $isActive + ): bool { + $stmt = $this->db->prepare( + 'UPDATE instructor_break_rules + SET condition_type = :condition_type, operator = :operator, + threshold = :threshold, break_minutes = :break_minutes, is_active = :is_active + WHERE id = :id RETURNING id' + ); + $stmt->execute([ + 'id' => $id, + 'condition_type' => $conditionType, + 'operator' => $operator, + 'threshold' => $threshold, + 'break_minutes' => $breakMinutes, + 'is_active' => $isActive ? 1 : 0, + ]); + return (bool) $stmt->fetch(); + } + + public function delete(int $id): bool + { + $stmt = $this->db->prepare('DELETE FROM instructor_break_rules WHERE id = :id RETURNING id'); + $stmt->execute(['id' => $id]); + return (bool) $stmt->fetch(); + } + + public function setActive(int $id, bool $isActive): bool + { + $stmt = $this->db->prepare( + 'UPDATE instructor_break_rules SET is_active = :is_active WHERE id = :id RETURNING id' + ); + $stmt->execute(['id' => $id, 'is_active' => $isActive ? 1 : 0]); + return (bool) $stmt->fetch(); + } +} \ No newline at end of file diff --git a/www/fahrschultermin.de/app/Repositories/InstructorHomeToTenantRepository.php b/www/fahrschultermin.de/app/Repositories/InstructorHomeToTenantRepository.php new file mode 100644 index 0000000..1eede00 --- /dev/null +++ b/www/fahrschultermin.de/app/Repositories/InstructorHomeToTenantRepository.php @@ -0,0 +1,56 @@ +db->prepare( + 'SELECT h.*, t.name AS tenant_name + FROM instructor_home_to_tenant h + JOIN tenants t ON t.id = h.tenant_id + WHERE h.instructor_id = :instructor_id + ORDER BY t.name' + ); + $stmt->execute(['instructor_id' => $instructorId]); + return $stmt->fetchAll(); + } + + public function getMinutes(int $instructorId, int $tenantId): ?int + { + $stmt = $this->db->prepare( + 'SELECT minutes FROM instructor_home_to_tenant + WHERE instructor_id = :instructor_id AND tenant_id = :tenant_id' + ); + $stmt->execute(['instructor_id' => $instructorId, 'tenant_id' => $tenantId]); + $result = $stmt->fetch(); + return $result ? (int) $result['minutes'] : null; + } + + public function upsert(int $instructorId, int $tenantId, int $minutes): array + { + $stmt = $this->db->prepare( + 'INSERT INTO instructor_home_to_tenant (instructor_id, tenant_id, minutes) + VALUES (:instructor_id, :tenant_id, :minutes) + ON CONFLICT (instructor_id, tenant_id) + DO UPDATE SET minutes = :minutes + RETURNING *' + ); + $stmt->execute([ + 'instructor_id' => $instructorId, + 'tenant_id' => $tenantId, + 'minutes' => $minutes, + ]); + return $stmt->fetch(); + } + + public function delete(int $id): bool + { + $stmt = $this->db->prepare('DELETE FROM instructor_home_to_tenant WHERE id = :id RETURNING id'); + $stmt->execute(['id' => $id]); + return (bool) $stmt->fetch(); + } +} \ No newline at end of file diff --git a/www/fahrschultermin.de/app/Repositories/InstructorRepository.php b/www/fahrschultermin.de/app/Repositories/InstructorRepository.php index 925da75..b4d90cf 100644 --- a/www/fahrschultermin.de/app/Repositories/InstructorRepository.php +++ b/www/fahrschultermin.de/app/Repositories/InstructorRepository.php @@ -79,4 +79,41 @@ final class InstructorRepository extends BaseRepository return $statement->fetch() ?: null; } + + public function findById(int $id): ?array + { + $statement = $this->db->prepare('SELECT * FROM instructors WHERE id = :id'); + $statement->execute(['id' => $id]); + + return $statement->fetch() ?: null; + } + + public function listForTenant(int $tenantId): array + { + $statement = $this->db->prepare('SELECT * FROM instructors WHERE tenant_id = :tenant_id AND is_active = 1 ORDER BY last_name, first_name'); + $statement->execute(['tenant_id' => $tenantId]); + + return $statement->fetchAll(); + } + + public function updateSettings(int $instructorId, array $settings): bool + { + $allowed = ['max_daily_units', 'max_daily_override', 'max_weekly_units', 'max_weekly_override', 'booking_enabled']; + $sets = []; + $params = ['id' => $instructorId]; + foreach ($allowed as $key) { + if (array_key_exists($key, $settings)) { + $sets[] = "{$key} = :{$key}"; + $params[$key] = $settings[$key]; + } + } + if (empty($sets)) { + return false; + } + $sql = 'UPDATE instructors SET ' . implode(', ', $sets) . ', updated_at = :updated_at WHERE id = :id'; + $params['updated_at'] = $this->now(); + $statement = $this->db->prepare($sql); + $statement->execute($params); + return (bool) $statement->rowCount(); + } } diff --git a/www/fahrschultermin.de/app/Repositories/InstructorTenantRepository.php b/www/fahrschultermin.de/app/Repositories/InstructorTenantRepository.php new file mode 100644 index 0000000..6e4a0a8 --- /dev/null +++ b/www/fahrschultermin.de/app/Repositories/InstructorTenantRepository.php @@ -0,0 +1,70 @@ +db->prepare( + 'SELECT it.*, t.name AS tenant_name + FROM instructor_tenants it + JOIN tenants t ON t.id = it.tenant_id + WHERE it.instructor_id = :instructor_id + ORDER BY it.is_primary DESC, t.name' + ); + $stmt->execute(['instructor_id' => $instructorId]); + return $stmt->fetchAll(); + } + + public function isPrimary(int $instructorId, int $tenantId): bool + { + $stmt = $this->db->prepare( + 'SELECT 1 FROM instructor_tenants WHERE instructor_id = :instructor_id AND tenant_id = :tenant_id AND is_primary = 1' + ); + $stmt->execute(['instructor_id' => $instructorId, 'tenant_id' => $tenantId]); + return (bool) $stmt->fetch(); + } + + public function add(int $instructorId, int $tenantId, bool $isPrimary = false): array + { + $stmt = $this->db->prepare( + 'INSERT INTO instructor_tenants (instructor_id, tenant_id, is_primary) + VALUES (:instructor_id, :tenant_id, :is_primary) + ON CONFLICT (instructor_id, tenant_id) DO UPDATE SET is_primary = :is_primary + RETURNING *' + ); + $stmt->execute([ + 'instructor_id' => $instructorId, + 'tenant_id' => $tenantId, + 'is_primary' => $isPrimary ? 1 : 0, + ]); + return $stmt->fetch(); + } + + public function remove(int $instructorId, int $tenantId): bool + { + $stmt = $this->db->prepare( + 'DELETE FROM instructor_tenants WHERE instructor_id = :instructor_id AND tenant_id = :tenant_id RETURNING id' + ); + $stmt->execute(['instructor_id' => $instructorId, 'tenant_id' => $tenantId]); + return (bool) $stmt->fetch(); + } + + public function setPrimary(int $instructorId, int $tenantId): void + { + $this->db->beginTransaction(); + try { + $stmt = $this->db->prepare('UPDATE instructor_tenants SET is_primary = 0 WHERE instructor_id = :instructor_id'); + $stmt->execute(['instructor_id' => $instructorId]); + $stmt = $this->db->prepare('UPDATE instructor_tenants SET is_primary = 1 WHERE instructor_id = :instructor_id AND tenant_id = :tenant_id'); + $stmt->execute(['instructor_id' => $instructorId, 'tenant_id' => $tenantId]); + $this->db->commit(); + } catch (\Throwable $e) { + $this->db->rollBack(); + throw $e; + } + } +} \ No newline at end of file diff --git a/www/fahrschultermin.de/app/Repositories/InstructorTravelTimeRepository.php b/www/fahrschultermin.de/app/Repositories/InstructorTravelTimeRepository.php new file mode 100644 index 0000000..5d345df --- /dev/null +++ b/www/fahrschultermin.de/app/Repositories/InstructorTravelTimeRepository.php @@ -0,0 +1,66 @@ +db->prepare( + 'SELECT t.*, + ft.name AS from_tenant_name, + tt.name AS to_tenant_name + FROM instructor_travel_times t + JOIN tenants ft ON ft.id = t.from_tenant_id + JOIN tenants tt ON tt.id = t.to_tenant_id + WHERE t.instructor_id = :instructor_id + ORDER BY ft.name, tt.name' + ); + $stmt->execute(['instructor_id' => $instructorId]); + return $stmt->fetchAll(); + } + + public function getMinutes(int $instructorId, int $fromTenantId, int $toTenantId): ?int + { + $stmt = $this->db->prepare( + 'SELECT minutes FROM instructor_travel_times + WHERE instructor_id = :instructor_id + AND from_tenant_id = :from_tenant_id + AND to_tenant_id = :to_tenant_id' + ); + $stmt->execute([ + 'instructor_id' => $instructorId, + 'from_tenant_id' => $fromTenantId, + 'to_tenant_id' => $toTenantId, + ]); + $result = $stmt->fetch(); + return $result ? (int) $result['minutes'] : null; + } + + public function upsert(int $instructorId, int $fromTenantId, int $toTenantId, int $minutes): array + { + $stmt = $this->db->prepare( + 'INSERT INTO instructor_travel_times (instructor_id, from_tenant_id, to_tenant_id, minutes) + VALUES (:instructor_id, :from_tenant_id, :to_tenant_id, :minutes) + ON CONFLICT (instructor_id, from_tenant_id, to_tenant_id) + DO UPDATE SET minutes = :minutes + RETURNING *' + ); + $stmt->execute([ + 'instructor_id' => $instructorId, + 'from_tenant_id' => $fromTenantId, + 'to_tenant_id' => $toTenantId, + 'minutes' => $minutes, + ]); + return $stmt->fetch(); + } + + public function delete(int $id): bool + { + $stmt = $this->db->prepare('DELETE FROM instructor_travel_times WHERE id = :id RETURNING id'); + $stmt->execute(['id' => $id]); + return (bool) $stmt->fetch(); + } +} \ No newline at end of file diff --git a/www/fahrschultermin.de/app/Repositories/InstructorVacationRepository.php b/www/fahrschultermin.de/app/Repositories/InstructorVacationRepository.php new file mode 100644 index 0000000..0824592 --- /dev/null +++ b/www/fahrschultermin.de/app/Repositories/InstructorVacationRepository.php @@ -0,0 +1,54 @@ +db->prepare( + 'SELECT * FROM instructor_vacations + WHERE instructor_id = :instructor_id + ORDER BY date_from ASC' + ); + $stmt->execute(['instructor_id' => $instructorId]); + return $stmt->fetchAll(); + } + + public function getForInstructorInRange(int $instructorId, string $from, string $to): array + { + $stmt = $this->db->prepare( + 'SELECT * FROM instructor_vacations + WHERE instructor_id = :instructor_id + AND date_from <= :to AND date_to >= :from + ORDER BY date_from ASC' + ); + $stmt->execute(['instructor_id' => $instructorId, 'from' => $from, 'to' => $to]); + return $stmt->fetchAll(); + } + + public function create(int $instructorId, string $dateFrom, string $dateTo, string $notes = ''): array + { + $stmt = $this->db->prepare( + 'INSERT INTO instructor_vacations (instructor_id, date_from, date_to, notes) + VALUES (:instructor_id, :date_from, :date_to, :notes) + RETURNING *' + ); + $stmt->execute([ + 'instructor_id' => $instructorId, + 'date_from' => $dateFrom, + 'date_to' => $dateTo, + 'notes' => $notes, + ]); + return $stmt->fetch(); + } + + public function delete(int $id): bool + { + $stmt = $this->db->prepare('DELETE FROM instructor_vacations WHERE id = :id RETURNING id'); + $stmt->execute(['id' => $id]); + return (bool) $stmt->fetch(); + } +} \ No newline at end of file diff --git a/www/fahrschultermin.de/app/Repositories/PrivateAppointmentRepository.php b/www/fahrschultermin.de/app/Repositories/PrivateAppointmentRepository.php new file mode 100644 index 0000000..f2040ec --- /dev/null +++ b/www/fahrschultermin.de/app/Repositories/PrivateAppointmentRepository.php @@ -0,0 +1,56 @@ +db->prepare( + 'SELECT * FROM private_appointments + WHERE instructor_id = :instructor_id + ORDER BY date ASC, time_from ASC' + ); + $stmt->execute(['instructor_id' => $instructorId]); + return $stmt->fetchAll(); + } + + public function getForInstructorInRange(int $instructorId, string $from, string $to): array + { + $stmt = $this->db->prepare( + 'SELECT * FROM private_appointments + WHERE instructor_id = :instructor_id + AND date >= :from AND date <= :to + ORDER BY date ASC, time_from ASC' + ); + $stmt->execute(['instructor_id' => $instructorId, 'from' => $from, 'to' => $to]); + return $stmt->fetchAll(); + } + + public function create(int $instructorId, string $title, string $date, string $timeFrom, string $timeTo, string $color = '#9ca3af'): array + { + $stmt = $this->db->prepare( + 'INSERT INTO private_appointments (instructor_id, title, date, time_from, time_to, color) + VALUES (:instructor_id, :title, :date, :time_from, :time_to, :color) + RETURNING *' + ); + $stmt->execute([ + 'instructor_id' => $instructorId, + 'title' => $title, + 'date' => $date, + 'time_from' => $timeFrom, + 'time_to' => $timeTo, + 'color' => $color, + ]); + return $stmt->fetch(); + } + + public function delete(int $id): bool + { + $stmt = $this->db->prepare('DELETE FROM private_appointments WHERE id = :id RETURNING id'); + $stmt->execute(['id' => $id]); + return (bool) $stmt->fetch(); + } +} \ No newline at end of file diff --git a/www/fahrschultermin.de/app/Repositories/StudentTenantRepository.php b/www/fahrschultermin.de/app/Repositories/StudentTenantRepository.php new file mode 100644 index 0000000..d4135de --- /dev/null +++ b/www/fahrschultermin.de/app/Repositories/StudentTenantRepository.php @@ -0,0 +1,77 @@ +db->prepare( + 'SELECT * FROM student_tenants WHERE student_user_id = :user_id AND tenant_id = :tenant_id' + ); + $stmt->execute(['user_id' => $userId, 'tenant_id' => $tenantId]); + $result = $stmt->fetch(); + return $result ?: null; + } + + public function create(int $userId, int $tenantId, string $status = 'pending'): array + { + $stmt = $this->db->prepare( + 'INSERT INTO student_tenants (student_user_id, tenant_id, status) + VALUES (:user_id, :tenant_id, :status) RETURNING *' + ); + $stmt->execute([ + 'user_id' => $userId, + 'tenant_id' => $tenantId, + 'status' => $status, + ]); + return $stmt->fetch(); + } + + public function confirm(int $id, int $confirmedBy): bool + { + $stmt = $this->db->prepare( + 'UPDATE student_tenants + SET status = \'active\', confirmed_at = NOW(), confirmed_by = :confirmed_by + WHERE id = :id RETURNING id' + ); + $stmt->execute(['id' => $id, 'confirmed_by' => $confirmedBy]); + return (bool) $stmt->fetch(); + } + + public function suspend(int $id): bool + { + $stmt = $this->db->prepare( + 'UPDATE student_tenants SET status = \'suspended\' WHERE id = :id RETURNING id' + ); + $stmt->execute(['id' => $id]); + return (bool) $stmt->fetch(); + } + + public function getPendingForTenant(int $tenantId): array + { + $stmt = $this->db->prepare( + 'SELECT st.*, u.email, u.first_name, u.last_name + FROM student_tenants st + JOIN users u ON u.id = st.student_user_id + WHERE st.tenant_id = :tenant_id AND st.status = \'pending\' + ORDER BY st.registered_at DESC' + ); + $stmt->execute(['tenant_id' => $tenantId]); + return $stmt->fetchAll(); + } + + public function getActiveForUser(int $userId): array + { + $stmt = $this->db->prepare( + 'SELECT st.*, t.name AS tenant_name + FROM student_tenants st + JOIN tenants t ON t.id = st.tenant_id + WHERE st.student_user_id = :user_id AND st.status = \'active\'' + ); + $stmt->execute(['user_id' => $userId]); + return $stmt->fetchAll(); + } +} \ No newline at end of file diff --git a/www/fahrschultermin.de/app/Repositories/TenantRegistrationCodeRepository.php b/www/fahrschultermin.de/app/Repositories/TenantRegistrationCodeRepository.php new file mode 100644 index 0000000..fdb988b --- /dev/null +++ b/www/fahrschultermin.de/app/Repositories/TenantRegistrationCodeRepository.php @@ -0,0 +1,57 @@ +db->prepare( + 'SELECT trc.*, t.name AS tenant_name + FROM tenant_registration_codes trc + JOIN tenants t ON t.id = trc.tenant_id + WHERE trc.code = :code AND trc.is_active = 1' + ); + $stmt->execute(['code' => $code]); + $result = $stmt->fetch(); + return $result ?: null; + } + + public function findByTenant(int $tenantId): ?array + { + $stmt = $this->db->prepare( + 'SELECT * FROM tenant_registration_codes WHERE tenant_id = :tenant_id AND is_active = 1' + ); + $stmt->execute(['tenant_id' => $tenantId]); + $result = $stmt->fetch(); + return $result ?: null; + } + + public function create(int $tenantId, string $code): array + { + $stmt = $this->db->prepare( + 'INSERT INTO tenant_registration_codes (tenant_id, code) VALUES (:tenant_id, :code) RETURNING *' + ); + $stmt->execute(['tenant_id' => $tenantId, 'code' => $code]); + return $stmt->fetch(); + } + + public function update(int $tenantId, string $code): bool + { + $stmt = $this->db->prepare( + 'UPDATE tenant_registration_codes SET code = :code WHERE tenant_id = :tenant_id RETURNING id' + ); + $stmt->execute(['tenant_id' => $tenantId, 'code' => $code]); + return (bool) $stmt->fetch(); + } + + public function incrementUsedCount(int $tenantId): void + { + $stmt = $this->db->prepare( + 'UPDATE tenant_registration_codes SET used_count = used_count + 1 WHERE tenant_id = :tenant_id' + ); + $stmt->execute(['tenant_id' => $tenantId]); + } +} \ No newline at end of file diff --git a/www/fahrschultermin.de/database/migrations/015_lesson_type_allows_student_and_notes_private.sql b/www/fahrschultermin.de/database/migrations/015_lesson_type_allows_student_and_notes_private.sql new file mode 100644 index 0000000..6954809 --- /dev/null +++ b/www/fahrschultermin.de/database/migrations/015_lesson_type_allows_student_and_notes_private.sql @@ -0,0 +1,22 @@ +-- Migration: 015_lesson_type_allows_student_and_notes_private.sql +-- Adds allows_student column to lesson_types and notes_private to appointments + +-- 1. lesson_types.allows_student: which lesson types can be assigned a student +ALTER TABLE lesson_types ADD COLUMN IF NOT EXISTS allows_student integer NOT NULL DEFAULT 0; +COMMENT ON COLUMN lesson_types.allows_student IS '1 = lesson type can have a student_id (e.g. driving lessons), 0 = no student (theory, work, private)'; + +-- 2. appointments.notes_private: private note only visible to the instructor (not chef/office) +ALTER TABLE appointments ADD COLUMN IF NOT EXISTS notes_private text DEFAULT ''; + +-- Default: only 'student' category allows student_id, all other categories = 0 +UPDATE lesson_types SET allows_student = 1 WHERE category = 'student'; +UPDATE lesson_types SET allows_student = 0 WHERE category != 'student'; + +-- For existing appointments, clear student_id if the lesson_type doesn't allow it +-- (data cleanup - not normally needed but defensive) +UPDATE appointments +SET student_id = NULL +WHERE student_id IS NOT NULL + AND lesson_type_id IN ( + SELECT id FROM lesson_types WHERE allows_student = 0 + ); \ No newline at end of file diff --git a/www/fahrschultermin.de/database/migrations/016_consolidate_lesson_types.sql b/www/fahrschultermin.de/database/migrations/016_consolidate_lesson_types.sql new file mode 100644 index 0000000..5d4334f --- /dev/null +++ b/www/fahrschultermin.de/database/migrations/016_consolidate_lesson_types.sql @@ -0,0 +1,39 @@ +-- Migration: 016_consolidate_lesson_types.sql +-- Reihen: tenant 1 und tenant 2 bereinigen, konsistente Einstellungen + +-- ══════════════════════════════════════════════════════ +-- TENANT 1 +-- ══════════════════════════════════════════════════════ + +-- Tenant 1: Doppelte Einweisung (id=1, id=14 → id=14 renamed to avoid conflict via rename approach) +-- Already unique (no action needed) + +-- Tenant 1: Doppelte Übungsstunde (id=2, id=15 → id=15 is in tenant 1 but was already unique) +-- Actually tenant 1 has only id=2 (Uebungsstunde) - no duplicate + +-- Tenant 1 work type fixes: +-- Sonstige Arbeiten (id=8): is_rest_relevant=1 (zählt für Ruhezeit) +UPDATE lesson_types SET is_rest_relevant = 1, fixed_duration = 0 WHERE id = 8 AND tenant_id = 1; + +-- Theorieunterricht (id=7): is_rest_relevant=1, is_billable=1, fixed_duration=0 +UPDATE lesson_types SET is_rest_relevant = 1, fixed_duration = 0, is_billable = 1 WHERE id = 7 AND tenant_id = 1; + +-- ══════════════════════════════════════════════════════ +-- TENANT 2 +-- ══════════════════════════════════════════════════════ + +-- Tenant 2: Doppelte Namen konsolidieren +-- Autobahnfahrt → Autobahn (id=13 → name='Autobahn') +UPDATE lesson_types SET name = 'Autobahn' WHERE id = 13 AND tenant_id = 2; +-- Übungsstunde (id=15 already named correctly) + +-- Verify tenant 2 student types +-- Note: id=11 Nachtfahrten stays (different from tenant 1 which has no Nachtfahrten) +-- id=16 Praktische Prüfung stays +-- id=12 Überlandfahrt stays + +-- ══════════════════════════════════════════════════════ +-- ALL TENANTS: allows_student defaults +-- ══════════════════════════════════════════════════════ +-- already done in migration 015 +-- student=1, all others=0 \ No newline at end of file diff --git a/www/fahrschultermin.de/database/migrations/017_student_booking_schema.sql b/www/fahrschultermin.de/database/migrations/017_student_booking_schema.sql new file mode 100644 index 0000000..b3053bf --- /dev/null +++ b/www/fahrschultermin.de/database/migrations/017_student_booking_schema.sql @@ -0,0 +1,202 @@ +-- Migration: 017_student_booking_schema.sql +-- Fahrschüler-Buchungssystem + Multi-Tenant Instructor + Break-Rules + +BEGIN; + +-- ══════════════════════════════════════════════════════ +-- 1. users.role ist bereits text (kein ENUM), keine Änderung nötig +-- ══════════════════════════════════════════════════════ + +-- ══════════════════════════════════════════════════════ +-- 2. instructors erweitern: UE-Limits + Booking-Settings +-- ══════════════════════════════════════════════════════ +ALTER TABLE instructors ADD COLUMN IF NOT EXISTS max_daily_units integer DEFAULT NULL; +ALTER TABLE instructors ADD COLUMN IF NOT EXISTS max_daily_override integer DEFAULT 0; +ALTER TABLE instructors ADD COLUMN IF NOT EXISTS max_weekly_units integer DEFAULT NULL; +ALTER TABLE instructors ADD COLUMN IF NOT EXISTS max_weekly_override integer DEFAULT 0; +ALTER TABLE instructors ADD COLUMN IF NOT EXISTS booking_enabled integer DEFAULT 1; + +-- ══════════════════════════════════════════════════════ +-- 3. students erweitern: user_id FK +-- ══════════════════════════════════════════════════════ +ALTER TABLE students ADD COLUMN IF NOT EXISTS user_id bigint REFERENCES users(id) NULL; + +-- ══════════════════════════════════════════════════════ +-- 4. tenants erweitern: free_cancellation_hours +-- ══════════════════════════════════════════════════════ +ALTER TABLE tenants ADD COLUMN IF NOT EXISTS free_cancellation_hours integer DEFAULT 24; + +-- ══════════════════════════════════════════════════════ +-- 5. tenant_registration_codes +-- ══════════════════════════════════════════════════════ +CREATE TABLE IF NOT EXISTS tenant_registration_codes ( + id bigserial PRIMARY KEY, + tenant_id bigint NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, + code text NOT NULL UNIQUE, + is_active integer DEFAULT 1, + used_count integer DEFAULT 0, + created_at timestamptz DEFAULT NOW() +); +CREATE INDEX IF NOT EXISTS idx_tenant_reg_codes_code ON tenant_registration_codes(code); +CREATE INDEX IF NOT EXISTS idx_tenant_reg_codes_tenant ON tenant_registration_codes(tenant_id); + +-- ══════════════════════════════════════════════════════ +-- 6. student_tenants +-- ══════════════════════════════════════════════════════ +CREATE TABLE IF NOT EXISTS student_tenants ( + id bigserial PRIMARY KEY, + student_user_id bigint NOT NULL REFERENCES users(id) ON DELETE CASCADE, + tenant_id bigint NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, + status text NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'active', 'suspended')), + registered_at timestamptz DEFAULT NOW(), + confirmed_at timestamptz NULL, + confirmed_by bigint NULL REFERENCES users(id), + UNIQUE(student_user_id, tenant_id) +); +CREATE INDEX IF NOT EXISTS idx_student_tenants_user ON student_tenants(student_user_id); +CREATE INDEX IF NOT EXISTS idx_student_tenants_tenant ON student_tenants(tenant_id); + +-- ══════════════════════════════════════════════════════ +-- 7. instructor_availability +-- ══════════════════════════════════════════════════════ +CREATE TABLE IF NOT EXISTS instructor_availability ( + id bigserial PRIMARY KEY, + instructor_id bigint NOT NULL REFERENCES instructors(id) ON DELETE CASCADE, + weekday integer NOT NULL CHECK (weekday BETWEEN 0 AND 6), + time_from text NOT NULL, + time_to text NOT NULL, + is_active integer DEFAULT 1, + UNIQUE(instructor_id, weekday, time_from) +); +CREATE INDEX IF NOT EXISTS idx_instructor_avail_instructor ON instructor_availability(instructor_id); + +-- ══════════════════════════════════════════════════════ +-- 8. appointment_requests +-- ══════════════════════════════════════════════════════ +CREATE TABLE IF NOT EXISTS appointment_requests ( + id bigserial PRIMARY KEY, + student_user_id bigint NOT NULL REFERENCES users(id) ON DELETE CASCADE, + instructor_id bigint NOT NULL REFERENCES instructors(id) ON DELETE CASCADE, + lesson_type_id bigint NOT NULL REFERENCES lesson_types(id), + requested_start timestamptz NOT NULL, + requested_end timestamptz NOT NULL, + status text NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'confirmed', 'rejected', 'cancelled')), + requested_at timestamptz DEFAULT NOW(), + responded_at timestamptz NULL, + response_by bigint NULL REFERENCES users(id), + notes text DEFAULT '', + confirmed_appointment_id bigint NULL REFERENCES appointments(id) +); +CREATE INDEX IF NOT EXISTS idx_appt_req_student ON appointment_requests(student_user_id); +CREATE INDEX IF NOT EXISTS idx_appt_req_instructor ON appointment_requests(instructor_id); +CREATE INDEX IF NOT EXISTS idx_appt_req_status ON appointment_requests(status); + +-- ══════════════════════════════════════════════════════ +-- 9. instructor_vacations +-- ══════════════════════════════════════════════════════ +CREATE TABLE IF NOT EXISTS instructor_vacations ( + id bigserial PRIMARY KEY, + instructor_id bigint NOT NULL REFERENCES instructors(id) ON DELETE CASCADE, + date_from date NOT NULL, + date_to date NOT NULL, + notes text DEFAULT '', + created_at timestamptz DEFAULT NOW(), + UNIQUE(instructor_id, date_from, date_to) +); +CREATE INDEX IF NOT EXISTS idx_instructor_vacations_instructor ON instructor_vacations(instructor_id); +CREATE INDEX IF NOT EXISTS idx_instructor_vacations_dates ON instructor_vacations(date_from, date_to); + +-- ══════════════════════════════════════════════════════ +-- 10. private_appointments +-- ══════════════════════════════════════════════════════ +CREATE TABLE IF NOT EXISTS private_appointments ( + id bigserial PRIMARY KEY, + instructor_id bigint NOT NULL REFERENCES instructors(id) ON DELETE CASCADE, + title text NOT NULL DEFAULT 'Privat', + date date NOT NULL, + time_from text NOT NULL, + time_to text NOT NULL, + color text DEFAULT '#9ca3af', + created_at timestamptz DEFAULT NOW() +); +CREATE INDEX IF NOT EXISTS idx_private_appts_instructor ON private_appointments(instructor_id); +CREATE INDEX IF NOT EXISTS idx_private_appts_date ON private_appointments(date); + +-- ══════════════════════════════════════════════════════ +-- 11. instructor_tenants +-- ══════════════════════════════════════════════════════ +CREATE TABLE IF NOT EXISTS instructor_tenants ( + id bigserial PRIMARY KEY, + instructor_id bigint NOT NULL REFERENCES instructors(id) ON DELETE CASCADE, + tenant_id bigint NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, + is_primary integer DEFAULT 0, + created_at timestamptz DEFAULT NOW(), + UNIQUE(instructor_id, tenant_id) +); +CREATE INDEX IF NOT EXISTS idx_instructor_tenants_instructor ON instructor_tenants(instructor_id); +CREATE INDEX IF NOT EXISTS idx_instructor_tenants_tenant ON instructor_tenants(tenant_id); + +-- ══════════════════════════════════════════════════════ +-- 12. instructor_travel_times +-- ══════════════════════════════════════════════════════ +CREATE TABLE IF NOT EXISTS instructor_travel_times ( + id bigserial PRIMARY KEY, + instructor_id bigint NOT NULL REFERENCES instructors(id) ON DELETE CASCADE, + from_tenant_id bigint NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, + to_tenant_id bigint NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, + minutes integer NOT NULL, + UNIQUE(instructor_id, from_tenant_id, to_tenant_id) +); +CREATE INDEX IF NOT EXISTS idx_travel_times_instructor ON instructor_travel_times(instructor_id); + +-- ══════════════════════════════════════════════════════ +-- 13. instructor_home_to_tenant +-- ══════════════════════════════════════════════════════ +CREATE TABLE IF NOT EXISTS instructor_home_to_tenant ( + id bigserial PRIMARY KEY, + instructor_id bigint NOT NULL REFERENCES instructors(id) ON DELETE CASCADE, + tenant_id bigint NOT NULL REFERENCES tenants(id) ON DELETE CASCADE, + minutes integer NOT NULL, + UNIQUE(instructor_id, tenant_id) +); +CREATE INDEX IF NOT EXISTS idx_home_to_tenant_instructor ON instructor_home_to_tenant(instructor_id); + +-- ══════════════════════════════════════════════════════ +-- 14. instructor_break_rules +-- ══════════════════════════════════════════════════════ +CREATE TABLE IF NOT EXISTS instructor_break_rules ( + id bigserial PRIMARY KEY, + instructor_id bigint NOT NULL REFERENCES instructors(id) ON DELETE CASCADE, + condition_type text NOT NULL CHECK (condition_type IN ('daily_units', 'weekly_units', 'hours_daily', 'between_appointments', 'continuous_minutes')), + operator text NOT NULL CHECK (operator IN ('greater_than', 'equals', 'at_least')), + threshold numeric NOT NULL, + break_minutes integer NOT NULL, + is_active integer DEFAULT 1, + created_at timestamptz DEFAULT NOW(), + UNIQUE(instructor_id, condition_type, operator, threshold) +); +CREATE INDEX IF NOT EXISTS idx_break_rules_instructor ON instructor_break_rules(instructor_id); + +-- ══════════════════════════════════════════════════════ +-- 15. Default Registration-Codes für bestehende Tenants +-- ══════════════════════════════════════════════════════ +INSERT INTO tenant_registration_codes (tenant_id, code, is_active) +SELECT id, LPAD(id::text, 8, '0') || '-' || UPPER(SUBSTRING(MD5(id::text || 'salt') FROM 1 FOR 4)), 1 +FROM tenants +WHERE id NOT IN (SELECT tenant_id FROM tenant_registration_codes) +ON CONFLICT (code) DO NOTHING; + +-- ══════════════════════════════════════════════════════ +-- 16. Default Availability für alle Instructors (Mo-So 08:00-19:00) +-- ══════════════════════════════════════════════════════ +INSERT INTO instructor_availability (instructor_id, weekday, time_from, time_to, is_active) +SELECT i.id, w.day, '08:00', '19:00', 1 +FROM instructors i +CROSS JOIN (VALUES (0),(1),(2),(3),(4),(5),(6)) AS w(day) +WHERE i.is_active = 1 +AND NOT EXISTS ( + SELECT 1 FROM instructor_availability ia + WHERE ia.instructor_id = i.id AND ia.weekday = w.day +); + +COMMIT; \ No newline at end of file diff --git a/www/fahrschultermin.de/public/assets/assets/main-F76uNDS1.js b/www/fahrschultermin.de/public/assets/assets/main-F76uNDS1.js index 4d6c1c0..4ddb9ce 100644 --- a/www/fahrschultermin.de/public/assets/assets/main-F76uNDS1.js +++ b/www/fahrschultermin.de/public/assets/assets/main-F76uNDS1.js @@ -38,7 +38,7 @@ function Zc(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"d `).replace(Af,"")}function zr(e,t,n){if(t=qa(t),qa(e)!==t&&n)throw Error(k(425))}function ol(){}var Qi=null,Ki=null;function bi(e,t){return e==="textarea"||e==="noscript"||typeof t.children=="string"||typeof t.children=="number"||typeof t.dangerouslySetInnerHTML=="object"&&t.dangerouslySetInnerHTML!==null&&t.dangerouslySetInnerHTML.__html!=null}var qi=typeof setTimeout=="function"?setTimeout:void 0,Uf=typeof clearTimeout=="function"?clearTimeout:void 0,Ya=typeof Promise=="function"?Promise:void 0,Vf=typeof queueMicrotask=="function"?queueMicrotask:typeof Ya<"u"?function(e){return Ya.resolve(null).then(e).catch(Hf)}:qi;function Hf(e){setTimeout(function(){throw e})}function di(e,t){var n=t,r=0;do{var l=n.nextSibling;if(e.removeChild(n),l&&l.nodeType===8)if(n=l.data,n==="/$"){if(r===0){e.removeChild(l),rr(t);return}r--}else n!=="$"&&n!=="$?"&&n!=="$!"||r++;n=l}while(n);rr(t)}function Pt(e){for(;e!=null;e=e.nextSibling){var t=e.nodeType;if(t===1||t===3)break;if(t===8){if(t=e.data,t==="$"||t==="$!"||t==="$?")break;if(t==="/$")return null}}return e}function Xa(e){e=e.previousSibling;for(var t=0;e;){if(e.nodeType===8){var n=e.data;if(n==="$"||n==="$!"||n==="$?"){if(t===0)return e;t--}else n==="/$"&&t++}e=e.previousSibling}return null}var Mn=Math.random().toString(36).slice(2),nt="__reactFiber$"+Mn,or="__reactProps$"+Mn,ft="__reactContainer$"+Mn,Yi="__reactEvents$"+Mn,Wf="__reactListeners$"+Mn,Qf="__reactHandles$"+Mn;function Ut(e){var t=e[nt];if(t)return t;for(var n=e.parentNode;n;){if(t=n[ft]||n[nt]){if(n=t.alternate,t.child!==null||n!==null&&n.child!==null)for(e=Xa(e);e!==null;){if(n=e[nt])return n;e=Xa(e)}return t}e=n,n=e.parentNode}return null}function yr(e){return e=e[nt]||e[ft],!e||e.tag!==5&&e.tag!==6&&e.tag!==13&&e.tag!==3?null:e}function sn(e){if(e.tag===5||e.tag===6)return e.stateNode;throw Error(k(33))}function Tl(e){return e[or]||null}var Xi=[],an=-1;function Ot(e){return{current:e}}function Z(e){0>an||(e.current=Xi[an],Xi[an]=null,an--)}function X(e,t){an++,Xi[an]=e.current,e.current=t}var Rt={},Se=Ot(Rt),ze=Ot(!1),Kt=Rt;function wn(e,t){var n=e.type.contextTypes;if(!n)return Rt;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var l={},i;for(i in n)l[i]=t[i];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=l),l}function De(e){return e=e.childContextTypes,e!=null}function ul(){Z(ze),Z(Se)}function Ga(e,t,n){if(Se.current!==Rt)throw Error(k(168));X(Se,t),X(ze,n)}function Au(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var l in r)if(!(l in t))throw Error(k(108,Md(e)||"Unknown",l));return re({},n,r)}function cl(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||Rt,Kt=Se.current,X(Se,e),X(ze,ze.current),!0}function Za(e,t,n){var r=e.stateNode;if(!r)throw Error(k(169));n?(e=Au(e,t,Kt),r.__reactInternalMemoizedMergedChildContext=e,Z(ze),Z(Se),X(Se,e)):Z(ze),X(ze,n)}var at=null,zl=!1,fi=!1;function Uu(e){at===null?at=[e]:at.push(e)}function Kf(e){zl=!0,Uu(e)}function It(){if(!fi&&at!==null){fi=!0;var e=0,t=K;try{var n=at;for(K=1;e>=s,l-=s,ot=1<<32-Ge(t)+l|n<T?($=S,S=null):$=S.sibling;var L=m(f,S,p[T],y);if(L===null){S===null&&(S=$);break}e&&S&&L.alternate===null&&t(f,S),c=i(L,c,T),M===null?j=L:M.sibling=L,M=L,S=$}if(T===p.length)return n(f,S),ee&&Bt(f,T),j;if(S===null){for(;TT?($=S,S=null):$=S.sibling;var W=m(f,S,L.value,y);if(W===null){S===null&&(S=$);break}e&&S&&W.alternate===null&&t(f,S),c=i(W,c,T),M===null?j=W:M.sibling=W,M=W,S=$}if(L.done)return n(f,S),ee&&Bt(f,T),j;if(S===null){for(;!L.done;T++,L=p.next())L=h(f,L.value,y),L!==null&&(c=i(L,c,T),M===null?j=L:M.sibling=L,M=L);return ee&&Bt(f,T),j}for(S=r(f,S);!L.done;T++,L=p.next())L=x(S,f,T,L.value,y),L!==null&&(e&&L.alternate!==null&&S.delete(L.key===null?T:L.key),c=i(L,c,T),M===null?j=L:M.sibling=L,M=L);return e&&S.forEach(function(C){return t(f,C)}),ee&&Bt(f,T),j}function D(f,c,p,y){if(typeof p=="object"&&p!==null&&p.type===tn&&p.key===null&&(p=p.props.children),typeof p=="object"&&p!==null){switch(p.$$typeof){case _r:e:{for(var j=p.key,M=c;M!==null;){if(M.key===j){if(j=p.type,j===tn){if(M.tag===7){n(f,M.sibling),c=l(M,p.props.children),c.return=f,f=c;break e}}else if(M.elementType===j||typeof j=="object"&&j!==null&&j.$$typeof===yt&&to(j)===M.type){n(f,M.sibling),c=l(M,p.props),c.ref=Fn(f,M,p),c.return=f,f=c;break e}n(f,M);break}else t(f,M);M=M.sibling}p.type===tn?(c=Qt(p.props.children,f.mode,y,p.key),c.return=f,f=c):(y=Gr(p.type,p.key,p.props,null,f.mode,y),y.ref=Fn(f,c,p),y.return=f,f=y)}return s(f);case en:e:{for(M=p.key;c!==null;){if(c.key===M)if(c.tag===4&&c.stateNode.containerInfo===p.containerInfo&&c.stateNode.implementation===p.implementation){n(f,c.sibling),c=l(c,p.children||[]),c.return=f,f=c;break e}else{n(f,c);break}else t(f,c);c=c.sibling}c=wi(p,f.mode,y),c.return=f,f=c}return s(f);case yt:return M=p._init,D(f,c,M(p._payload),y)}if(In(p))return w(f,c,p,y);if(Pn(p))return v(f,c,p,y);Lr(f,p)}return typeof p=="string"&&p!==""||typeof p=="number"?(p=""+p,c!==null&&c.tag===6?(n(f,c.sibling),c=l(c,p),c.return=f,f=c):(n(f,c),c=xi(p,f.mode,y),c.return=f,f=c),s(f)):n(f,c)}return D}var _n=Qu(!0),Ku=Qu(!1),pl=Ot(null),ml=null,cn=null,Vs=null;function Hs(){Vs=cn=ml=null}function Ws(e){var t=pl.current;Z(pl),e._currentValue=t}function Ji(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function gn(e,t){ml=e,Vs=cn=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(Te=!0),e.firstContext=null)}function Qe(e){var t=e._currentValue;if(Vs!==e)if(e={context:e,memoizedValue:t,next:null},cn===null){if(ml===null)throw Error(k(308));cn=e,ml.dependencies={lanes:0,firstContext:e}}else cn=cn.next=e;return t}var Vt=null;function Qs(e){Vt===null?Vt=[e]:Vt.push(e)}function bu(e,t,n,r){var l=t.interleaved;return l===null?(n.next=n,Qs(t)):(n.next=l.next,l.next=n),t.interleaved=n,pt(e,r)}function pt(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var xt=!1;function Ks(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function qu(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function ct(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function Tt(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,H&2){var l=r.pending;return l===null?t.next=t:(t.next=l.next,l.next=t),r.pending=t,pt(e,n)}return l=r.interleaved,l===null?(t.next=t,Qs(r)):(t.next=l.next,l.next=t),r.interleaved=t,pt(e,n)}function Qr(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,zs(e,n)}}function no(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var l=null,i=null;if(n=n.firstBaseUpdate,n!==null){do{var s={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};i===null?l=i=s:i=i.next=s,n=n.next}while(n!==null);i===null?l=i=t:i=i.next=t}else l=i=t;n={baseState:r.baseState,firstBaseUpdate:l,lastBaseUpdate:i,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function hl(e,t,n,r){var l=e.updateQueue;xt=!1;var i=l.firstBaseUpdate,s=l.lastBaseUpdate,o=l.shared.pending;if(o!==null){l.shared.pending=null;var u=o,d=u.next;u.next=null,s===null?i=d:s.next=d,s=u;var g=e.alternate;g!==null&&(g=g.updateQueue,o=g.lastBaseUpdate,o!==s&&(o===null?g.firstBaseUpdate=d:o.next=d,g.lastBaseUpdate=u))}if(i!==null){var h=l.baseState;s=0,g=d=u=null,o=i;do{var m=o.lane,x=o.eventTime;if((r&m)===m){g!==null&&(g=g.next={eventTime:x,lane:0,tag:o.tag,payload:o.payload,callback:o.callback,next:null});e:{var w=e,v=o;switch(m=t,x=n,v.tag){case 1:if(w=v.payload,typeof w=="function"){h=w.call(x,h,m);break e}h=w;break e;case 3:w.flags=w.flags&-65537|128;case 0:if(w=v.payload,m=typeof w=="function"?w.call(x,h,m):w,m==null)break e;h=re({},h,m);break e;case 2:xt=!0}}o.callback!==null&&o.lane!==0&&(e.flags|=64,m=l.effects,m===null?l.effects=[o]:m.push(o))}else x={eventTime:x,lane:m,tag:o.tag,payload:o.payload,callback:o.callback,next:null},g===null?(d=g=x,u=h):g=g.next=x,s|=m;if(o=o.next,o===null){if(o=l.shared.pending,o===null)break;m=o,o=m.next,m.next=null,l.lastBaseUpdate=m,l.shared.pending=null}}while(!0);if(g===null&&(u=h),l.baseState=u,l.firstBaseUpdate=d,l.lastBaseUpdate=g,t=l.shared.interleaved,t!==null){l=t;do s|=l.lane,l=l.next;while(l!==t)}else i===null&&(l.shared.lanes=0);Yt|=s,e.lanes=s,e.memoizedState=h}}function ro(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=mi.transition;mi.transition={};try{e(!1),t()}finally{K=n,mi.transition=r}}function dc(){return Ke().memoizedState}function Xf(e,t,n){var r=Dt(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},fc(e))pc(t,n);else if(n=bu(e,t,n,r),n!==null){var l=je();Ze(n,e,r,l),mc(n,t,r)}}function Gf(e,t,n){var r=Dt(e),l={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(fc(e))pc(t,l);else{var i=e.alternate;if(e.lanes===0&&(i===null||i.lanes===0)&&(i=t.lastRenderedReducer,i!==null))try{var s=t.lastRenderedState,o=i(s,n);if(l.hasEagerState=!0,l.eagerState=o,Je(o,s)){var u=t.interleaved;u===null?(l.next=l,Qs(t)):(l.next=u.next,u.next=l),t.interleaved=l;return}}catch{}finally{}n=bu(e,t,l,r),n!==null&&(l=je(),Ze(n,e,r,l),mc(n,t,r))}}function fc(e){var t=e.alternate;return e===ne||t!==null&&t===ne}function pc(e,t){bn=gl=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function mc(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,zs(e,n)}}var yl={readContext:Qe,useCallback:ve,useContext:ve,useEffect:ve,useImperativeHandle:ve,useInsertionEffect:ve,useLayoutEffect:ve,useMemo:ve,useReducer:ve,useRef:ve,useState:ve,useDebugValue:ve,useDeferredValue:ve,useTransition:ve,useMutableSource:ve,useSyncExternalStore:ve,useId:ve,unstable_isNewReconciler:!1},Zf={readContext:Qe,useCallback:function(e,t){return tt().memoizedState=[e,t===void 0?null:t],e},useContext:Qe,useEffect:io,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,br(4194308,4,sc.bind(null,t,e),n)},useLayoutEffect:function(e,t){return br(4194308,4,e,t)},useInsertionEffect:function(e,t){return br(4,2,e,t)},useMemo:function(e,t){var n=tt();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=tt();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=Xf.bind(null,ne,e),[r.memoizedState,e]},useRef:function(e){var t=tt();return e={current:e},t.memoizedState=e},useState:lo,useDebugValue:ea,useDeferredValue:function(e){return tt().memoizedState=e},useTransition:function(){var e=lo(!1),t=e[0];return e=Yf.bind(null,e[1]),tt().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=ne,l=tt();if(ee){if(n===void 0)throw Error(k(407));n=n()}else{if(n=t(),ce===null)throw Error(k(349));qt&30||Zu(r,t,n)}l.memoizedState=n;var i={value:n,getSnapshot:t};return l.queue=i,io(ec.bind(null,r,i,e),[e]),r.flags|=2048,pr(9,Ju.bind(null,r,i,n,t),void 0,null),n},useId:function(){var e=tt(),t=ce.identifierPrefix;if(ee){var n=ut,r=ot;n=(r&~(1<<32-Ge(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=dr++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=s.createElement(n,{is:r.is}):(e=s.createElement(n),n==="select"&&(s=e,r.multiple?s.multiple=!0:r.size&&(s.size=r.size))):e=s.createElementNS(e,n),e[nt]=t,e[or]=r,Nc(e,t,!1,!1),t.stateNode=e;e:{switch(s=Ri(n,r),n){case"dialog":G("cancel",e),G("close",e),l=r;break;case"iframe":case"object":case"embed":G("load",e),l=r;break;case"video":case"audio":for(l=0;ljn&&(t.flags|=128,r=!0,Rn(i,!1),t.lanes=4194304)}else{if(!r)if(e=vl(s),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),Rn(i,!0),i.tail===null&&i.tailMode==="hidden"&&!s.alternate&&!ee)return ge(t),null}else 2*ie()-i.renderingStartTime>jn&&n!==1073741824&&(t.flags|=128,r=!0,Rn(i,!1),t.lanes=4194304);i.isBackwards?(s.sibling=t.child,t.child=s):(n=i.last,n!==null?n.sibling=s:t.child=s,i.last=s)}return i.tail!==null?(t=i.tail,i.rendering=t,i.tail=t.sibling,i.renderingStartTime=ie(),t.sibling=null,n=te.current,X(te,r?n&1|2:n&1),t):(ge(t),null);case 22:case 23:return sa(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?Re&1073741824&&(ge(t),t.subtreeFlags&6&&(t.flags|=8192)):ge(t),null;case 24:return null;case 25:return null}throw Error(k(156,t.tag))}function sp(e,t){switch(As(t),t.tag){case 1:return De(t.type)&&ul(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return kn(),Z(ze),Z(Se),Ys(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return qs(t),null;case 13:if(Z(te),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(k(340));Sn()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return Z(te),null;case 4:return kn(),null;case 10:return Ws(t.type._context),null;case 22:case 23:return sa(),null;case 24:return null;default:return null}}var Rr=!1,we=!1,ap=typeof WeakSet=="function"?WeakSet:Set,z=null;function dn(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){le(e,t,r)}else n.current=null}function os(e,t,n){try{n()}catch(r){le(e,t,r)}}var go=!1;function op(e,t){if(Qi=il,e=zu(),Is(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var l=r.anchorOffset,i=r.focusNode;r=r.focusOffset;try{n.nodeType,i.nodeType}catch{n=null;break e}var s=0,o=-1,u=-1,d=0,g=0,h=e,m=null;t:for(;;){for(var x;h!==n||l!==0&&h.nodeType!==3||(o=s+l),h!==i||r!==0&&h.nodeType!==3||(u=s+r),h.nodeType===3&&(s+=h.nodeValue.length),(x=h.firstChild)!==null;)m=h,h=x;for(;;){if(h===e)break t;if(m===n&&++d===l&&(o=s),m===i&&++g===r&&(u=s),(x=h.nextSibling)!==null)break;h=m,m=h.parentNode}h=x}n=o===-1||u===-1?null:{start:o,end:u}}else n=null}n=n||{start:0,end:0}}else n=null;for(Ki={focusedElem:e,selectionRange:n},il=!1,z=t;z!==null;)if(t=z,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,z=e;else for(;z!==null;){t=z;try{var w=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(w!==null){var v=w.memoizedProps,D=w.memoizedState,f=t.stateNode,c=f.getSnapshotBeforeUpdate(t.elementType===t.type?v:qe(t.type,v),D);f.__reactInternalSnapshotBeforeUpdate=c}break;case 3:var p=t.stateNode.containerInfo;p.nodeType===1?p.textContent="":p.nodeType===9&&p.documentElement&&p.removeChild(p.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(k(163))}}catch(y){le(t,t.return,y)}if(e=t.sibling,e!==null){e.return=t.return,z=e;break}z=t.return}return w=go,go=!1,w}function qn(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var l=r=r.next;do{if((l.tag&e)===e){var i=l.destroy;l.destroy=void 0,i!==void 0&&os(t,n,i)}l=l.next}while(l!==r)}}function Fl(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function us(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function Cc(e){var t=e.alternate;t!==null&&(e.alternate=null,Cc(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[nt],delete t[or],delete t[Yi],delete t[Wf],delete t[Qf])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function Mc(e){return e.tag===5||e.tag===3||e.tag===4}function yo(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||Mc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function cs(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=ol));else if(r!==4&&(e=e.child,e!==null))for(cs(e,t,n),e=e.sibling;e!==null;)cs(e,t,n),e=e.sibling}function ds(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(ds(e,t,n),e=e.sibling;e!==null;)ds(e,t,n),e=e.sibling}var de=null,Ye=!1;function gt(e,t,n){for(n=n.child;n!==null;)Pc(e,t,n),n=n.sibling}function Pc(e,t,n){if(rt&&typeof rt.onCommitFiberUnmount=="function")try{rt.onCommitFiberUnmount(El,n)}catch{}switch(n.tag){case 5:we||dn(n,t);case 6:var r=de,l=Ye;de=null,gt(e,t,n),de=r,Ye=l,de!==null&&(Ye?(e=de,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):de.removeChild(n.stateNode));break;case 18:de!==null&&(Ye?(e=de,n=n.stateNode,e.nodeType===8?di(e.parentNode,n):e.nodeType===1&&di(e,n),rr(e)):di(de,n.stateNode));break;case 4:r=de,l=Ye,de=n.stateNode.containerInfo,Ye=!0,gt(e,t,n),de=r,Ye=l;break;case 0:case 11:case 14:case 15:if(!we&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){l=r=r.next;do{var i=l,s=i.destroy;i=i.tag,s!==void 0&&(i&2||i&4)&&os(n,t,s),l=l.next}while(l!==r)}gt(e,t,n);break;case 1:if(!we&&(dn(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(o){le(n,t,o)}gt(e,t,n);break;case 21:gt(e,t,n);break;case 22:n.mode&1?(we=(r=we)||n.memoizedState!==null,gt(e,t,n),we=r):gt(e,t,n);break;default:gt(e,t,n)}}function xo(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new ap),t.forEach(function(r){var l=gp.bind(null,e,r);n.has(r)||(n.add(r),r.then(l,l))})}}function be(e,t){var n=t.deletions;if(n!==null)for(var r=0;rl&&(l=s),r&=~i}if(r=l,r=ie()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*cp(r/1960))-r,10e?16:e,Nt===null)var r=!1;else{if(e=Nt,Nt=null,Sl=0,H&6)throw Error(k(331));var l=H;for(H|=4,z=e.current;z!==null;){var i=z,s=i.child;if(z.flags&16){var o=i.deletions;if(o!==null){for(var u=0;uie()-la?Wt(e,0):ra|=n),Le(e,t)}function Oc(e,t){t===0&&(e.mode&1?(t=Er,Er<<=1,!(Er&130023424)&&(Er=4194304)):t=1);var n=je();e=pt(e,t),e!==null&&(vr(e,t,n),Le(e,n))}function vp(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Oc(e,n)}function gp(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(k(314))}r!==null&&r.delete(t),Oc(e,n)}var Ic;Ic=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||ze.current)Te=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return Te=!1,lp(e,t,n);Te=!!(e.flags&131072)}else Te=!1,ee&&t.flags&1048576&&Vu(t,fl,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;qr(e,t),e=t.pendingProps;var l=wn(t,Se.current);gn(t,n),l=Gs(null,t,r,e,l,n);var i=Zs();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,De(r)?(i=!0,cl(t)):i=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,Ks(t),l.updater=Ll,t.stateNode=l,l._reactInternals=t,ts(t,r,e,n),t=ls(null,t,r,!0,i,n)):(t.tag=0,ee&&i&&Bs(t),Ne(null,t,l,n),t=t.child),t;case 16:r=t.elementType;e:{switch(qr(e,t),e=t.pendingProps,l=r._init,r=l(r._payload),t.type=r,l=t.tag=xp(r),e=qe(r,e),l){case 0:t=rs(null,t,r,e,n);break e;case 1:t=mo(null,t,r,e,n);break e;case 11:t=fo(null,t,r,e,n);break e;case 14:t=po(null,t,r,qe(r.type,e),n);break e}throw Error(k(306,r,""))}return t;case 0:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:qe(r,l),rs(e,t,r,l,n);case 1:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:qe(r,l),mo(e,t,r,l,n);case 3:e:{if(Sc(t),e===null)throw Error(k(387));r=t.pendingProps,i=t.memoizedState,l=i.element,qu(e,t),hl(t,r,null,n);var s=t.memoizedState;if(r=s.element,i.isDehydrated)if(i={element:r,isDehydrated:!1,cache:s.cache,pendingSuspenseBoundaries:s.pendingSuspenseBoundaries,transitions:s.transitions},t.updateQueue.baseState=i,t.memoizedState=i,t.flags&256){l=Nn(Error(k(423)),t),t=ho(e,t,r,n,l);break e}else if(r!==l){l=Nn(Error(k(424)),t),t=ho(e,t,r,n,l);break e}else for($e=Pt(t.stateNode.containerInfo.firstChild),Oe=t,ee=!0,Xe=null,n=Ku(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(Sn(),r===l){t=mt(e,t,n);break e}Ne(e,t,r,n)}t=t.child}return t;case 5:return Yu(t),e===null&&Zi(t),r=t.type,l=t.pendingProps,i=e!==null?e.memoizedProps:null,s=l.children,bi(r,l)?s=null:i!==null&&bi(r,i)&&(t.flags|=32),wc(e,t),Ne(e,t,s,n),t.child;case 6:return e===null&&Zi(t),null;case 13:return _c(e,t,n);case 4:return bs(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=_n(t,null,r,n):Ne(e,t,r,n),t.child;case 11:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:qe(r,l),fo(e,t,r,l,n);case 7:return Ne(e,t,t.pendingProps,n),t.child;case 8:return Ne(e,t,t.pendingProps.children,n),t.child;case 12:return Ne(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,l=t.pendingProps,i=t.memoizedProps,s=l.value,X(pl,r._currentValue),r._currentValue=s,i!==null)if(Je(i.value,s)){if(i.children===l.children&&!ze.current){t=mt(e,t,n);break e}}else for(i=t.child,i!==null&&(i.return=t);i!==null;){var o=i.dependencies;if(o!==null){s=i.child;for(var u=o.firstContext;u!==null;){if(u.context===r){if(i.tag===1){u=ct(-1,n&-n),u.tag=2;var d=i.updateQueue;if(d!==null){d=d.shared;var g=d.pending;g===null?u.next=u:(u.next=g.next,g.next=u),d.pending=u}}i.lanes|=n,u=i.alternate,u!==null&&(u.lanes|=n),Ji(i.return,n,t),o.lanes|=n;break}u=u.next}}else if(i.tag===10)s=i.type===t.type?null:i.child;else if(i.tag===18){if(s=i.return,s===null)throw Error(k(341));s.lanes|=n,o=s.alternate,o!==null&&(o.lanes|=n),Ji(s,n,t),s=i.sibling}else s=i.child;if(s!==null)s.return=i;else for(s=i;s!==null;){if(s===t){s=null;break}if(i=s.sibling,i!==null){i.return=s.return,s=i;break}s=s.return}i=s}Ne(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,r=t.pendingProps.children,gn(t,n),l=Qe(l),r=r(l),t.flags|=1,Ne(e,t,r,n),t.child;case 14:return r=t.type,l=qe(r,t.pendingProps),l=qe(r.type,l),po(e,t,r,l,n);case 15:return yc(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:qe(r,l),qr(e,t),t.tag=1,De(r)?(e=!0,cl(t)):e=!1,gn(t,n),hc(t,r,l),ts(t,r,l,n),ls(null,t,r,!0,e,n);case 19:return kc(e,t,n);case 22:return xc(e,t,n)}throw Error(k(156,t.tag))};function Bc(e,t){return fu(e,t)}function yp(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function He(e,t,n,r){return new yp(e,t,n,r)}function oa(e){return e=e.prototype,!(!e||!e.isReactComponent)}function xp(e){if(typeof e=="function")return oa(e)?1:0;if(e!=null){if(e=e.$$typeof,e===Cs)return 11;if(e===Ms)return 14}return 2}function Lt(e,t){var n=e.alternate;return n===null?(n=He(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Gr(e,t,n,r,l,i){var s=2;if(r=e,typeof e=="function")oa(e)&&(s=1);else if(typeof e=="string")s=5;else e:switch(e){case tn:return Qt(n.children,l,i,t);case Es:s=8,l|=8;break;case ji:return e=He(12,n,t,l|2),e.elementType=ji,e.lanes=i,e;case Ei:return e=He(13,n,t,l),e.elementType=Ei,e.lanes=i,e;case Ci:return e=He(19,n,t,l),e.elementType=Ci,e.lanes=i,e;case Yo:return $l(n,l,i,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case bo:s=10;break e;case qo:s=9;break e;case Cs:s=11;break e;case Ms:s=14;break e;case yt:s=16,r=null;break e}throw Error(k(130,e==null?e:typeof e,""))}return t=He(s,n,t,l),t.elementType=e,t.type=r,t.lanes=i,t}function Qt(e,t,n,r){return e=He(7,e,r,t),e.lanes=n,e}function $l(e,t,n,r){return e=He(22,e,r,t),e.elementType=Yo,e.lanes=n,e.stateNode={isHidden:!1},e}function xi(e,t,n){return e=He(6,e,null,t),e.lanes=n,e}function wi(e,t,n){return t=He(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function wp(e,t,n,r,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=ei(0),this.expirationTimes=ei(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=ei(0),this.identifierPrefix=r,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function ua(e,t,n,r,l,i,s,o,u){return e=new wp(e,t,n,o,u),t===1?(t=1,i===!0&&(t|=8)):t=0,i=He(3,null,null,t),e.current=i,i.stateNode=e,i.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},Ks(i),e}function Sp(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(Hc)}catch(e){console.error(e)}}Hc(),Ho.exports=Be;var Ep=Ho.exports,Co=Ep;ki.createRoot=Co.createRoot,ki.hydrateRoot=Co.hydrateRoot;const Cp={"Content-Type":"application/json"};async function Ir(e,t={}){const n=await fetch(e,{credentials:"same-origin",headers:{...t.body?Cp:{},...t.headers||{}},...t});if(n.status===204)return null;const r=n.headers.get("content-type")||"",l=await n.text();let i=null;if(l!=="")if(r.includes("application/json"))try{i=JSON.parse(l)}catch{const s=new Error("Server lieferte ungueltiges JSON.");throw s.status=n.status,s.payload={raw:l},s}else i={raw:l};if(!n.ok){const s=(i==null?void 0:i.message)||(l===""?`Request failed with status ${n.status}`:"Server lieferte keine JSON-Antwort."),o=new Error(s);throw o.status=n.status,o.payload=i,o}return i}const ye={get:e=>Ir(e),post:(e,t)=>Ir(e,{method:"POST",body:JSON.stringify(t)}),patch:(e,t)=>Ir(e,{method:"PATCH",body:JSON.stringify(t)}),delete:e=>Ir(e,{method:"DELETE"})};function Mp(e){const t=()=>window.matchMedia(e).matches,[n,r]=F.useState(t);return F.useEffect(()=>{const l=window.matchMedia(e),i=s=>r(s.matches);return l.addEventListener("change",i),r(l.matches),()=>l.removeEventListener("change",i)},[e]),n}const Pp=[{id:"calendar",label:"Kalender",roles:["tenant_admin","dispatcher","instructor"]},{id:"students",label:"Fahrschueler",roles:["tenant_admin","dispatcher"]},{id:"templates",label:"Klassen",roles:["tenant_admin"]},{id:"lesson-types",label:"Stundenarten",roles:["tenant_admin"]},{id:"instructors",label:"Fahrlehrer",roles:["tenant_admin"]},{id:"tenant-profile",label:"Fahrschule",roles:["tenant_admin"]},{id:"users",label:"Benutzer",roles:["tenant_admin","superadmin"]},{id:"tenants",label:"Mandanten",roles:["superadmin"]}],Wc=[{value:"BW",label:"Baden-Wuerttemberg"},{value:"BY",label:"Bayern"},{value:"BE",label:"Berlin"},{value:"BB",label:"Brandenburg"},{value:"HB",label:"Bremen"},{value:"HH",label:"Hamburg"},{value:"HE",label:"Hessen"},{value:"MV",label:"Mecklenburg-Vorpommern"},{value:"NI",label:"Niedersachsen"},{value:"NW",label:"Nordrhein-Westfalen"},{value:"RP",label:"Rheinland-Pfalz"},{value:"SL",label:"Saarland"},{value:"SN",label:"Sachsen"},{value:"ST",label:"Sachsen-Anhalt"},{value:"SH",label:"Schleswig-Holstein"},{value:"TH",label:"Thueringen"}];function vs(e=new Date){const t=new Date(e),r=(t.getDay()+6)%7;return t.setDate(t.getDate()-r),t.setHours(0,0,0,0),t}function Gn(e,t){const n=new Date(e);return n.setDate(n.getDate()+t),n}function xe(e){const t=e.getFullYear(),n=String(e.getMonth()+1).padStart(2,"0"),r=String(e.getDate()).padStart(2,"0");return`${t}-${n}-${r}`}function Tp(e,t){return xe(e)===xe(t)}function Qc(e,t){const n=new Date(e),r=new Date(t);return n.setHours(0,0,0,0),r.setHours(0,0,0,0),n0?l:r,s=Number((e==null?void 0:e.fixed_duration)||0)===1,o=s?1:Math.max(1,Math.round(i/r));return{baseDuration:r,duration:i,units:o,isFixed:s}}function _t(e,t=!1){return new Intl.DateTimeFormat("de-DE",{weekday:t?void 0:"short",day:"2-digit",month:"2-digit",hour:t?"2-digit":void 0,minute:t?"2-digit":void 0}).format(new Date(e))}function zp(e){return new Intl.DateTimeFormat("de-DE",{weekday:"long",day:"2-digit",month:"2-digit",year:"numeric"}).format(new Date(e))}function Po(e){return new Intl.DateTimeFormat("de-DE",{hour:"2-digit",minute:"2-digit"}).format(new Date(e))}function To(e){const t=Math.max(0,Number(e||0)),n=Math.floor(t/60),r=t%60;return`${String(n).padStart(2,"0")}:${String(r).padStart(2,"0")}`}function Dp(e){return e>9*60?45:e>6*60?30:0}function Lp(e,t){const n=new Date(t);n.setHours(0,0,0,0);const r=new Date(t);r.setHours(24,0,0,0);const l=(e||[]).filter(x=>{if(x.status==="cancelled")return!1;const w=new Date(x.start_at),v=new Date(x.end_at);return wn}).map(x=>{const w=new Date(x.start_at),v=new Date(x.end_at);return{...x,visibleStart:w>n?w:n,visibleEnd:vx.visibleEnd>x.visibleStart).sort((x,w)=>x.visibleStart-w.visibleStart),i=l.reduce((x,w)=>w.category!=="student"?x:x+Math.max(0,Number(w.units||0)),0);if(!l.length)return{taughtUnits:i,workSpanMinutes:0,breakMinutes:0,requiredBreakMinutes:0,breakStatus:"ok"};const s=[];for(const x of l){const w={start:x.visibleStart,end:x.visibleEnd},v=s[s.length-1];if(!v||w.start>v.end){s.push(w);continue}w.end>v.end&&(v.end=w.end)}let o=0,u=0;s.forEach((x,w)=>{if(o+=Math.max(0,Math.round((x.end.getTime()-x.start.getTime())/6e4)),w===0)return;const v=s[w-1];u+=Math.max(0,Math.round((x.start.getTime()-v.end.getTime())/6e4))});const d=s[0].start,g=s[s.length-1].end,h=Math.max(0,Math.round((g.getTime()-d.getTime())/6e4)),m=Dp(o);return{taughtUnits:i,workSpanMinutes:h,breakMinutes:u,requiredBreakMinutes:m,breakStatus:u>=m?"ok":"warning"}}function Fp(e){return String(e||"").replace(/[^\d+]/g,"").replace(/^\+/,"")}function Rp(e,t,n,r){if(!n||!r)return"";const l=e!=null&&e.first_name?` ${e.first_name}`:"",i=t?`${t.first_name} ${t.last_name}`.trim():"dein Fahrlehrer";return`Hi${l}, die Fahrstunde ist am ${zp(n)} um ${Po(n)} Uhr und geht voraussichtlich bis ${Po(r)} Uhr. Gruss ${i}`}function $p(e,t){const n=[0,1,2,3,4,5,6],r=new Date(e),l=Gn(e,7);return{start:r,end:l,days:n.map(i=>Gn(e,i))}}function Op(e){const t=[];for(const n of e.conflicts||[])t.push(`- ${n.message}`);for(const n of e.rest_period_warnings||[])t.push(`- ${n.message}`);return t.join(` -`)}function Ip(e){return Object.fromEntries(e.map(t=>[t.id,t.progress||[]]))}function Bp(e){return{...e,requirements:(e.requirements||[]).map((t,n)=>({...t,sort_order:n,required_units:Number(t.required_units||0),phase_label:t.phase_label||""}))}}function Ap(e,t){return e?`${e}::${t}`:t}function Kc(e){if(!Number(e==null?void 0:e.is_combination))return[""];const t=[...new Set((e.requirements||[]).map(n=>n.phase_label||"").filter(Boolean))];return t.length>0?t:["C","CE"]}function Zr(e,t){const n=new Set(e.lesson_type_ids&&e.lesson_type_ids.length>0?e.lesson_type_ids.map(s=>Number(s)):t.filter(s=>s.category==="student").map(s=>Number(s.id))),r=new Map((e.requirements||[]).map(s=>[s.requirement_key,s])),l=Kc(e),i=[];return t.filter(s=>s.category==="student"&&Number(s.is_counted)===1&&n.has(Number(s.id))).forEach((s,o)=>{const u=s.effective_requirement_key||s.requirement_key||`lesson_type_${s.id}`;l.forEach((d,g)=>{const h=Ap(d,u),m=r.get(h)||(g===0?r.get(u):null);i.push({requirement_key:h,phase_label:d,base_requirement_key:u,label:s.name,required_units:Number((m==null?void 0:m.required_units)||0),sort_order:o+g*1e3,lesson_type_id:Number(s.id),color:s.color})})}),i}function pa(e){const t=[];for(const n of e||[]){const r=n.phase_label||"";let l=t.find(i=>i.phase_label===r);l||(l={phase_label:r,rows:[]},t.push(l)),l.rows.push(n)}return t}function bc(e){const t=e.reduce((r,l)=>r+Number(l.required_units||0),0),n=e.reduce((r,l)=>r+Number(l.open_units||0),0);return{required:t,open:n,status:n>0?"missing":n<0?"over":"complete"}}function Jr(e){return Number(e)>0?{className:"is-missing",text:`${e} offen`}:Number(e)<0?{className:"is-over",text:`${Math.abs(e)} ueberplant`}:{className:"is-complete",text:"0 offen"}}function qc(e,t){return e.category!=="student"||!t?!0:(e.visible_template_ids||[]).includes(t)}function Up(e,t,n){if(!t)return e;const r=n.find(l=>Number(l.id)===Number(t));return r?e.filter(l=>l.category==="student"&&qc(l,Number(r.template_id))):e}function Yc(e){return{student:"Fahrschueler",private:"Privat",work:"Sonstige Arbeiten",theory:"Theorie",other:"Sonstiges",blocked:"Sperre"}[e]||e}function Vp(){var _,b;const e=Mp("(max-width: 960px)"),[t,n]=F.useState({user:null}),[r,l]=F.useState(null),[i,s]=F.useState(!0),[o,u]=F.useState("calendar"),[d,g]=F.useState(vs()),[h,m]=F.useState(null),[x,w]=F.useState("student"),[v,D]=F.useState(null),[f,c]=F.useState(null),[p,y]=F.useState(""),[j,M]=F.useState(()=>window.localStorage.getItem("calendar-theme")||"light"),S=F.useMemo(()=>$p(d),[d]);async function T(){try{const E=await ye.get("https://api.fahrschultermin.dehttps://api.fahrschultermin.de/api/v1/auth/me");return n(E),E.user&&await $(E.user,S.start,S.end),E}catch(E){return y(E.message),{user:null}}finally{s(!1)}}async function $(E=t.user,B=S.start,me=S.end){var he;if(!E)return;const ke=new URLSearchParams({from:B.toISOString(),to:me.toISOString()}),Me=await ye.get(`https://api.fahrschultermin.de/api/v1/bootstrap?${ke.toString()}`);l(Me),!h&&((he=Me.students)!=null&&he.length)&&m(Me.students[0].id)}F.useEffect(()=>{T()},[]),F.useEffect(()=>{t.user&&t.user.role!=="superadmin"&&$(t.user,S.start,S.end).catch(E=>y(E.message))},[d]),F.useEffect(()=>{document.documentElement.setAttribute("data-theme",j),window.localStorage.setItem("calendar-theme",j)},[j]);const L=(r==null?void 0:r.students)||[],W=(r==null?void 0:r.lessonTypes)||[],C=(r==null?void 0:r.instructors)||[],R=(r==null?void 0:r.templates)||[],A=(r==null?void 0:r.users)||[],N=(r==null?void 0:r.tenants)||[],V=(r==null?void 0:r.appointments)||[],J=(r==null?void 0:r.nextAppointment)||null,P=(r==null?void 0:r.sunTimes)||{},O=(r==null?void 0:r.dayMeta)||{},I=L.find(E=>E.id===h)||null,q=Ip(L),Q=Pp.filter(E=>{var B;return E.roles.includes((B=t.user)==null?void 0:B.role)});F.useEffect(()=>{Q.length&&!Q.some(E=>E.id===o)&&u(Q[0].id)},[o,Q]);async function it(E){y("");try{await ye.post("https://api.fahrschultermin.dehttps://api.fahrschultermin.de/api/v1/auth/login",E);const B=await T();B!=null&&B.user||y("Login war erfolgreich, aber die Session konnte nicht geladen werden. Bitte Server-Session und Cookies pruefen.")}catch(B){y(B.message)}}async function Y(){await ye.post("https://api.fahrschultermin.dehttps://api.fahrschultermin.de/api/v1/auth/logout",{}),window.location.reload()}async function Fe(E,B,me=null){me?await ye.patch(`${E}/${me}`,B):await ye.post(E,B),await $(t.user,S.start,S.end)}async function _e(E,B){await ye.delete(`${E}/${B}`),await $(t.user,S.start,S.end)}async function vt(E,B){await ye.patch(`https://api.fahrschultermin.de/api/v1/students/${E}/training`,{rows:B}),await $(t.user,S.start,S.end)}async function Wl(E){E.preventDefault();const B=W.find(Me=>Number(Me.id)===Number(v==null?void 0:v.lesson_type_id)),me=ys(B,v==null?void 0:v.start_at,v==null?void 0:v.end_at);if(!(v!=null&&v.start_at)||!(v!=null&&v.end_at)||Ul(v.start_at,v.end_at)<=0){y("Bitte eine gueltige Start- und Endzeit waehlen.");return}const ke={student_id:v.student_id?Number(v.student_id):null,lesson_type_id:Number(v.lesson_type_id),instructor_id:Number(v.instructor_id),start_at:new Date(v.start_at).toISOString(),end_at:new Date(v.end_at).toISOString(),units:me.units,notes:v.notes,status:v.status,requirement_phase:v.requirement_phase||"",warning_acknowledged:!1};try{f?await ye.patch(`https://api.fahrschultermin.de/api/v1/appointments/${f}`,ke):await ye.post("https://api.fahrschultermin.dehttps://api.fahrschultermin.de/api/v1/appointments",ke),D(null),c(null),await $(t.user,S.start,S.end)}catch(Me){Me.status===409?window.confirm(`Warnungen beim Einplanen: +`)}function Ip(e){return Object.fromEntries(e.map(t=>[t.id,t.progress||[]]))}function Bp(e){return{...e,requirements:(e.requirements||[]).map((t,n)=>({...t,sort_order:n,required_units:Number(t.required_units||0),phase_label:t.phase_label||""}))}}function Ap(e,t){return e?`${e}::${t}`:t}function Kc(e){if(!Number(e==null?void 0:e.is_combination))return[""];const t=[...new Set((e.requirements||[]).map(n=>n.phase_label||"").filter(Boolean))];return t.length>0?t:["C","CE"]}function Zr(e,t){const n=new Set(e.lesson_type_ids&&e.lesson_type_ids.length>0?e.lesson_type_ids.map(s=>Number(s)):t.filter(s=>s.category==="student").map(s=>Number(s.id))),r=new Map((e.requirements||[]).map(s=>[s.requirement_key,s])),l=Kc(e),i=[];return t.filter(s=>s.category==="student"&&Number(s.is_counted)===1&&n.has(Number(s.id))).forEach((s,o)=>{const u=s.effective_requirement_key||s.requirement_key||`lesson_type_${s.id}`;l.forEach((d,g)=>{const h=Ap(d,u),m=r.get(h)||(g===0?r.get(u):null);i.push({requirement_key:h,phase_label:d,base_requirement_key:u,label:s.name,required_units:Number((m==null?void 0:m.required_units)||0),sort_order:o+g*1e3,lesson_type_id:Number(s.id),color:s.color})})}),i}function pa(e){const t=[];for(const n of e||[]){const r=n.phase_label||"";let l=t.find(i=>i.phase_label===r);l||(l={phase_label:r,rows:[]},t.push(l)),l.rows.push(n)}return t}function bc(e){const t=e.reduce((r,l)=>r+Number(l.required_units||0),0),n=e.reduce((r,l)=>r+Number(l.open_units||0),0);return{required:t,open:n,status:n>0?"missing":n<0?"over":"complete"}}function Jr(e){return Number(e)>0?{className:"is-missing",text:`${e} offen`}:Number(e)<0?{className:"is-over",text:`${Math.abs(e)} ueberplant`}:{className:"is-complete",text:"0 offen"}}function qc(e,t){return e.category!=="student"||!t?!0:(e.visible_template_ids||[]).includes(t)}function Up(e,t,n){if(!t)return e;const r=n.find(l=>Number(l.id)===Number(t));return r?e.filter(l=>l.category==="student"&&qc(l,Number(r.template_id))):e}function Yc(e){return{student:"Fahrschueler",private:"Privat",work:"Sonstige Arbeiten",theory:"Theorie",other:"Sonstiges",blocked:"Sperre"}[e]||e}function Vp(){var _,b;const e=Mp("(max-width: 960px)"),[t,n]=F.useState({user:null}),[r,l]=F.useState(null),[i,s]=F.useState(!0),[o,u]=F.useState("calendar"),[d,g]=F.useState(vs()),[h,m]=F.useState(null),[x,w]=F.useState("student"),[v,D]=F.useState(null),[f,c]=F.useState(null),[p,y]=F.useState(""),[j,M]=F.useState(()=>window.localStorage.getItem("calendar-theme")||"light"),S=F.useMemo(()=>$p(d),[d]);async function T(){try{const E=await ye.get("https://api.fahrschultermin.de/api/v1/auth/me");return n(E),E.user&&await $(E.user,S.start,S.end),E}catch(E){return y(E.message),{user:null}}finally{s(!1)}}async function $(E=t.user,B=S.start,me=S.end){var he;if(!E)return;const ke=new URLSearchParams({from:B.toISOString(),to:me.toISOString()}),Me=await ye.get(`https://api.fahrschultermin.de/api/v1/bootstrap?${ke.toString()}`);l(Me),!h&&((he=Me.students)!=null&&he.length)&&m(Me.students[0].id)}F.useEffect(()=>{T()},[]),F.useEffect(()=>{t.user&&t.user.role!=="superadmin"&&$(t.user,S.start,S.end).catch(E=>y(E.message))},[d]),F.useEffect(()=>{document.documentElement.setAttribute("data-theme",j),window.localStorage.setItem("calendar-theme",j)},[j]);const L=(r==null?void 0:r.students)||[],W=(r==null?void 0:r.lessonTypes)||[],C=(r==null?void 0:r.instructors)||[],R=(r==null?void 0:r.templates)||[],A=(r==null?void 0:r.users)||[],N=(r==null?void 0:r.tenants)||[],V=(r==null?void 0:r.appointments)||[],J=(r==null?void 0:r.nextAppointment)||null,P=(r==null?void 0:r.sunTimes)||{},O=(r==null?void 0:r.dayMeta)||{},I=L.find(E=>E.id===h)||null,q=Ip(L),Q=Pp.filter(E=>{var B;return E.roles.includes((B=t.user)==null?void 0:B.role)});F.useEffect(()=>{Q.length&&!Q.some(E=>E.id===o)&&u(Q[0].id)},[o,Q]);async function it(E){y("");try{await ye.post("https://api.fahrschultermin.de/api/v1/auth/login",E);const B=await T();B!=null&&B.user||y("Login war erfolgreich, aber die Session konnte nicht geladen werden. Bitte Server-Session und Cookies pruefen.")}catch(B){y(B.message)}}async function Y(){await ye.post("https://api.fahrschultermin.de/api/v1/auth/logout",{}),window.location.reload()}async function Fe(E,B,me=null){me?await ye.patch(`${E}/${me}`,B):await ye.post(E,B),await $(t.user,S.start,S.end)}async function _e(E,B){await ye.delete(`${E}/${B}`),await $(t.user,S.start,S.end)}async function vt(E,B){await ye.patch(`https://api.fahrschultermin.de/api/v1/students/${E}/training`,{rows:B}),await $(t.user,S.start,S.end)}async function Wl(E){E.preventDefault();const B=W.find(Me=>Number(Me.id)===Number(v==null?void 0:v.lesson_type_id)),me=ys(B,v==null?void 0:v.start_at,v==null?void 0:v.end_at);if(!(v!=null&&v.start_at)||!(v!=null&&v.end_at)||Ul(v.start_at,v.end_at)<=0){y("Bitte eine gueltige Start- und Endzeit waehlen.");return}const ke={student_id:v.student_id?Number(v.student_id):null,lesson_type_id:Number(v.lesson_type_id),instructor_id:Number(v.instructor_id),start_at:new Date(v.start_at).toISOString(),end_at:new Date(v.end_at).toISOString(),units:me.units,notes:v.notes,status:v.status,requirement_phase:v.requirement_phase||"",warning_acknowledged:!1};try{f?await ye.patch(`https://api.fahrschultermin.de/api/v1/appointments/${f}`,ke):await ye.post("https://api.fahrschultermin.de/api/v1/appointments",ke),D(null),c(null),await $(t.user,S.start,S.end)}catch(Me){Me.status===409?window.confirm(`Warnungen beim Einplanen: ${Op(Me.payload.warnings)} -Trotzdem speichern?`)&&(ke.warning_acknowledged=!0,f?await ye.patch(`https://api.fahrschultermin.de/api/v1/appointments/${f}`,ke):await ye.post("https://api.fahrschultermin.dehttps://api.fahrschultermin.de/api/v1/appointments",ke),D(null),c(null),await $(t.user,S.start,S.end)):y(Me.message)}}async function Ql(){f&&window.confirm("Termin wirklich loeschen?")&&(await ye.delete(`https://api.fahrschultermin.de/api/v1/appointments/${f}`),D(null),c(null),await $(t.user,S.start,S.end))}function wr({lessonTypeId:E,day:B,hour:me,minute:ke=0,instructorId:Me,appointment:he=null,startAt:Kl=null,endAt:ma=null}){var va,ga;if(he){c(he.id),D({student_id:he.student_id||"",lesson_type_id:he.lesson_type_id,instructor_id:he.instructor_id,start_at:Un(new Date(he.start_at)),end_at:Un(new Date(he.end_at)),units:he.units,notes:he.notes||"",status:he.status,requirement_phase:(he.counted_requirement_key||"").includes("::")?he.counted_requirement_key.split("::")[0]:""});return}const bl=Kl?new Date(Kl):new Date(B);Kl||bl.setHours(me,ke,0,0);const ha=E||((va=W[0])==null?void 0:va.id)||"",ql=W.find(Gc=>Number(Gc.id)===Number(ha)),Xc=ma?new Date(ma):new Date(bl.getTime()+Math.max(1,Number((ql==null?void 0:ql.default_duration)||45))*60*1e3);c(null),D({student_id:x==="student"&&h||"",lesson_type_id:ha,instructor_id:Me||((ga=C[0])==null?void 0:ga.id)||"",start_at:Un(bl),end_at:Un(Xc),units:1,notes:"",status:"planned",requirement_phase:""})}return i?a.jsx("div",{className:"app-shell",children:a.jsx("div",{className:"loading-panel",children:"Lade Anwendung ..."})}):t.user?a.jsxs("div",{className:"app-shell",children:[a.jsxs("header",{className:"topbar",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Fahrschul-Kalender"}),a.jsx("h1",{children:"DriveTime Planner"}),a.jsx("p",{className:"meta-line",children:t.user.role==="superadmin"?"Systemweit":`${(_=r==null?void 0:r.tenant)==null?void 0:_.name} · ${(b=r==null?void 0:r.tenant)==null?void 0:b.timezone}`})]}),a.jsxs("div",{className:"topbar-actions",children:[a.jsxs("span",{className:"badge",children:[t.user.first_name," ",t.user.last_name]}),a.jsx("button",{className:"ghost-button",onClick:()=>M(E=>E==="dark"?"light":"dark"),children:j==="dark"?"Hell":"Dunkel"}),a.jsx("button",{className:"ghost-button",onClick:Y,children:"Logout"})]})]}),p&&a.jsx("div",{className:"flash-error",children:p}),a.jsx("nav",{className:"tab-strip",children:Q.map(E=>a.jsx("button",{className:E.id===o?"tab-button active":"tab-button",onClick:()=>u(E.id),children:E.label},E.id))}),a.jsxs("main",{className:"content-grid",children:[o==="calendar"&&t.user.role!=="superadmin"&&a.jsx(Wp,{isMobile:e,weekStart:d,setWeekStart:g,range:S,instructors:C,students:L,lessonTypes:W,appointments:V,nextAppointment:J,sunTimes:P,dayMeta:O,selectedStudent:I,selectedStudentId:h,setSelectedStudentId:m,plannerMode:x,setPlannerMode:w,progress:I?q[I.id]||[]:[],onCreate:wr}),o==="students"&&t.user.role!=="superadmin"&&a.jsx(bp,{students:L,templates:R,onSave:(E,B)=>Fe("https://api.fahrschultermin.dehttps://api.fahrschultermin.de/api/v1/students",E,B),onSaveTraining:vt,onDelete:E=>_e("https://api.fahrschultermin.dehttps://api.fahrschultermin.de/api/v1/students",E)}),o==="lesson-types"&&t.user.role!=="superadmin"&&a.jsx(qp,{lessonTypes:W,templates:R,onSave:(E,B)=>Fe("https://api.fahrschultermin.dehttps://api.fahrschultermin.de/api/v1/lesson-types",E,B),onDelete:E=>_e("https://api.fahrschultermin.dehttps://api.fahrschultermin.de/api/v1/lesson-types",E),onReorder:async E=>{await ye.post("https://api.fahrschultermin.dehttps://api.fahrschultermin.de/api/v1/lesson-types/reorder",{lesson_type_ids:E}),await $(t.user,S.start,S.end)},onSaveMatrix:async E=>{await ye.post("https://api.fahrschultermin.dehttps://api.fahrschultermin.de/api/v1/lesson-types/visibility",{rows:E}),await $(t.user,S.start,S.end)}}),o==="templates"&&t.user.role!=="superadmin"&&a.jsx(Xp,{templates:R,lessonTypes:W,onSave:(E,B)=>Fe("https://api.fahrschultermin.dehttps://api.fahrschultermin.de/api/v1/license-class-templates",E,B)}),o==="instructors"&&t.user.role!=="superadmin"&&a.jsx(Yp,{instructors:C,onSave:(E,B)=>Fe("https://api.fahrschultermin.dehttps://api.fahrschultermin.de/api/v1/instructors",E,B)}),o==="users"&&a.jsx(Gp,{users:A,tenants:N,currentUser:t.user,onSave:(E,B)=>Fe("https://api.fahrschultermin.dehttps://api.fahrschultermin.de/api/v1/users",E,B)}),o==="tenant-profile"&&t.user.role==="tenant_admin"&&(r==null?void 0:r.tenant)&&a.jsx(Jp,{tenant:r.tenant,onSave:async E=>{await ye.patch("https://api.fahrschultermin.dehttps://api.fahrschultermin.de/api/v1/tenant/profile",E),await $(t.user,S.start,S.end)}}),o==="tenants"&&t.user.role==="superadmin"&&a.jsx(Zp,{tenants:N,onSave:(E,B)=>Fe("https://api.fahrschultermin.dehttps://api.fahrschultermin.de/api/v1/tenants",E,B)})]}),v&&a.jsx(tm,{draft:v,setDraft:D,lessonTypes:W,instructors:C,students:L,templates:R,onPickStudent:E=>{E&&(m(Number(E)),w("student"),D(null),c(null))},onSubmit:Wl,onDelete:Ql,onClose:()=>{D(null),c(null)},isEditing:!!f})]}):a.jsx(Hp,{onSubmit:it,error:p})}function Hp({onSubmit:e,error:t}){async function n(r){r.preventDefault();const l=new FormData(r.currentTarget);await e({email:l.get("email"),password:l.get("password")})}return a.jsx("div",{className:"login-shell",children:a.jsxs("div",{className:"login-card",children:[a.jsx("p",{className:"eyebrow",children:"Mandantenfaehig · Kalenderfokus"}),a.jsx("h1",{children:"Dispositionsfenster"}),a.jsx("p",{className:"login-copy",children:"Planung fuer Fahrstunden, Vorlagen und Ruhezeitwarnungen in einer kompakten Leitstelle."}),a.jsxs("form",{onSubmit:n,className:"stack-form",children:[a.jsxs("label",{children:["E-Mail",a.jsx("input",{name:"email",type:"email",defaultValue:"admin@nordring.test",required:!0})]}),a.jsxs("label",{children:["Passwort",a.jsx("input",{name:"password",type:"password",defaultValue:"secret123",required:!0})]}),t&&a.jsx("div",{className:"inline-error",children:t}),a.jsx("button",{className:"primary-button",type:"submit",children:"Einloggen"})]}),a.jsx("p",{className:"hint-line",children:"Demo: `admin@nordring.test` / `secret123` oder `superadmin@example.com` / `secret123`"})]})})}function Wp(e){var wr;const{isMobile:t,weekStart:n,setWeekStart:r,range:l,instructors:i,students:s,lessonTypes:o,appointments:u,nextAppointment:d,sunTimes:g,dayMeta:h,selectedStudent:m,selectedStudentId:x,setSelectedStudentId:w,plannerMode:v,setPlannerMode:D,progress:f,onCreate:c}=e,p=t?[new Date]:l.days,[y,j]=F.useState(()=>new Date),[M,S]=F.useState(""),[T,$]=F.useState(!1),[L,W]=F.useState(null),[C,R]=F.useState(((wr=i[0])==null?void 0:wr.id)||null),[A,N]=F.useState(()=>xe(new Date)),[V,J]=F.useState("08:00"),[P,O]=F.useState("10:00"),I=[{id:"student",label:"Fahrschueler"},{id:"private",label:"Privat"},{id:"work",label:"Sonstige Arbeiten"},{id:"theory",label:"Theorie"}],q=Array.from({length:96},(_,b)=>{const E=Math.floor(b/4),B=b%4*15;return{hour:E,minute:B,label:`${String(E).padStart(2,"0")}:${String(B).padStart(2,"0")}`,rowClass:B===0?"full-hour":""}}),Q=o.filter(_=>v==="student"?_.category==="student"&&qc(_,m==null?void 0:m.template_id):v==="private"?_.category==="private":v==="work"?_.category==="work"||_.category==="other"||_.category==="blocked":v==="theory"?_.category==="theory":!1),it=s.filter(_=>`${_.first_name} ${_.last_name} ${_.template_code}`.toLowerCase().includes(M.toLowerCase())).slice(0,8);F.useEffect(()=>{const _=window.setInterval(()=>{j(new Date)},6e4);return()=>window.clearInterval(_)},[]),F.useEffect(()=>{m&&S(`${m.first_name} ${m.last_name}`)},[x,m]),F.useEffect(()=>{if(!i.length){R(null);return}i.some(_=>_.id===C)||R(i[0].id)},[i,C]),F.useEffect(()=>{if(Q.length===0){W(null);return}Q.some(_=>_.id===L)||W(Q[0].id)},[L,Q]);const Y=i.find(_=>_.id===C)||i[0]||null;Q.find(_=>_.id===L);const Fe=A&&Ul(jt(A,V),jt(A,P))>0,_e=Y?u.filter(_=>_.instructor_id===Y.id):[],vt=Math.max(0,Number((Y==null?void 0:Y.pre_start_buffer_minutes)||0)),Wl=Array.from(_e.filter(_=>Number(_.is_rest_relevant)===1).reduce((_,b)=>{const E=new Date(b.end_at),B=`${E.getFullYear()}-${String(E.getMonth()+1).padStart(2,"0")}-${String(E.getDate()).padStart(2,"0")}`,me=_.get(B);return(!me||E>me.endAt)&&_.set(B,{endAt:E}),_},new Map).values()).map(({endAt:_})=>{const b=_,E=new Date(_.getTime()+11*60*60*1e3),B=_e.some(me=>{const ke=new Date(me.start_at);return ke>b&&ke_.start-b.start);function Ql(){!L||!(Y!=null&&Y.id)||!Fe||c({lessonTypeId:L,instructorId:Y.id,startAt:jt(A,V),endAt:jt(A,P)})}return a.jsxs(a.Fragment,{children:[a.jsxs("section",{className:"calendar-stage",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Einsatzfenster"}),a.jsxs("h2",{children:[_t(l.start)," - ",_t(Gn(l.end,-1))]})]}),a.jsxs("div",{className:"toolbar",children:[a.jsxs("label",{className:"calendar-filter",children:["Fahrlehrer",a.jsx("select",{value:C||"",onChange:_=>R(Number(_.target.value)),children:i.map(_=>a.jsxs("option",{value:_.id,children:[_.first_name," ",_.last_name]},_.id))})]}),a.jsx("button",{className:"ghost-button",onClick:()=>r(Gn(n,-7)),children:"Zurueck"}),a.jsx("button",{className:"ghost-button",onClick:()=>r(vs()),children:"Heute"}),d&&a.jsx("button",{className:"ghost-button",onClick:()=>r(vs(new Date(d.start_at))),children:"Naechster Termin"}),a.jsx("button",{className:"ghost-button",onClick:()=>r(Gn(n,7)),children:"Weiter"})]})]}),a.jsxs("div",{className:"calendar-grid",children:[a.jsx("div",{className:"calendar-head time-col",children:"Zeit"}),p.map(_=>(()=>{var E,B,me,ke,Me;const b=Lp(_e,_);return a.jsxs("div",{className:["calendar-head day-col",Qc(_,y)?"past-head":"",(E=h[xe(_)])!=null&&E.holidayName?"holiday-head":"",(B=h[xe(_)])!=null&&B.isSunday?"sunday-head":""].filter(Boolean).join(" "),children:[a.jsx("strong",{children:_t(_)}),a.jsxs("div",{className:"day-summary",children:[a.jsxs("span",{className:"day-summary-line",children:[a.jsxs("strong",{children:[b.taughtUnits," UE"]}),a.jsx("span",{children:"geschult"})]}),a.jsxs("span",{className:"day-summary-line",children:[a.jsx("strong",{children:To(b.workSpanMinutes)}),a.jsx("span",{children:"Arbeitszeit"})]}),a.jsxs("span",{className:`day-summary-line break-line ${b.breakStatus}`,children:[a.jsx("strong",{children:To(b.breakMinutes)}),a.jsxs("span",{children:["(Pause ",b.requiredBreakMinutes," min)"]})]})]}),((me=h[xe(_)])==null?void 0:me.holidayName)&&a.jsx("span",{className:"holiday-meta",children:h[xe(_)].holidayName}),((ke=g[xe(_)])==null?void 0:ke.sunrise)&&((Me=g[xe(_)])==null?void 0:Me.sunset)&&a.jsxs("span",{className:"sun-meta",children:["Auf ",g[xe(_)].sunrise," · Unter ",g[xe(_)].sunset]})]},_.toISOString())})()),a.jsx("div",{className:"time-grid",children:q.map(_=>a.jsx("div",{className:`slot-label ${_.rowClass}`,children:_.label},_.label))}),p.map(_=>a.jsx(Qp,{day:_,slots:q,instructor:Y,appointments:_e,preStartBufferMinutes:vt,restIntervals:Wl,sunInfo:g[xe(_)]||null,dayMeta:h[xe(_)]||null,now:y,armedLessonTypeId:L,onCreate:c},_.toISOString()))]})]}),a.jsxs("aside",{className:"planner-panel",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Planung"}),a.jsx("h2",{children:"Bausteine"})]}),t&&a.jsx("button",{className:"primary-button compact",onClick:()=>c({day:new Date,hour:8,minute:0,instructorId:Y==null?void 0:Y.id}),children:"+"})]}),a.jsx("div",{className:"mode-switch",children:I.map(_=>a.jsx("button",{className:v===_.id?"tab-button active":"tab-button",onClick:()=>D(_.id),children:_.label},_.id))}),v==="student"&&a.jsxs("label",{className:"stack-form",children:["Fahrschueler",a.jsxs("div",{className:"autocomplete-shell",children:[a.jsx("input",{type:"text",value:M,placeholder:"Name eingeben ...",onFocus:()=>$(!0),onChange:_=>{S(_.target.value),$(!0)},onBlur:()=>{window.setTimeout(()=>$(!1),120)}}),T&&it.length>0&&a.jsx("div",{className:"autocomplete-list",children:it.map(_=>a.jsxs("button",{type:"button",className:"autocomplete-option",onMouseDown:b=>{b.preventDefault(),w(_.id),S(`${_.first_name} ${_.last_name}`),$(!1)},children:[a.jsxs("strong",{children:[_.first_name," ",_.last_name]}),a.jsx("span",{children:_.template_code})]},_.id))})]})]}),a.jsx("div",{className:"block-bank",children:Q.map(_=>a.jsxs("div",{className:L===_.id?"lesson-chip active":"lesson-chip",draggable:!t,onDragStart:b=>{const E=JSON.stringify({lessonTypeId:_.id});b.dataTransfer.effectAllowed="move",b.dataTransfer.setData("application/json",E),b.dataTransfer.setData("text/plain",E)},onClick:()=>{W(_.id),t&&c({day:new Date,hour:8,minute:0,lessonTypeId:_.id,instructorId:Y==null?void 0:Y.id})},style:{"--chip-color":_.color},children:[a.jsx("strong",{children:_.name}),a.jsx("span",{children:_.fixed_duration?`${_.default_duration} min fix`:`${_.default_duration} min pro Einheit`})]},_.id))}),v==="private"&&L&&a.jsxs("div",{className:"quick-plan-panel",children:[a.jsx("div",{className:"section-head compact-head",children:a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Privat"}),a.jsx("h2",{children:"Schnell eintragen"})]})}),a.jsxs("label",{className:"stack-form",children:["Datum",a.jsx("input",{type:"date",value:A,onChange:_=>N(_.target.value)})]}),a.jsxs("div",{className:"time-editor-grid two-col",children:[a.jsx(Nl,{label:"Start",value:V,onChange:J}),a.jsx(Nl,{label:"Ende",value:P,onChange:O})]}),a.jsx("button",{type:"button",className:"primary-button",onClick:Ql,disabled:!Fe,children:"Privatblock anlegen"})]}),m&&a.jsxs("div",{className:"progress-panel",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Reststunden"}),a.jsxs("h2",{children:[m.first_name," ",m.last_name]})]}),a.jsx("span",{className:"badge",children:m.template_code})]}),pa(f).map(_=>{const b=bc(_.rows);return a.jsxs("div",{className:"progress-phase",children:[_.phase_label&&a.jsxs("div",{className:"phase-head",children:[a.jsx("strong",{children:_.phase_label}),a.jsx("span",{className:`phase-state ${b.status}`,children:Jr(b.open).text})]}),_.rows.map(E=>a.jsxs("div",{className:"progress-row",children:[a.jsxs("div",{children:[a.jsx("strong",{children:E.label}),a.jsxs("p",{children:["Soll ",E.required_units," · Geplant ",E.planned_units," · Erledigt ",E.completed_units]})]}),a.jsx("div",{className:`open-pill ${Jr(E.open_units).className}`,children:Jr(E.open_units).text})]},E.requirement_key))]},_.phase_label||"default")})]})]})]})}function Qp({day:e,slots:t,instructor:n,appointments:r,preStartBufferMinutes:l,restIntervals:i,sunInfo:s,dayMeta:o,now:u,armedLessonTypeId:d,onCreate:g}){const h=new Date(e);h.setHours(0,0,0,0);const m=new Date(e);m.setHours(24,0,0,0);const x=Tp(e,u),w=Qc(e,u),v=u.getHours()*60+u.getMinutes(),D=Math.min(96,Math.max(0,v/15)),f=Math.min(96,Math.max(0,Math.ceil(D))),c=r.filter(C=>{const R=new Date(C.start_at),A=new Date(C.end_at);return Rh}),p=r.filter(C=>{const R=new Date(C.start_at);return R>=h&&Rnew Date(C.start_at)-new Date(R.start_at))[0]||null,y=(()=>{if(!p||l<=0)return null;const C=new Date(p.start_at),R=new Date(Math.max(h.getTime(),C.getTime()-l*60*1e3)),A=C;if(A<=R)return null;const N=R.getHours()*60+R.getMinutes(),V=A.getHours()*60+A.getMinutes(),J=Math.max(0,Math.floor(N/15)),P=Math.max(1,Math.ceil((V-N)/15));return{startIndex:J,spanBlocks:Math.min(P,96-J),label:`Anfahrt ${l} min`}})(),j=i.map(C=>{const R=C.start>h?C.start:h,A=C.end=m?24*60:A.getHours()*60+A.getMinutes(),J=Math.max(0,Math.floor(N/15)),P=Math.max(1,Math.ceil((V-N)/15));return{key:`${C.start.toISOString()}-${C.end.toISOString()}-${e.toISOString()}`,startIndex:J,spanBlocks:Math.min(P,96-J),label:`Ruhezeit bis ${String(C.end.getHours()).padStart(2,"0")}:${String(C.end.getMinutes()).padStart(2,"0")}`,hasConflict:C.hasConflict}}).filter(Boolean),M=Mo(s==null?void 0:s.sunrise),S=Mo(s==null?void 0:s.sunset),T=[];if(M!==null&&M>0&&T.push({key:`night-before-${xe(e)}`,startIndex:0,spanBlocks:Math.max(1,Math.ceil(M/15)),label:`Nacht bis ${s.sunrise}`,align:"end"}),S!==null&&S<24*60){const C=Math.max(0,Math.floor(S/15));T.push({key:`night-after-${xe(e)}`,startIndex:C,spanBlocks:Math.max(1,Math.ceil((24*60-S)/15)),label:`Dunkel ab ${s.sunset}`,align:"start"})}function $(C){g({day:e,hour:C.hour,minute:C.minute,instructorId:n==null?void 0:n.id,lessonTypeId:d||void 0})}function L(C,R){C.preventDefault();const A=C.dataTransfer.getData("application/json")||C.dataTransfer.getData("text/plain");if(!A)return;const N=JSON.parse(A);g({lessonTypeId:N.lessonTypeId,day:e,hour:R.hour,minute:R.minute,instructorId:n==null?void 0:n.id})}function W(C,R){const A=C.currentTarget.getBoundingClientRect(),N=Math.max(0,Math.min(A.height-1,C.clientY-A.top)),V=Math.floor(N/28),J=Math.min(95,R.startIndex+V);return t[J]}return a.jsxs("div",{className:["day-grid",w?"past-column":"",o!=null&&o.holidayName?"holiday-column":"",o!=null&&o.isSunday?"sunday-column":""].filter(Boolean).join(" "),children:[w&&a.jsx("div",{className:"day-past-overlay",style:{gridRow:"1 / span 96"}}),x&&f>0&&a.jsx("div",{className:"current-time-past",style:{gridRow:`1 / span ${f}`},"aria-hidden":"true"}),x&&D>0&&D<96&&a.jsx("div",{className:"current-time-line",style:{top:`${D*28}px`},"aria-hidden":"true",children:a.jsx("span",{children:"Jetzt"})}),t.map((C,R)=>a.jsx("button",{type:"button",className:`slot-cell quarter-hour ${C.rowClass}`,style:{gridRow:R+1},onDragOver:A=>{A.preventDefault(),A.dataTransfer.dropEffect="move"},onDrop:A=>L(A,C),onClick:()=>$(C)},C.label)),T.map(C=>a.jsx("div",{className:`sun-band ${C.align==="end"?"top-band":"bottom-band"}`,style:{gridRow:`${C.startIndex+1} / span ${C.spanBlocks}`},children:a.jsx("span",{children:C.label})},C.key)),y&&a.jsx("div",{className:"pre-start-stripe",style:{gridRow:`${y.startIndex+1} / span ${y.spanBlocks}`},"aria-hidden":"true",children:a.jsx("span",{children:y.label})}),c.map(C=>{const R=new Date(C.start_at),A=new Date(C.end_at),N=R>h?R:h,V=A=m?24*60:V.getHours()*60+V.getMinutes(),O=Math.max(0,Math.floor(J/15)),I=Math.max(1,Math.min(96-O,Math.ceil((P-J)/15)));return a.jsx(Kp,{appointment:C,onOpen:()=>g({appointment:C}),compact:!0,spanBlocks:I,gridRowStart:O+1},`${C.id}-${xe(e)}`)}),j.map(C=>a.jsx("div",{className:C.hasConflict?"rest-stripe warning":"rest-stripe ok",style:{gridRow:`${C.startIndex+1} / span ${C.spanBlocks}`},onDragOver:R=>{R.preventDefault(),R.dataTransfer.dropEffect="move"},onDrop:R=>{const A=W(R,C);L(R,A)},onClick:R=>{const A=W(R,C);$(A)},children:a.jsx("span",{children:C.label})},C.key))]})}function Kp({appointment:e,onOpen:t,compact:n=!1,spanBlocks:r=1,gridRowStart:l=null}){const s=`${e.student_first_name||""} ${e.student_last_name||""}`.trim()||Yc(e.category),o=r>=3,u=Number(e.student_needs_glasses||0)===1;return a.jsx("button",{className:n?"appointment-card compact-card day-appointment":"appointment-card",onClick:d=>{d.stopPropagation(),t()},style:{"--appointment-color":e.lesson_type_color,"--span-blocks":r,...l?{gridRow:`${l} / span ${r}`}:{}},children:n?a.jsxs(a.Fragment,{children:[a.jsxs("div",{className:"appointment-top",children:[a.jsxs("strong",{className:"appointment-title-row",children:[a.jsx("span",{children:e.lesson_type_name}),u&&a.jsx("span",{className:"vision-badge",title:"Brille erforderlich",children:"[Br]"})]}),a.jsxs("span",{className:"appointment-student",children:[s,u&&a.jsx("span",{className:"vision-inline","aria-hidden":"true",children:" [Brille]"})]}),o&&a.jsxs("span",{className:"appointment-meta",children:[_t(e.start_at)," · ",_t(e.start_at,!0)]})]}),a.jsx("div",{className:"appointment-bottom",children:a.jsxs("span",{children:["bis ",_t(e.end_at,!0)]})})]}):a.jsxs(a.Fragment,{children:[a.jsxs("strong",{className:"appointment-title-row",children:[a.jsx("span",{children:e.lesson_type_name}),u&&a.jsx("span",{className:"vision-badge",title:"Brille erforderlich",children:"[Br]"})]}),a.jsxs("span",{children:[_t(e.start_at,!0)," - ",_t(e.end_at,!0)]}),a.jsxs("span",{children:[s,u&&a.jsx("span",{className:"vision-inline",children:" [Brille]"})]})]})})}function bp({students:e,templates:t,onSave:n,onSaveTraining:r,onDelete:l}){var g;const[i,s]=F.useState(null),[o,u]=F.useState("master"),d=e.find(h=>Number(h.id)===Number(i))||null;return i==="new"?a.jsx(zo,{student:{first_name:"",last_name:"",phone:"",email:"",needs_glasses:0,notes:"",template_id:(g=t[0])==null?void 0:g.id,status:"active",progress:[]},templates:t,activeTab:"master",setActiveTab:u,onBack:()=>s(null),onSave:async h=>{await n(h,null),s(null)},onSaveTraining:null,onDelete:null,isNew:!0}):d?a.jsx(zo,{student:d,templates:t,activeTab:o,setActiveTab:u,onBack:()=>{s(null),u("master")},onSave:async h=>{await n(h,d.id)},onSaveTraining:async h=>{await r(d.id,h)},onDelete:async()=>{window.confirm(`Fahrschueler ${d.first_name} ${d.last_name} wirklich loeschen? Termine bleiben erhalten, verlieren aber den Fahrschueler-Bezug.`)&&(await l(d.id),s(null),u("master"))}}):a.jsxs("section",{className:"list-panel student-page",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Verwaltung"}),a.jsx("h2",{children:"Fahrschueler"}),a.jsx("p",{className:"meta-line",children:"Klick oeffnet die eigene Fahrschueler-Seite mit Stammdaten und Ausbildung."})]}),a.jsx("button",{className:"primary-button",onClick:()=>{u("master"),s("new")},children:"Neu"})]}),a.jsx("div",{className:"entity-list",children:e.map(h=>a.jsxs("button",{className:"entity-item student-list-row",onClick:()=>s(h.id),children:[a.jsxs("strong",{children:[h.first_name," ",h.last_name]}),a.jsxs("span",{children:[h.template_code," · ",h.status]})]},h.id))})]})}function zo({student:e,templates:t,activeTab:n,setActiveTab:r,onBack:l,onSave:i,onSaveTraining:s,onDelete:o,isNew:u=!1}){const[d,g]=F.useState(e),[h,m]=F.useState(e.progress||[]),[x,w]=F.useState(!1);F.useEffect(()=>{g(e),m(e.progress||[])},[e]);async function v(f){f.preventDefault(),await i(d)}async function D(f){if(f.preventDefault(),!!s){w(!0);try{await s(h.map(c=>({requirement_key:c.requirement_key,phase_label:c.phase_label||"",prior_completed_units:Number(c.prior_completed_units||0)})))}finally{w(!1)}}}return a.jsxs("section",{className:"list-panel student-page student-detail-page",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Fahrschueler"}),a.jsx("h2",{children:u?"Neuer Fahrschueler":`${e.first_name} ${e.last_name}`}),a.jsx("p",{className:"meta-line",children:u?"Stammdaten anlegen.":`${e.template_name||e.template_code} · ${e.status}`})]}),a.jsx("button",{type:"button",className:"ghost-button",onClick:l,children:"Zurueck"})]}),!u&&a.jsxs("div",{className:"mode-switch detail-tabs",children:[a.jsx("button",{className:n==="master"?"tab-button active":"tab-button",onClick:()=>r("master"),children:"Stammdaten"}),a.jsx("button",{className:n==="training"?"tab-button active":"tab-button",onClick:()=>r("training"),children:"Ausbildung"})]}),(n==="master"||u)&&a.jsxs("form",{className:"stack-form",onSubmit:v,children:[a.jsxs("label",{children:["Vorname",a.jsx("input",{value:d.first_name||"",onChange:f=>g({...d,first_name:f.target.value}),required:!0})]}),a.jsxs("label",{children:["Nachname",a.jsx("input",{value:d.last_name||"",onChange:f=>g({...d,last_name:f.target.value}),required:!0})]}),a.jsxs("label",{children:["Telefon",a.jsx("input",{value:d.phone||"",onChange:f=>g({...d,phone:f.target.value})})]}),a.jsxs("label",{children:["E-Mail",a.jsx("input",{type:"email",value:d.email||"",onChange:f=>g({...d,email:f.target.value})})]}),a.jsxs("label",{className:"checkbox-row",children:[a.jsx("input",{type:"checkbox",checked:!!d.needs_glasses,onChange:f=>g({...d,needs_glasses:f.target.checked?1:0})}),a.jsx("span",{children:"Benoetigt Brille beim Fahren"})]}),a.jsxs("label",{children:["Fuehrerscheinklasse",a.jsx("select",{value:d.template_id||"",onChange:f=>g({...d,template_id:f.target.value}),children:t.map(f=>a.jsx("option",{value:f.id,children:f.name},f.id))})]}),a.jsxs("label",{children:["Status",a.jsxs("select",{value:d.status||"active",onChange:f=>g({...d,status:f.target.value}),children:[a.jsx("option",{value:"active",children:"aktiv"}),a.jsx("option",{value:"inactive",children:"inaktiv"})]})]}),a.jsxs("label",{children:["Notiz",a.jsx("textarea",{rows:"5",value:d.notes||"",onChange:f=>g({...d,notes:f.target.value})})]}),a.jsxs("div",{className:"modal-actions",children:[!u&&o&&a.jsx("button",{type:"button",className:"danger-button",onClick:o,children:"Fahrschueler loeschen"}),a.jsx("button",{className:"primary-button",type:"submit",children:"Speichern"})]})]}),!u&&n==="training"&&a.jsxs("form",{className:"training-panel",onSubmit:D,children:[(e.progress||[]).length===0&&a.jsx("p",{className:"meta-line",children:"Noch keine Ausbildungsvorgaben hinterlegt."}),pa(h).map(f=>{const c=bc(f.rows);return a.jsxs("div",{className:"training-phase",children:[f.phase_label&&a.jsxs("div",{className:"phase-head",children:[a.jsx("strong",{children:f.phase_label}),a.jsx("span",{className:`phase-state ${c.status}`,children:Jr(c.open).text})]}),f.rows.map(p=>{const y=h.findIndex(C=>C.requirement_key===p.requirement_key),j=Number(p.required_units||0),M=Number(p.completed_units||0),S=Number(p.prior_completed_units||0),T=Number(p.appointment_completed_units||0),$=Number(p.planned_units||0),L=M+$,W=j>0?Math.min(100,Math.round(L/j*100)):100;return a.jsxs("div",{className:"training-row",children:[a.jsxs("div",{className:"training-row-head",children:[a.jsxs("div",{children:[a.jsx("strong",{children:p.label}),a.jsxs("p",{children:["Altbestand ",S," · Termine erledigt ",T," · Geplant ",$," · Soll ",j," · Offen ",p.open_units]})]}),a.jsxs("label",{className:"training-import-field",children:["Schon erledigt",a.jsx("input",{type:"number",min:"0",step:"1",value:p.prior_completed_units??0,onChange:C=>{const R=[...h];R[y]={...p,prior_completed_units:C.target.value},m(R)}})]})]}),a.jsx("div",{className:"training-meter","aria-label":`${W} Prozent`,children:a.jsx("span",{style:{width:`${W}%`}})})]},p.requirement_key)})]},f.phase_label||"default")}),(e.progress||[]).length>0&&a.jsx("div",{className:"modal-actions",children:a.jsx("button",{className:"primary-button",type:"submit",disabled:x,children:x?"Speichert ...":"Ausbildung speichern"})})]})]})}function qp({lessonTypes:e,templates:t,onSave:n,onDelete:r,onReorder:l,onSaveMatrix:i}){const[s,o]=F.useState(null),[u,d]=F.useState([]),[g,h]=F.useState(!1),[m,x]=F.useState(e),w=e.filter(c=>c.category==="student");F.useEffect(()=>{d(t.map(c=>({template_id:c.id,lesson_type_ids:c.lesson_type_ids||[]})))},[t]),F.useEffect(()=>{x(e)},[e]);function v(c,p){d(y=>y.map(j=>{if(j.template_id!==c)return j;const M=j.lesson_type_ids.includes(p);return{...j,lesson_type_ids:M?j.lesson_type_ids.filter(S=>S!==p):[...j.lesson_type_ids,p]}}))}function D(c,p){const y=[...m];if(c<0||p<0||c>=y.length||p>=y.length)return y;const[j]=y.splice(c,1);return y.splice(p,0,j),y}async function f(c){x(c),await l(c.map(p=>p.id))}return a.jsxs(a.Fragment,{children:[a.jsxs(a.Fragment,{children:[a.jsxs("section",{className:"list-panel",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Verwaltung"}),a.jsx("h2",{children:"Stundenarten"}),a.jsx("p",{className:"meta-line",children:"Mit Hoch-/Runter-Knoepfen umsortieren. Farben, Dauer und Kategorie gelten mandantenweit."})]}),a.jsx("button",{className:"primary-button",onClick:()=>o({name:"",color:"#0E5D80",default_duration:45,category:"student",requirement_key:"",is_billable:1,is_counted:1,is_rest_relevant:1,fixed_duration:0,sort_order:m.length+1,system_key:""}),children:"Neu"})]}),a.jsx("div",{className:"entity-list lesson-type-list",children:m.map((c,p)=>a.jsxs("div",{className:"entity-item lesson-type-row",children:[a.jsxs("div",{className:"lesson-type-actions",children:[a.jsx("button",{type:"button",className:"ghost-button compact reorder-button",disabled:p===0,onClick:async y=>{y.stopPropagation(),await f(D(p,p-1))},children:"Hoch"}),a.jsx("button",{type:"button",className:"ghost-button compact reorder-button",disabled:p===m.length-1,onClick:async y=>{y.stopPropagation(),await f(D(p,p+1))},children:"Runter"})]}),a.jsx("span",{className:"color-dot",style:{"--dot-color":c.color||"#67625a"}}),a.jsxs("span",{className:"lesson-type-main",children:[a.jsx("strong",{children:c.name}),a.jsxs("span",{className:"meta-line",children:[c.default_duration," min · ",Yc(c.category)]})]}),a.jsx("button",{type:"button",className:"ghost-button compact",onClick:()=>o(c),children:"Bearbeiten"})]},c.id))})]}),s&&a.jsx(Hl,{title:"Stundenart",initial:s,onClose:()=>o(null),onSubmit:async c=>{c.category=c.category||s.category,c.default_duration=Number(c.default_duration),c.sort_order=Number(c.sort_order),await n(c,s.id),o(null)},onDelete:s.id&&s.system_key!=="private_block"?async()=>{window.confirm(`Stundenart "${s.name}" wirklich loeschen?`)&&(await r(s.id),o(null))}:null,fields:[{name:"name",label:"Bezeichnung"},{name:"color",label:"Farbe",type:"color"},{name:"default_duration",label:"Dauer in Minuten",type:"number"},{name:"category",label:"Kategorie",type:"select",disabled:s.system_key==="private_block",options:[{value:"student",label:"Fahrschueler"},{value:"private",label:"Privat"},{value:"work",label:"Sonstige Arbeiten"},{value:"theory",label:"Theorie"},{value:"blocked",label:"Sperre"}]},{name:"requirement_key",label:"Pflichtbaustein-Key"},{name:"sort_order",label:"Sortierung",type:"number"},{name:"is_billable",label:"Abrechenbar",type:"checkbox"},{name:"is_counted",label:"Zaehlt fuer Pflichtstunden",type:"checkbox"},{name:"is_rest_relevant",label:"Ruhezeit-relevant",type:"checkbox"},{name:"fixed_duration",label:"Feste Dauer",type:"checkbox"}]})]}),w.length>0&&t.length>0&&a.jsxs("section",{className:"list-panel",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Sichtbarkeit"}),a.jsx("h2",{children:"Klassenmatrix"}),a.jsx("p",{className:"meta-line",children:"Fuehrerscheinklassen stehen vertikal, schuelerbezogene Stundenarten horizontal."})]}),a.jsx("button",{className:"primary-button",onClick:async()=>{h(!0);try{await i(u)}finally{h(!1)}},disabled:g,children:g?"Speichert ...":"Matrix speichern"})]}),a.jsx("div",{className:"visibility-matrix-wrap",children:a.jsxs("table",{className:"visibility-matrix",children:[a.jsx("thead",{children:a.jsxs("tr",{children:[a.jsx("th",{children:"Fuehrerscheinklasse"}),w.map(c=>a.jsx("th",{children:c.name},c.id))]})}),a.jsx("tbody",{children:t.map(c=>{const p=u.find(y=>y.template_id===c.id)||{lesson_type_ids:[]};return a.jsxs("tr",{children:[a.jsx("th",{children:c.name}),w.map(y=>a.jsx("td",{children:a.jsx("input",{type:"checkbox",checked:p.lesson_type_ids.includes(y.id),onChange:()=>v(c.id,y.id)})},y.id))]},c.id)})})]})})]})]})}function Yp({instructors:e,onSave:t}){const[n,r]=F.useState(null);return a.jsx(Vl,{title:"Fahrlehrer",subtitle:"Mehrere Ressourcen werden direkt im Kalender abgebildet.",items:e,columns:l=>`${l.first_name} ${l.last_name} · ${l.is_active?"aktiv":"inaktiv"} · Anfahrt ${Number(l.pre_start_buffer_minutes||0)} min`,onEdit:l=>r(l),onCreate:()=>r({first_name:"",last_name:"",color:"#0E5D80",notes:"",is_active:1,pre_start_buffer_minutes:0}),form:n&&a.jsx(Hl,{title:"Fahrlehrer",initial:n,onClose:()=>r(null),onSubmit:async l=>{await t(l,n.id),r(null)},fields:[{name:"first_name",label:"Vorname"},{name:"last_name",label:"Nachname"},{name:"color",label:"Farbe",type:"color"},{name:"pre_start_buffer_minutes",label:"Anfahrtszeit vor erstem Termin (Minuten)",type:"number"},{name:"notes",label:"Notiz",type:"textarea"},{name:"is_active",label:"Aktiv",type:"checkbox"}]})})}function Xp({templates:e,lessonTypes:t,onSave:n}){const[r,l]=F.useState(null);return a.jsx(Vl,{title:"Klassenvorlagen",subtitle:"Kombinationen wie C + CE sind eigene Vorlagen mit festen Sollwerten.",items:e,columns:i=>`${i.code} · ${Zr(i,t).map(s=>`${s.label}: ${s.required_units}`).join(", ")||"keine Pflichtstunden"}`,onEdit:i=>{const s=Bp(i);l({...s,requirements:Zr(s,t)})},onCreate:()=>l({code:"",name:"",is_combination:0,lesson_type_ids:t.filter(i=>i.category==="student").map(i=>i.id),requirements:Zr({lesson_type_ids:t.filter(i=>i.category==="student").map(i=>i.id),requirements:[]},t)}),form:r&&a.jsx(em,{template:r,setTemplate:l,lessonTypes:t,onClose:()=>l(null),onSubmit:async()=>{await n(r,r.id),l(null)}})})}function Gp({users:e,tenants:t,currentUser:n,onSave:r}){const[l,i]=F.useState(null);return a.jsx(Vl,{title:"Benutzer",subtitle:"Rollen und Zugriff werden serverseitig geprueft.",items:e,columns:s=>`${s.first_name} ${s.last_name} · ${s.role} · ${s.email}${s.tenant_name?` · ${s.tenant_name}`:""}`,onEdit:s=>i(s),onCreate:()=>{var s;return i({tenant_id:n.tenant_id||((s=t[0])==null?void 0:s.id)||"",role:"dispatcher",email:"",password:"",first_name:"",last_name:"",is_active:1})},form:l&&a.jsx(Hl,{title:"Benutzer",initial:l,onClose:()=>i(null),onSubmit:async s=>{await r(s,l.id),i(null)},fields:[...n.role==="superadmin"?[{name:"tenant_id",label:"Mandant",type:"select",options:t.map(s=>({value:s.id,label:s.name}))}]:[],{name:"role",label:"Rolle",type:"select",options:[{value:"tenant_admin",label:"Mandanten-Admin"},{value:"dispatcher",label:"Disponent"},{value:"instructor",label:"Fahrlehrer"},...n.role==="superadmin"?[{value:"superadmin",label:"Superadmin"}]:[]]},{name:"email",label:"E-Mail"},{name:"password",label:"Passwort"},{name:"first_name",label:"Vorname"},{name:"last_name",label:"Nachname"},{name:"is_active",label:"Aktiv",type:"checkbox"}]})})}function Zp({tenants:e,onSave:t}){const[n,r]=F.useState(null);return a.jsx(Vl,{title:"Mandanten",subtitle:"Superadmin-Verwaltung fuer Mandantenstatus und Zeitzone.",items:e,columns:l=>`${l.name} · ${l.timezone} · ${l.is_active?"aktiv":"inaktiv"}`,onEdit:l=>r(l),onCreate:()=>r({name:"",timezone:"Europe/Berlin",is_active:1}),form:n&&a.jsx(Hl,{title:"Mandant",initial:n,onClose:()=>r(null),onSubmit:async l=>{await t(l,n.id),r(null)},fields:[{name:"name",label:"Name"},{name:"timezone",label:"Zeitzone"},{name:"federal_state",label:"Bundesland",type:"select",options:Wc},{name:"location_label",label:"Ort / Filiale"},{name:"latitude",label:"Breitengrad",type:"number"},{name:"longitude",label:"Laengengrad",type:"number"},{name:"is_active",label:"Aktiv",type:"checkbox"}]})})}function Jp({tenant:e,onSave:t}){const[n,r]=F.useState(e),[l,i]=F.useState((e==null?void 0:e.location_label)||""),[s,o]=F.useState([]),[u,d]=F.useState(!1),[g,h]=F.useState("");F.useEffect(()=>{r(e),i((e==null?void 0:e.location_label)||""),o([]),h("")},[e]);const m=Number(n==null?void 0:n.latitude),x=Number(n==null?void 0:n.longitude),w=Number.isFinite(m)&&Number.isFinite(x),v=.025,D=w?`https://www.openstreetmap.org/export/embed.html?bbox=${x-v}%2C${m-v}%2C${x+v}%2C${m+v}&layer=mapnik&marker=${m}%2C${x}`:"";async function f(){const c=l.trim();if(!c){o([]),h("Bitte einen Ort oder eine Adresse eingeben.");return}d(!0),h("");try{const p=await fetch(`https://nominatim.openstreetmap.org/search?format=jsonv2&limit=5&q=${encodeURIComponent(c)}`);if(!p.ok)throw new Error(`Ortssuche fehlgeschlagen (${p.status})`);const y=await p.json();o(y),y.length||h("Kein passender Ort gefunden.")}catch(p){h(p.message),o([])}finally{d(!1)}}return a.jsxs("section",{className:"list-panel",children:[a.jsx("div",{className:"section-head",children:a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Fahrschule"}),a.jsx("h2",{children:"Standort und Zeit"}),a.jsx("p",{className:"meta-line",children:"Ort, Bundesland und Zeitzone steuern Sonnenzeiten, Feiertage und die Hell-Dunkel-Markierung im Kalender."})]})}),a.jsxs("form",{className:"stack-form",onSubmit:async c=>{c.preventDefault(),await t(n)},children:[a.jsxs("label",{children:["Name",a.jsx("input",{value:n.name||"",onChange:c=>r({...n,name:c.target.value})})]}),a.jsxs("label",{children:["Zeitzone",a.jsx("input",{value:n.timezone||"",onChange:c=>r({...n,timezone:c.target.value})})]}),a.jsxs("label",{children:["Bundesland",a.jsx("select",{value:n.federal_state||"BE",onChange:c=>r({...n,federal_state:c.target.value}),children:Wc.map(c=>a.jsx("option",{value:c.value,children:c.label},c.value))})]}),a.jsxs("label",{children:["Ort / Filiale",a.jsxs("div",{className:"location-search",children:[a.jsx("input",{value:l,onChange:c=>{const p=c.target.value;i(p),r({...n,location_label:p})},placeholder:"z. B. Duisburg, Bahnhofstrasse 12"}),a.jsx("button",{type:"button",className:"ghost-button",onClick:f,disabled:u,children:u?"Suche...":"Ort suchen"})]})]}),g&&a.jsx("p",{className:"form-error",children:g}),s.length>0&&a.jsx("div",{className:"location-result-list",children:s.map(c=>a.jsxs("button",{type:"button",className:"location-result",onClick:()=>{r({...n,location_label:c.display_name,latitude:Number(c.lat),longitude:Number(c.lon)}),i(c.display_name),o([]),h("")},children:[a.jsx("strong",{children:c.display_name}),a.jsxs("span",{children:[Number(c.lat).toFixed(6)," / ",Number(c.lon).toFixed(6)]})]},`${c.place_id}-${c.lat}-${c.lon}`))}),a.jsxs("div",{className:"coordinate-grid",children:[a.jsxs("label",{children:["Breitengrad",a.jsx("input",{type:"number",step:"0.000001",value:n.latitude??"",onChange:c=>r({...n,latitude:c.target.value})})]}),a.jsxs("label",{children:["Laengengrad",a.jsx("input",{type:"number",step:"0.000001",value:n.longitude??"",onChange:c=>r({...n,longitude:c.target.value})})]})]}),w&&a.jsxs("div",{className:"map-preview",children:[a.jsx("iframe",{title:"Standortkarte",src:D,loading:"lazy",referrerPolicy:"no-referrer-when-downgrade"}),a.jsx("p",{className:"meta-line",children:"Karte zeigt den derzeit gespeicherten Standort."})]}),a.jsxs("div",{className:"location-hint",children:[a.jsx("strong",{children:"Hinweis"}),a.jsx("span",{children:"Feiertage richten sich nach dem Bundesland. Sonnenaufgang und Sonnenuntergang richten sich nach dem gespeicherten Standort."})]}),a.jsx("button",{className:"primary-button",type:"submit",children:"Speichern"})]})]})}function Vl({title:e,subtitle:t,items:n,columns:r,onEdit:l,onCreate:i,form:s}){return a.jsxs(a.Fragment,{children:[a.jsxs("section",{className:"list-panel",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Verwaltung"}),a.jsx("h2",{children:e}),a.jsx("p",{className:"meta-line",children:t})]}),a.jsx("button",{className:"primary-button",onClick:i,children:"Neu"})]}),a.jsx("div",{className:"entity-list",children:n.map(o=>a.jsx("button",{className:"entity-item",onClick:()=>l(o),children:r(o)},o.id))})]}),s]})}function Hl({title:e,initial:t,fields:n,onSubmit:r,onClose:l,onDelete:i=null}){async function s(o){o.preventDefault();const u=new FormData(o.currentTarget),d=Object.fromEntries(u.entries());for(const g of n)g.type==="checkbox"&&(d[g.name]=u.get(g.name)?1:0);await r(d)}return a.jsx("div",{className:"modal-shell",children:a.jsxs("form",{className:"modal-card stack-form",onSubmit:s,children:[a.jsxs("div",{className:"section-head",children:[a.jsx("h2",{children:e}),a.jsx("button",{type:"button",className:"ghost-button",onClick:l,children:"Schliessen"})]}),n.map(o=>a.jsxs("label",{children:[o.label,o.type==="textarea"?a.jsx("textarea",{name:o.name,defaultValue:t[o.name]||"",rows:"4",disabled:o.disabled}):o.type==="select"?a.jsx("select",{name:o.name,defaultValue:String(t[o.name]??""),disabled:o.disabled,children:o.options.map(u=>a.jsx("option",{value:u.value,children:u.label},u.value))}):o.type==="checkbox"?a.jsx("input",{name:o.name,type:"checkbox",defaultChecked:!!t[o.name],disabled:o.disabled}):a.jsx("input",{name:o.name,type:o.type||"text",defaultValue:t[o.name]||"",disabled:o.disabled})]},o.name)),a.jsxs("div",{className:"modal-actions",children:[i&&a.jsx("button",{type:"button",className:"danger-button",onClick:i,children:"Loeschen"}),a.jsx("button",{className:"primary-button",type:"submit",children:"Speichern"})]})]})})}function em({template:e,setTemplate:t,lessonTypes:n,onClose:r,onSubmit:l}){function i(s){const o={...e,is_combination:s?1:0};t({...o,requirements:Zr(o,n)})}return a.jsx("div",{className:"modal-shell",children:a.jsxs("form",{className:"modal-card stack-form",onSubmit:s=>{s.preventDefault(),l()},children:[a.jsxs("div",{className:"section-head",children:[a.jsx("h2",{children:"Klassenvorlage"}),a.jsx("button",{type:"button",className:"ghost-button",onClick:r,children:"Schliessen"})]}),a.jsxs("label",{children:["Code",a.jsx("input",{value:e.code,onChange:s=>t({...e,code:s.target.value})})]}),a.jsxs("label",{children:["Name",a.jsx("input",{value:e.name,onChange:s=>t({...e,name:s.target.value})})]}),a.jsxs("label",{children:["Kombination",a.jsx("input",{type:"checkbox",checked:!!e.is_combination,onChange:s=>i(s.target.checked)})]}),pa(e.requirements).map(s=>a.jsxs("div",{className:"requirement-phase",children:[s.phase_label&&a.jsx("h3",{children:s.phase_label}),a.jsx("div",{className:"requirements-grid",children:s.rows.map(o=>{const u=e.requirements.findIndex(d=>d.requirement_key===o.requirement_key);return a.jsxs("div",{className:"requirement-editor",children:[a.jsxs("strong",{className:"requirement-title",children:[a.jsx("span",{className:"color-dot",style:{"--dot-color":o.color||"#67625a"}}),o.label]}),a.jsx("input",{type:"number",min:"0",value:o.required_units,onChange:d=>{const g=[...e.requirements];g[u]={...g[u],required_units:Number(d.target.value)},t({...e,requirements:g})}})]},o.requirement_key)})})]},s.phase_label||"default")),a.jsx("button",{className:"primary-button",type:"submit",children:"Speichern"})]})})}function Nl({label:e,value:t,onChange:n}){const[r,l]=F.useState(!1),[i,s]=F.useState("hour"),o=Number((t||"00:00").split(":")[0]||0),u=Number((t||"00:00").split(":")[1]||0);F.useEffect(()=>{r&&s("hour")},[r]);const d=Array.from({length:12},(v,D)=>D),g=Array.from({length:12},(v,D)=>D+12),h=Array.from({length:12},(v,D)=>D*5);function m(v,D){const f=v/12*Math.PI*2-Math.PI/2,c=Math.cos(f)*D,p=Math.sin(f)*D;return{left:`calc(50% + ${c}px)`,top:`calc(50% + ${p}px)`}}function x(v){n(`${String(v).padStart(2,"0")}:${String(u).padStart(2,"0")}`),s("minute")}function w(v){n(`${String(o).padStart(2,"0")}:${String(v).padStart(2,"0")}`),l(!1)}return a.jsxs("div",{className:"time-dial-field",children:[a.jsxs("button",{type:"button",className:"time-dial-button",onClick:()=>l(v=>!v),children:[a.jsx("span",{children:e}),a.jsx("strong",{children:t})]}),r&&a.jsxs("div",{className:"time-dial-popover",children:[a.jsxs("div",{className:"time-dial-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Uhrzeit"}),a.jsx("strong",{children:t})]}),a.jsx("button",{type:"button",className:"ghost-button compact",onClick:()=>l(!1),children:"Schliessen"})]}),a.jsxs("div",{className:"mode-switch",children:[a.jsx("button",{type:"button",className:i==="hour"?"tab-button active compact":"tab-button compact",onClick:()=>s("hour"),children:"Stunde"}),a.jsx("button",{type:"button",className:i==="minute"?"tab-button active compact":"tab-button compact",onClick:()=>s("minute"),children:"Minute"})]}),a.jsxs("div",{className:"time-dial-face",children:[a.jsx("div",{className:"time-dial-center",children:i==="hour"?String(o).padStart(2,"0"):String(u).padStart(2,"0")}),i==="hour"?a.jsxs(a.Fragment,{children:[g.map((v,D)=>a.jsx("button",{type:"button",className:["time-dial-point","outer-ring",v===o?"active":""].filter(Boolean).join(" "),style:m(D,112),onClick:()=>x(v),children:String(v).padStart(2,"0")},`outer-${v}`)),d.map((v,D)=>a.jsx("button",{type:"button",className:["time-dial-point","inner-ring",v===o?"active":""].filter(Boolean).join(" "),style:m(D,72),onClick:()=>x(v),children:String(v).padStart(2,"0")},`inner-${v}`))]}):h.map((v,D)=>a.jsx("button",{type:"button",className:["time-dial-point",v===u?"active":""].filter(Boolean).join(" "),style:m(D,108),onClick:()=>w(v),children:String(v).padStart(2,"0")},`${i}-${v}`))]}),a.jsxs("label",{className:"stack-form",children:["Direkt eingeben",a.jsx("input",{type:"time",step:"300",value:t,onChange:v=>n(v.target.value)})]})]})]})}function tm({draft:e,setDraft:t,lessonTypes:n,instructors:r,students:l,templates:i,onPickStudent:s,onSubmit:o,onDelete:u,onClose:d,isEditing:g}){const h=Up(n,e.student_id,l),m=n.find(N=>Number(N.id)===Number(e.lesson_type_id)),x=h.some(N=>Number(N.id)===Number(e.lesson_type_id))?h:m?[m,...h]:h,w=l.find(N=>Number(N.id)===Number(e.student_id)),v=i.find(N=>Number(N.id)===Number(w==null?void 0:w.template_id)),D=r.find(N=>Number(N.id)===Number(e.instructor_id)),f=v&&Number(v.is_combination)?Kc(v):[],c=ys(m,e.start_at,e.end_at),p=Si(e.start_at),y=Br(e.start_at),j=Br(e.end_at),M=Rp(w,D,e.start_at,e.end_at),S=Fp(w==null?void 0:w.phone),T=S&&M?`https://wa.me/${S}?text=${encodeURIComponent(M)}`:"",$=S&&M?`sms:${S}?body=${encodeURIComponent(M)}`:"",L=w!=null&&w.email&&M?`mailto:${encodeURIComponent(w.email)}?subject=${encodeURIComponent("Fahrstunde")}&body=${encodeURIComponent(M)}`:"";F.useEffect(()=>{x.length&&(x.some(N=>Number(N.id)===Number(e.lesson_type_id))||t(N=>({...N,lesson_type_id:x[0].id})))},[e.lesson_type_id,x,t]),F.useEffect(()=>{e.end_at&&Ul(e.start_at,e.end_at)>0||t(N=>({...N,end_at:_i(N.start_at,Math.max(1,Number((m==null?void 0:m.default_duration)||45)))}))},[m==null?void 0:m.default_duration,e.end_at,e.start_at,t]);function W(N){N&&t(V=>({...V,start_at:jt(N,Br(V.start_at)),end_at:jt(N,Br(V.end_at))}))}function C(N){t(V=>{const J=jt(Si(V.start_at),N);return{...V,start_at:J,end_at:_i(J,ys(m,V.start_at,V.end_at).duration)}})}function R(N){t(V=>({...V,end_at:jt(Si(V.start_at),N)}))}function A(N){const V=Number((m==null?void 0:m.fixed_duration)||0)===1?Math.max(1,Number((m==null?void 0:m.default_duration)||45)):Math.max(1,Number((m==null?void 0:m.default_duration)||45))*N;t(J=>({...J,units:N,end_at:_i(J.start_at,V)}))}return a.jsx("div",{className:"modal-shell",children:a.jsxs("form",{className:"modal-card stack-form",onSubmit:o,children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Termin"}),a.jsx("h2",{children:g?"Termin bearbeiten":"Termin einplanen"})]}),a.jsx("button",{type:"button",className:"ghost-button",onClick:d,children:"Schliessen"})]}),a.jsxs("label",{children:["Fahrschueler",a.jsxs("select",{name:"student_id",value:e.student_id,onChange:N=>t({...e,student_id:N.target.value}),children:[a.jsx("option",{value:"",children:"Kein Fahrschueler"}),l.map(N=>a.jsxs("option",{value:N.id,children:[N.first_name," ",N.last_name]},N.id))]})]}),a.jsxs("label",{children:["Stundenart",a.jsx("select",{name:"lesson_type_id",value:e.lesson_type_id,onChange:N=>t({...e,lesson_type_id:N.target.value}),children:x.map(N=>a.jsx("option",{value:N.id,children:N.name},N.id))})]}),f.length>0&&a.jsxs("label",{children:["Ausbildungsteil",a.jsx("select",{name:"requirement_phase",value:e.requirement_phase||f[0],onChange:N=>t({...e,requirement_phase:N.target.value}),children:f.map(N=>a.jsx("option",{value:N,children:N},N))})]}),a.jsxs("label",{children:["Fahrlehrer",a.jsx("select",{name:"instructor_id",value:e.instructor_id,onChange:N=>t({...e,instructor_id:N.target.value}),children:r.map(N=>a.jsxs("option",{value:N.id,children:[N.first_name," ",N.last_name]},N.id))})]}),a.jsxs("div",{className:"time-editor-grid",children:[a.jsxs("label",{children:["Datum",a.jsx("input",{type:"date",value:p,onChange:N=>W(N.target.value)})]}),a.jsxs("div",{className:"stack-form",children:[a.jsx("span",{children:"Startzeit"}),a.jsx(Nl,{label:"Start",value:y,onChange:C})]}),a.jsxs("div",{className:"stack-form",children:[a.jsx("span",{children:"Endzeit"}),a.jsx(Nl,{label:"Ende",value:j,onChange:R})]})]}),a.jsxs("div",{className:"duration-preset-panel",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Dauer"}),a.jsx("h2",{children:"Fahrstunden"})]}),a.jsxs("span",{className:"badge",children:[c.duration," min · ",c.units," FS"]})]}),a.jsxs("div",{className:"duration-preset-row",children:[[1,2,3,4].map(N=>a.jsxs("button",{type:"button",className:c.units===N?"tab-button active compact":"tab-button compact",onClick:()=>A(N),children:[N," FS"]},N)),Number((m==null?void 0:m.fixed_duration)||0)===1&&a.jsxs("button",{type:"button",className:"ghost-button compact",onClick:()=>A(1),children:["Standard ",m==null?void 0:m.default_duration," min"]})]}),a.jsx("p",{className:"hint-line",children:"Start und Ende lassen sich direkt ueber die Uhr setzen. Die angerechneten Fahrstunden werden daraus automatisch berechnet."})]}),a.jsxs("div",{className:"share-panel",children:[a.jsx("div",{className:"section-head",children:a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Benachrichtigung"}),a.jsx("h2",{children:"Termin teilen"})]})}),a.jsxs("div",{className:"share-actions",children:[a.jsxs("a",{className:T?"share-button":"share-button disabled",href:T||void 0,target:"_blank",rel:"noreferrer","aria-disabled":!T,onClick:N=>{T||N.preventDefault()},children:[a.jsx("span",{className:"share-badge whatsapp",children:"WA"}),"WhatsApp"]}),a.jsxs("a",{className:$?"share-button":"share-button disabled",href:$||void 0,"aria-disabled":!$,onClick:N=>{$||N.preventDefault()},children:[a.jsx("span",{className:"share-badge sms",children:"SMS"}),"SMS"]}),a.jsxs("a",{className:L?"share-button":"share-button disabled",href:L||void 0,"aria-disabled":!L,onClick:N=>{L||N.preventDefault()},children:[a.jsx("span",{className:"share-badge email",children:"@"}),"E-Mail"]})]}),a.jsx("p",{className:"hint-line",children:w?"Die Nachricht wird mit Datum, Start- und Endzeit vorbefuellt.":"Fahrschueler waehlen, damit Kontakt-Buttons aktiv werden."})]}),a.jsxs("label",{children:["Status",a.jsxs("select",{name:"status",value:e.status,onChange:N=>t({...e,status:N.target.value}),children:[a.jsx("option",{value:"planned",children:"geplant"}),a.jsx("option",{value:"completed",children:"erledigt"}),a.jsx("option",{value:"cancelled",children:"abgesagt"})]})]}),a.jsxs("label",{children:["Notiz",a.jsx("textarea",{name:"notes",rows:"4",value:e.notes,onChange:N=>t({...e,notes:N.target.value})})]}),a.jsxs("div",{className:"modal-actions",children:[g&&a.jsx("button",{type:"button",className:"danger-button",onClick:u,children:"Termin loeschen"}),a.jsx("button",{type:"button",className:"ghost-button",onClick:()=>s==null?void 0:s(e.student_id),disabled:!e.student_id,children:"Fahrschueler waehlen"}),a.jsx("button",{className:"primary-button",type:"submit",children:"Speichern"})]})]})})}ki.createRoot(document.getElementById("root")).render(a.jsx(md.StrictMode,{children:a.jsx(Vp,{})})); +Trotzdem speichern?`)&&(ke.warning_acknowledged=!0,f?await ye.patch(`https://api.fahrschultermin.de/api/v1/appointments/${f}`,ke):await ye.post("https://api.fahrschultermin.de/api/v1/appointments",ke),D(null),c(null),await $(t.user,S.start,S.end)):y(Me.message)}}async function Ql(){f&&window.confirm("Termin wirklich loeschen?")&&(await ye.delete(`https://api.fahrschultermin.de/api/v1/appointments/${f}`),D(null),c(null),await $(t.user,S.start,S.end))}function wr({lessonTypeId:E,day:B,hour:me,minute:ke=0,instructorId:Me,appointment:he=null,startAt:Kl=null,endAt:ma=null}){var va,ga;if(he){c(he.id),D({student_id:he.student_id||"",lesson_type_id:he.lesson_type_id,instructor_id:he.instructor_id,start_at:Un(new Date(he.start_at)),end_at:Un(new Date(he.end_at)),units:he.units,notes:he.notes||"",status:he.status,requirement_phase:(he.counted_requirement_key||"").includes("::")?he.counted_requirement_key.split("::")[0]:""});return}const bl=Kl?new Date(Kl):new Date(B);Kl||bl.setHours(me,ke,0,0);const ha=E||((va=W[0])==null?void 0:va.id)||"",ql=W.find(Gc=>Number(Gc.id)===Number(ha)),Xc=ma?new Date(ma):new Date(bl.getTime()+Math.max(1,Number((ql==null?void 0:ql.default_duration)||45))*60*1e3);c(null),D({student_id:x==="student"&&h||"",lesson_type_id:ha,instructor_id:Me||((ga=C[0])==null?void 0:ga.id)||"",start_at:Un(bl),end_at:Un(Xc),units:1,notes:"",status:"planned",requirement_phase:""})}return i?a.jsx("div",{className:"app-shell",children:a.jsx("div",{className:"loading-panel",children:"Lade Anwendung ..."})}):t.user?a.jsxs("div",{className:"app-shell",children:[a.jsxs("header",{className:"topbar",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Fahrschul-Kalender"}),a.jsx("h1",{children:"DriveTime Planner"}),a.jsx("p",{className:"meta-line",children:t.user.role==="superadmin"?"Systemweit":`${(_=r==null?void 0:r.tenant)==null?void 0:_.name} · ${(b=r==null?void 0:r.tenant)==null?void 0:b.timezone}`})]}),a.jsxs("div",{className:"topbar-actions",children:[a.jsxs("span",{className:"badge",children:[t.user.first_name," ",t.user.last_name]}),a.jsx("button",{className:"ghost-button",onClick:()=>M(E=>E==="dark"?"light":"dark"),children:j==="dark"?"Hell":"Dunkel"}),a.jsx("button",{className:"ghost-button",onClick:Y,children:"Logout"})]})]}),p&&a.jsx("div",{className:"flash-error",children:p}),a.jsx("nav",{className:"tab-strip",children:Q.map(E=>a.jsx("button",{className:E.id===o?"tab-button active":"tab-button",onClick:()=>u(E.id),children:E.label},E.id))}),a.jsxs("main",{className:"content-grid",children:[o==="calendar"&&t.user.role!=="superadmin"&&a.jsx(Wp,{isMobile:e,weekStart:d,setWeekStart:g,range:S,instructors:C,students:L,lessonTypes:W,appointments:V,nextAppointment:J,sunTimes:P,dayMeta:O,selectedStudent:I,selectedStudentId:h,setSelectedStudentId:m,plannerMode:x,setPlannerMode:w,progress:I?q[I.id]||[]:[],onCreate:wr}),o==="students"&&t.user.role!=="superadmin"&&a.jsx(bp,{students:L,templates:R,onSave:(E,B)=>Fe("https://api.fahrschultermin.de/api/v1/students",E,B),onSaveTraining:vt,onDelete:E=>_e("https://api.fahrschultermin.de/api/v1/students",E)}),o==="lesson-types"&&t.user.role!=="superadmin"&&a.jsx(qp,{lessonTypes:W,templates:R,onSave:(E,B)=>Fe("https://api.fahrschultermin.de/api/v1/lesson-types",E,B),onDelete:E=>_e("https://api.fahrschultermin.de/api/v1/lesson-types",E),onReorder:async E=>{await ye.post("https://api.fahrschultermin.de/api/v1/lesson-types/reorder",{lesson_type_ids:E}),await $(t.user,S.start,S.end)},onSaveMatrix:async E=>{await ye.post("https://api.fahrschultermin.de/api/v1/lesson-types/visibility",{rows:E}),await $(t.user,S.start,S.end)}}),o==="templates"&&t.user.role!=="superadmin"&&a.jsx(Xp,{templates:R,lessonTypes:W,onSave:(E,B)=>Fe("https://api.fahrschultermin.de/api/v1/license-class-templates",E,B)}),o==="instructors"&&t.user.role!=="superadmin"&&a.jsx(Yp,{instructors:C,onSave:(E,B)=>Fe("https://api.fahrschultermin.de/api/v1/instructors",E,B)}),o==="users"&&a.jsx(Gp,{users:A,tenants:N,currentUser:t.user,onSave:(E,B)=>Fe("https://api.fahrschultermin.de/api/v1/users",E,B)}),o==="tenant-profile"&&t.user.role==="tenant_admin"&&(r==null?void 0:r.tenant)&&a.jsx(Jp,{tenant:r.tenant,onSave:async E=>{await ye.patch("https://api.fahrschultermin.de/api/v1/tenant/profile",E),await $(t.user,S.start,S.end)}}),o==="tenants"&&t.user.role==="superadmin"&&a.jsx(Zp,{tenants:N,onSave:(E,B)=>Fe("https://api.fahrschultermin.de/api/v1/tenants",E,B)})]}),v&&a.jsx(tm,{draft:v,setDraft:D,lessonTypes:W,instructors:C,students:L,templates:R,onPickStudent:E=>{E&&(m(Number(E)),w("student"),D(null),c(null))},onSubmit:Wl,onDelete:Ql,onClose:()=>{D(null),c(null)},isEditing:!!f})]}):a.jsx(Hp,{onSubmit:it,error:p})}function Hp({onSubmit:e,error:t}){async function n(r){r.preventDefault();const l=new FormData(r.currentTarget);await e({email:l.get("email"),password:l.get("password")})}return a.jsx("div",{className:"login-shell",children:a.jsxs("div",{className:"login-card",children:[a.jsx("p",{className:"eyebrow",children:"Mandantenfaehig · Kalenderfokus"}),a.jsx("h1",{children:"Dispositionsfenster"}),a.jsx("p",{className:"login-copy",children:"Planung fuer Fahrstunden, Vorlagen und Ruhezeitwarnungen in einer kompakten Leitstelle."}),a.jsxs("form",{onSubmit:n,className:"stack-form",children:[a.jsxs("label",{children:["E-Mail",a.jsx("input",{name:"email",type:"email",defaultValue:"admin@nordring.test",required:!0})]}),a.jsxs("label",{children:["Passwort",a.jsx("input",{name:"password",type:"password",defaultValue:"secret123",required:!0})]}),t&&a.jsx("div",{className:"inline-error",children:t}),a.jsx("button",{className:"primary-button",type:"submit",children:"Einloggen"})]}),a.jsx("p",{className:"hint-line",children:"Demo: `admin@nordring.test` / `secret123` oder `superadmin@example.com` / `secret123`"})]})})}function Wp(e){var wr;const{isMobile:t,weekStart:n,setWeekStart:r,range:l,instructors:i,students:s,lessonTypes:o,appointments:u,nextAppointment:d,sunTimes:g,dayMeta:h,selectedStudent:m,selectedStudentId:x,setSelectedStudentId:w,plannerMode:v,setPlannerMode:D,progress:f,onCreate:c}=e,p=t?[new Date]:l.days,[y,j]=F.useState(()=>new Date),[M,S]=F.useState(""),[T,$]=F.useState(!1),[L,W]=F.useState(null),[C,R]=F.useState(((wr=i[0])==null?void 0:wr.id)||null),[A,N]=F.useState(()=>xe(new Date)),[V,J]=F.useState("08:00"),[P,O]=F.useState("10:00"),I=[{id:"student",label:"Fahrschueler"},{id:"private",label:"Privat"},{id:"work",label:"Sonstige Arbeiten"},{id:"theory",label:"Theorie"}],q=Array.from({length:96},(_,b)=>{const E=Math.floor(b/4),B=b%4*15;return{hour:E,minute:B,label:`${String(E).padStart(2,"0")}:${String(B).padStart(2,"0")}`,rowClass:B===0?"full-hour":""}}),Q=o.filter(_=>v==="student"?_.category==="student"&&qc(_,m==null?void 0:m.template_id):v==="private"?_.category==="private":v==="work"?_.category==="work"||_.category==="other"||_.category==="blocked":v==="theory"?_.category==="theory":!1),it=s.filter(_=>`${_.first_name} ${_.last_name} ${_.template_code}`.toLowerCase().includes(M.toLowerCase())).slice(0,8);F.useEffect(()=>{const _=window.setInterval(()=>{j(new Date)},6e4);return()=>window.clearInterval(_)},[]),F.useEffect(()=>{m&&S(`${m.first_name} ${m.last_name}`)},[x,m]),F.useEffect(()=>{if(!i.length){R(null);return}i.some(_=>_.id===C)||R(i[0].id)},[i,C]),F.useEffect(()=>{if(Q.length===0){W(null);return}Q.some(_=>_.id===L)||W(Q[0].id)},[L,Q]);const Y=i.find(_=>_.id===C)||i[0]||null;Q.find(_=>_.id===L);const Fe=A&&Ul(jt(A,V),jt(A,P))>0,_e=Y?u.filter(_=>_.instructor_id===Y.id):[],vt=Math.max(0,Number((Y==null?void 0:Y.pre_start_buffer_minutes)||0)),Wl=Array.from(_e.filter(_=>Number(_.is_rest_relevant)===1).reduce((_,b)=>{const E=new Date(b.end_at),B=`${E.getFullYear()}-${String(E.getMonth()+1).padStart(2,"0")}-${String(E.getDate()).padStart(2,"0")}`,me=_.get(B);return(!me||E>me.endAt)&&_.set(B,{endAt:E}),_},new Map).values()).map(({endAt:_})=>{const b=_,E=new Date(_.getTime()+11*60*60*1e3),B=_e.some(me=>{const ke=new Date(me.start_at);return ke>b&&ke_.start-b.start);function Ql(){!L||!(Y!=null&&Y.id)||!Fe||c({lessonTypeId:L,instructorId:Y.id,startAt:jt(A,V),endAt:jt(A,P)})}return a.jsxs(a.Fragment,{children:[a.jsxs("section",{className:"calendar-stage",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Einsatzfenster"}),a.jsxs("h2",{children:[_t(l.start)," - ",_t(Gn(l.end,-1))]})]}),a.jsxs("div",{className:"toolbar",children:[a.jsxs("label",{className:"calendar-filter",children:["Fahrlehrer",a.jsx("select",{value:C||"",onChange:_=>R(Number(_.target.value)),children:i.map(_=>a.jsxs("option",{value:_.id,children:[_.first_name," ",_.last_name]},_.id))})]}),a.jsx("button",{className:"ghost-button",onClick:()=>r(Gn(n,-7)),children:"Zurueck"}),a.jsx("button",{className:"ghost-button",onClick:()=>r(vs()),children:"Heute"}),d&&a.jsx("button",{className:"ghost-button",onClick:()=>r(vs(new Date(d.start_at))),children:"Naechster Termin"}),a.jsx("button",{className:"ghost-button",onClick:()=>r(Gn(n,7)),children:"Weiter"})]})]}),a.jsxs("div",{className:"calendar-grid",children:[a.jsx("div",{className:"calendar-head time-col",children:"Zeit"}),p.map(_=>(()=>{var E,B,me,ke,Me;const b=Lp(_e,_);return a.jsxs("div",{className:["calendar-head day-col",Qc(_,y)?"past-head":"",(E=h[xe(_)])!=null&&E.holidayName?"holiday-head":"",(B=h[xe(_)])!=null&&B.isSunday?"sunday-head":""].filter(Boolean).join(" "),children:[a.jsx("strong",{children:_t(_)}),a.jsxs("div",{className:"day-summary",children:[a.jsxs("span",{className:"day-summary-line",children:[a.jsxs("strong",{children:[b.taughtUnits," UE"]}),a.jsx("span",{children:"geschult"})]}),a.jsxs("span",{className:"day-summary-line",children:[a.jsx("strong",{children:To(b.workSpanMinutes)}),a.jsx("span",{children:"Arbeitszeit"})]}),a.jsxs("span",{className:`day-summary-line break-line ${b.breakStatus}`,children:[a.jsx("strong",{children:To(b.breakMinutes)}),a.jsxs("span",{children:["(Pause ",b.requiredBreakMinutes," min)"]})]})]}),((me=h[xe(_)])==null?void 0:me.holidayName)&&a.jsx("span",{className:"holiday-meta",children:h[xe(_)].holidayName}),((ke=g[xe(_)])==null?void 0:ke.sunrise)&&((Me=g[xe(_)])==null?void 0:Me.sunset)&&a.jsxs("span",{className:"sun-meta",children:["Auf ",g[xe(_)].sunrise," · Unter ",g[xe(_)].sunset]})]},_.toISOString())})()),a.jsx("div",{className:"time-grid",children:q.map(_=>a.jsx("div",{className:`slot-label ${_.rowClass}`,children:_.label},_.label))}),p.map(_=>a.jsx(Qp,{day:_,slots:q,instructor:Y,appointments:_e,preStartBufferMinutes:vt,restIntervals:Wl,sunInfo:g[xe(_)]||null,dayMeta:h[xe(_)]||null,now:y,armedLessonTypeId:L,onCreate:c},_.toISOString()))]})]}),a.jsxs("aside",{className:"planner-panel",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Planung"}),a.jsx("h2",{children:"Bausteine"})]}),t&&a.jsx("button",{className:"primary-button compact",onClick:()=>c({day:new Date,hour:8,minute:0,instructorId:Y==null?void 0:Y.id}),children:"+"})]}),a.jsx("div",{className:"mode-switch",children:I.map(_=>a.jsx("button",{className:v===_.id?"tab-button active":"tab-button",onClick:()=>D(_.id),children:_.label},_.id))}),v==="student"&&a.jsxs("label",{className:"stack-form",children:["Fahrschueler",a.jsxs("div",{className:"autocomplete-shell",children:[a.jsx("input",{type:"text",value:M,placeholder:"Name eingeben ...",onFocus:()=>$(!0),onChange:_=>{S(_.target.value),$(!0)},onBlur:()=>{window.setTimeout(()=>$(!1),120)}}),T&&it.length>0&&a.jsx("div",{className:"autocomplete-list",children:it.map(_=>a.jsxs("button",{type:"button",className:"autocomplete-option",onMouseDown:b=>{b.preventDefault(),w(_.id),S(`${_.first_name} ${_.last_name}`),$(!1)},children:[a.jsxs("strong",{children:[_.first_name," ",_.last_name]}),a.jsx("span",{children:_.template_code})]},_.id))})]})]}),a.jsx("div",{className:"block-bank",children:Q.map(_=>a.jsxs("div",{className:L===_.id?"lesson-chip active":"lesson-chip",draggable:!t,onDragStart:b=>{const E=JSON.stringify({lessonTypeId:_.id});b.dataTransfer.effectAllowed="move",b.dataTransfer.setData("application/json",E),b.dataTransfer.setData("text/plain",E)},onClick:()=>{W(_.id),t&&c({day:new Date,hour:8,minute:0,lessonTypeId:_.id,instructorId:Y==null?void 0:Y.id})},style:{"--chip-color":_.color},children:[a.jsx("strong",{children:_.name}),a.jsx("span",{children:_.fixed_duration?`${_.default_duration} min fix`:`${_.default_duration} min pro Einheit`})]},_.id))}),v==="private"&&L&&a.jsxs("div",{className:"quick-plan-panel",children:[a.jsx("div",{className:"section-head compact-head",children:a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Privat"}),a.jsx("h2",{children:"Schnell eintragen"})]})}),a.jsxs("label",{className:"stack-form",children:["Datum",a.jsx("input",{type:"date",value:A,onChange:_=>N(_.target.value)})]}),a.jsxs("div",{className:"time-editor-grid two-col",children:[a.jsx(Nl,{label:"Start",value:V,onChange:J}),a.jsx(Nl,{label:"Ende",value:P,onChange:O})]}),a.jsx("button",{type:"button",className:"primary-button",onClick:Ql,disabled:!Fe,children:"Privatblock anlegen"})]}),m&&a.jsxs("div",{className:"progress-panel",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Reststunden"}),a.jsxs("h2",{children:[m.first_name," ",m.last_name]})]}),a.jsx("span",{className:"badge",children:m.template_code})]}),pa(f).map(_=>{const b=bc(_.rows);return a.jsxs("div",{className:"progress-phase",children:[_.phase_label&&a.jsxs("div",{className:"phase-head",children:[a.jsx("strong",{children:_.phase_label}),a.jsx("span",{className:`phase-state ${b.status}`,children:Jr(b.open).text})]}),_.rows.map(E=>a.jsxs("div",{className:"progress-row",children:[a.jsxs("div",{children:[a.jsx("strong",{children:E.label}),a.jsxs("p",{children:["Soll ",E.required_units," · Geplant ",E.planned_units," · Erledigt ",E.completed_units]})]}),a.jsx("div",{className:`open-pill ${Jr(E.open_units).className}`,children:Jr(E.open_units).text})]},E.requirement_key))]},_.phase_label||"default")})]})]})]})}function Qp({day:e,slots:t,instructor:n,appointments:r,preStartBufferMinutes:l,restIntervals:i,sunInfo:s,dayMeta:o,now:u,armedLessonTypeId:d,onCreate:g}){const h=new Date(e);h.setHours(0,0,0,0);const m=new Date(e);m.setHours(24,0,0,0);const x=Tp(e,u),w=Qc(e,u),v=u.getHours()*60+u.getMinutes(),D=Math.min(96,Math.max(0,v/15)),f=Math.min(96,Math.max(0,Math.ceil(D))),c=r.filter(C=>{const R=new Date(C.start_at),A=new Date(C.end_at);return Rh}),p=r.filter(C=>{const R=new Date(C.start_at);return R>=h&&Rnew Date(C.start_at)-new Date(R.start_at))[0]||null,y=(()=>{if(!p||l<=0)return null;const C=new Date(p.start_at),R=new Date(Math.max(h.getTime(),C.getTime()-l*60*1e3)),A=C;if(A<=R)return null;const N=R.getHours()*60+R.getMinutes(),V=A.getHours()*60+A.getMinutes(),J=Math.max(0,Math.floor(N/15)),P=Math.max(1,Math.ceil((V-N)/15));return{startIndex:J,spanBlocks:Math.min(P,96-J),label:`Anfahrt ${l} min`}})(),j=i.map(C=>{const R=C.start>h?C.start:h,A=C.end=m?24*60:A.getHours()*60+A.getMinutes(),J=Math.max(0,Math.floor(N/15)),P=Math.max(1,Math.ceil((V-N)/15));return{key:`${C.start.toISOString()}-${C.end.toISOString()}-${e.toISOString()}`,startIndex:J,spanBlocks:Math.min(P,96-J),label:`Ruhezeit bis ${String(C.end.getHours()).padStart(2,"0")}:${String(C.end.getMinutes()).padStart(2,"0")}`,hasConflict:C.hasConflict}}).filter(Boolean),M=Mo(s==null?void 0:s.sunrise),S=Mo(s==null?void 0:s.sunset),T=[];if(M!==null&&M>0&&T.push({key:`night-before-${xe(e)}`,startIndex:0,spanBlocks:Math.max(1,Math.ceil(M/15)),label:`Nacht bis ${s.sunrise}`,align:"end"}),S!==null&&S<24*60){const C=Math.max(0,Math.floor(S/15));T.push({key:`night-after-${xe(e)}`,startIndex:C,spanBlocks:Math.max(1,Math.ceil((24*60-S)/15)),label:`Dunkel ab ${s.sunset}`,align:"start"})}function $(C){g({day:e,hour:C.hour,minute:C.minute,instructorId:n==null?void 0:n.id,lessonTypeId:d||void 0})}function L(C,R){C.preventDefault();const A=C.dataTransfer.getData("application/json")||C.dataTransfer.getData("text/plain");if(!A)return;const N=JSON.parse(A);g({lessonTypeId:N.lessonTypeId,day:e,hour:R.hour,minute:R.minute,instructorId:n==null?void 0:n.id})}function W(C,R){const A=C.currentTarget.getBoundingClientRect(),N=Math.max(0,Math.min(A.height-1,C.clientY-A.top)),V=Math.floor(N/28),J=Math.min(95,R.startIndex+V);return t[J]}return a.jsxs("div",{className:["day-grid",w?"past-column":"",o!=null&&o.holidayName?"holiday-column":"",o!=null&&o.isSunday?"sunday-column":""].filter(Boolean).join(" "),children:[w&&a.jsx("div",{className:"day-past-overlay",style:{gridRow:"1 / span 96"}}),x&&f>0&&a.jsx("div",{className:"current-time-past",style:{gridRow:`1 / span ${f}`},"aria-hidden":"true"}),x&&D>0&&D<96&&a.jsx("div",{className:"current-time-line",style:{top:`${D*28}px`},"aria-hidden":"true",children:a.jsx("span",{children:"Jetzt"})}),t.map((C,R)=>a.jsx("button",{type:"button",className:`slot-cell quarter-hour ${C.rowClass}`,style:{gridRow:R+1},onDragOver:A=>{A.preventDefault(),A.dataTransfer.dropEffect="move"},onDrop:A=>L(A,C),onClick:()=>$(C)},C.label)),T.map(C=>a.jsx("div",{className:`sun-band ${C.align==="end"?"top-band":"bottom-band"}`,style:{gridRow:`${C.startIndex+1} / span ${C.spanBlocks}`},children:a.jsx("span",{children:C.label})},C.key)),y&&a.jsx("div",{className:"pre-start-stripe",style:{gridRow:`${y.startIndex+1} / span ${y.spanBlocks}`},"aria-hidden":"true",children:a.jsx("span",{children:y.label})}),c.map(C=>{const R=new Date(C.start_at),A=new Date(C.end_at),N=R>h?R:h,V=A=m?24*60:V.getHours()*60+V.getMinutes(),O=Math.max(0,Math.floor(J/15)),I=Math.max(1,Math.min(96-O,Math.ceil((P-J)/15)));return a.jsx(Kp,{appointment:C,onOpen:()=>g({appointment:C}),compact:!0,spanBlocks:I,gridRowStart:O+1},`${C.id}-${xe(e)}`)}),j.map(C=>a.jsx("div",{className:C.hasConflict?"rest-stripe warning":"rest-stripe ok",style:{gridRow:`${C.startIndex+1} / span ${C.spanBlocks}`},onDragOver:R=>{R.preventDefault(),R.dataTransfer.dropEffect="move"},onDrop:R=>{const A=W(R,C);L(R,A)},onClick:R=>{const A=W(R,C);$(A)},children:a.jsx("span",{children:C.label})},C.key))]})}function Kp({appointment:e,onOpen:t,compact:n=!1,spanBlocks:r=1,gridRowStart:l=null}){const s=`${e.student_first_name||""} ${e.student_last_name||""}`.trim()||Yc(e.category),o=r>=3,u=Number(e.student_needs_glasses||0)===1;return a.jsx("button",{className:n?"appointment-card compact-card day-appointment":"appointment-card",onClick:d=>{d.stopPropagation(),t()},style:{"--appointment-color":e.lesson_type_color,"--span-blocks":r,...l?{gridRow:`${l} / span ${r}`}:{}},children:n?a.jsxs(a.Fragment,{children:[a.jsxs("div",{className:"appointment-top",children:[a.jsxs("strong",{className:"appointment-title-row",children:[a.jsx("span",{children:e.lesson_type_name}),u&&a.jsx("span",{className:"vision-badge",title:"Brille erforderlich",children:"[Br]"})]}),a.jsxs("span",{className:"appointment-student",children:[s,u&&a.jsx("span",{className:"vision-inline","aria-hidden":"true",children:" [Brille]"})]}),o&&a.jsxs("span",{className:"appointment-meta",children:[_t(e.start_at)," · ",_t(e.start_at,!0)]})]}),a.jsx("div",{className:"appointment-bottom",children:a.jsxs("span",{children:["bis ",_t(e.end_at,!0)]})})]}):a.jsxs(a.Fragment,{children:[a.jsxs("strong",{className:"appointment-title-row",children:[a.jsx("span",{children:e.lesson_type_name}),u&&a.jsx("span",{className:"vision-badge",title:"Brille erforderlich",children:"[Br]"})]}),a.jsxs("span",{children:[_t(e.start_at,!0)," - ",_t(e.end_at,!0)]}),a.jsxs("span",{children:[s,u&&a.jsx("span",{className:"vision-inline",children:" [Brille]"})]})]})})}function bp({students:e,templates:t,onSave:n,onSaveTraining:r,onDelete:l}){var g;const[i,s]=F.useState(null),[o,u]=F.useState("master"),d=e.find(h=>Number(h.id)===Number(i))||null;return i==="new"?a.jsx(zo,{student:{first_name:"",last_name:"",phone:"",email:"",needs_glasses:0,notes:"",template_id:(g=t[0])==null?void 0:g.id,status:"active",progress:[]},templates:t,activeTab:"master",setActiveTab:u,onBack:()=>s(null),onSave:async h=>{await n(h,null),s(null)},onSaveTraining:null,onDelete:null,isNew:!0}):d?a.jsx(zo,{student:d,templates:t,activeTab:o,setActiveTab:u,onBack:()=>{s(null),u("master")},onSave:async h=>{await n(h,d.id)},onSaveTraining:async h=>{await r(d.id,h)},onDelete:async()=>{window.confirm(`Fahrschueler ${d.first_name} ${d.last_name} wirklich loeschen? Termine bleiben erhalten, verlieren aber den Fahrschueler-Bezug.`)&&(await l(d.id),s(null),u("master"))}}):a.jsxs("section",{className:"list-panel student-page",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Verwaltung"}),a.jsx("h2",{children:"Fahrschueler"}),a.jsx("p",{className:"meta-line",children:"Klick oeffnet die eigene Fahrschueler-Seite mit Stammdaten und Ausbildung."})]}),a.jsx("button",{className:"primary-button",onClick:()=>{u("master"),s("new")},children:"Neu"})]}),a.jsx("div",{className:"entity-list",children:e.map(h=>a.jsxs("button",{className:"entity-item student-list-row",onClick:()=>s(h.id),children:[a.jsxs("strong",{children:[h.first_name," ",h.last_name]}),a.jsxs("span",{children:[h.template_code," · ",h.status]})]},h.id))})]})}function zo({student:e,templates:t,activeTab:n,setActiveTab:r,onBack:l,onSave:i,onSaveTraining:s,onDelete:o,isNew:u=!1}){const[d,g]=F.useState(e),[h,m]=F.useState(e.progress||[]),[x,w]=F.useState(!1);F.useEffect(()=>{g(e),m(e.progress||[])},[e]);async function v(f){f.preventDefault(),await i(d)}async function D(f){if(f.preventDefault(),!!s){w(!0);try{await s(h.map(c=>({requirement_key:c.requirement_key,phase_label:c.phase_label||"",prior_completed_units:Number(c.prior_completed_units||0)})))}finally{w(!1)}}}return a.jsxs("section",{className:"list-panel student-page student-detail-page",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Fahrschueler"}),a.jsx("h2",{children:u?"Neuer Fahrschueler":`${e.first_name} ${e.last_name}`}),a.jsx("p",{className:"meta-line",children:u?"Stammdaten anlegen.":`${e.template_name||e.template_code} · ${e.status}`})]}),a.jsx("button",{type:"button",className:"ghost-button",onClick:l,children:"Zurueck"})]}),!u&&a.jsxs("div",{className:"mode-switch detail-tabs",children:[a.jsx("button",{className:n==="master"?"tab-button active":"tab-button",onClick:()=>r("master"),children:"Stammdaten"}),a.jsx("button",{className:n==="training"?"tab-button active":"tab-button",onClick:()=>r("training"),children:"Ausbildung"})]}),(n==="master"||u)&&a.jsxs("form",{className:"stack-form",onSubmit:v,children:[a.jsxs("label",{children:["Vorname",a.jsx("input",{value:d.first_name||"",onChange:f=>g({...d,first_name:f.target.value}),required:!0})]}),a.jsxs("label",{children:["Nachname",a.jsx("input",{value:d.last_name||"",onChange:f=>g({...d,last_name:f.target.value}),required:!0})]}),a.jsxs("label",{children:["Telefon",a.jsx("input",{value:d.phone||"",onChange:f=>g({...d,phone:f.target.value})})]}),a.jsxs("label",{children:["E-Mail",a.jsx("input",{type:"email",value:d.email||"",onChange:f=>g({...d,email:f.target.value})})]}),a.jsxs("label",{className:"checkbox-row",children:[a.jsx("input",{type:"checkbox",checked:!!d.needs_glasses,onChange:f=>g({...d,needs_glasses:f.target.checked?1:0})}),a.jsx("span",{children:"Benoetigt Brille beim Fahren"})]}),a.jsxs("label",{children:["Fuehrerscheinklasse",a.jsx("select",{value:d.template_id||"",onChange:f=>g({...d,template_id:f.target.value}),children:t.map(f=>a.jsx("option",{value:f.id,children:f.name},f.id))})]}),a.jsxs("label",{children:["Status",a.jsxs("select",{value:d.status||"active",onChange:f=>g({...d,status:f.target.value}),children:[a.jsx("option",{value:"active",children:"aktiv"}),a.jsx("option",{value:"inactive",children:"inaktiv"})]})]}),a.jsxs("label",{children:["Notiz",a.jsx("textarea",{rows:"5",value:d.notes||"",onChange:f=>g({...d,notes:f.target.value})})]}),a.jsxs("div",{className:"modal-actions",children:[!u&&o&&a.jsx("button",{type:"button",className:"danger-button",onClick:o,children:"Fahrschueler loeschen"}),a.jsx("button",{className:"primary-button",type:"submit",children:"Speichern"})]})]}),!u&&n==="training"&&a.jsxs("form",{className:"training-panel",onSubmit:D,children:[(e.progress||[]).length===0&&a.jsx("p",{className:"meta-line",children:"Noch keine Ausbildungsvorgaben hinterlegt."}),pa(h).map(f=>{const c=bc(f.rows);return a.jsxs("div",{className:"training-phase",children:[f.phase_label&&a.jsxs("div",{className:"phase-head",children:[a.jsx("strong",{children:f.phase_label}),a.jsx("span",{className:`phase-state ${c.status}`,children:Jr(c.open).text})]}),f.rows.map(p=>{const y=h.findIndex(C=>C.requirement_key===p.requirement_key),j=Number(p.required_units||0),M=Number(p.completed_units||0),S=Number(p.prior_completed_units||0),T=Number(p.appointment_completed_units||0),$=Number(p.planned_units||0),L=M+$,W=j>0?Math.min(100,Math.round(L/j*100)):100;return a.jsxs("div",{className:"training-row",children:[a.jsxs("div",{className:"training-row-head",children:[a.jsxs("div",{children:[a.jsx("strong",{children:p.label}),a.jsxs("p",{children:["Altbestand ",S," · Termine erledigt ",T," · Geplant ",$," · Soll ",j," · Offen ",p.open_units]})]}),a.jsxs("label",{className:"training-import-field",children:["Schon erledigt",a.jsx("input",{type:"number",min:"0",step:"1",value:p.prior_completed_units??0,onChange:C=>{const R=[...h];R[y]={...p,prior_completed_units:C.target.value},m(R)}})]})]}),a.jsx("div",{className:"training-meter","aria-label":`${W} Prozent`,children:a.jsx("span",{style:{width:`${W}%`}})})]},p.requirement_key)})]},f.phase_label||"default")}),(e.progress||[]).length>0&&a.jsx("div",{className:"modal-actions",children:a.jsx("button",{className:"primary-button",type:"submit",disabled:x,children:x?"Speichert ...":"Ausbildung speichern"})})]})]})}function qp({lessonTypes:e,templates:t,onSave:n,onDelete:r,onReorder:l,onSaveMatrix:i}){const[s,o]=F.useState(null),[u,d]=F.useState([]),[g,h]=F.useState(!1),[m,x]=F.useState(e),w=e.filter(c=>c.category==="student");F.useEffect(()=>{d(t.map(c=>({template_id:c.id,lesson_type_ids:c.lesson_type_ids||[]})))},[t]),F.useEffect(()=>{x(e)},[e]);function v(c,p){d(y=>y.map(j=>{if(j.template_id!==c)return j;const M=j.lesson_type_ids.includes(p);return{...j,lesson_type_ids:M?j.lesson_type_ids.filter(S=>S!==p):[...j.lesson_type_ids,p]}}))}function D(c,p){const y=[...m];if(c<0||p<0||c>=y.length||p>=y.length)return y;const[j]=y.splice(c,1);return y.splice(p,0,j),y}async function f(c){x(c),await l(c.map(p=>p.id))}return a.jsxs(a.Fragment,{children:[a.jsxs(a.Fragment,{children:[a.jsxs("section",{className:"list-panel",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Verwaltung"}),a.jsx("h2",{children:"Stundenarten"}),a.jsx("p",{className:"meta-line",children:"Mit Hoch-/Runter-Knoepfen umsortieren. Farben, Dauer und Kategorie gelten mandantenweit."})]}),a.jsx("button",{className:"primary-button",onClick:()=>o({name:"",color:"#0E5D80",default_duration:45,category:"student",requirement_key:"",is_billable:1,is_counted:1,is_rest_relevant:1,fixed_duration:0,sort_order:m.length+1,system_key:""}),children:"Neu"})]}),a.jsx("div",{className:"entity-list lesson-type-list",children:m.map((c,p)=>a.jsxs("div",{className:"entity-item lesson-type-row",children:[a.jsxs("div",{className:"lesson-type-actions",children:[a.jsx("button",{type:"button",className:"ghost-button compact reorder-button",disabled:p===0,onClick:async y=>{y.stopPropagation(),await f(D(p,p-1))},children:"Hoch"}),a.jsx("button",{type:"button",className:"ghost-button compact reorder-button",disabled:p===m.length-1,onClick:async y=>{y.stopPropagation(),await f(D(p,p+1))},children:"Runter"})]}),a.jsx("span",{className:"color-dot",style:{"--dot-color":c.color||"#67625a"}}),a.jsxs("span",{className:"lesson-type-main",children:[a.jsx("strong",{children:c.name}),a.jsxs("span",{className:"meta-line",children:[c.default_duration," min · ",Yc(c.category)]})]}),a.jsx("button",{type:"button",className:"ghost-button compact",onClick:()=>o(c),children:"Bearbeiten"})]},c.id))})]}),s&&a.jsx(Hl,{title:"Stundenart",initial:s,onClose:()=>o(null),onSubmit:async c=>{c.category=c.category||s.category,c.default_duration=Number(c.default_duration),c.sort_order=Number(c.sort_order),await n(c,s.id),o(null)},onDelete:s.id&&s.system_key!=="private_block"?async()=>{window.confirm(`Stundenart "${s.name}" wirklich loeschen?`)&&(await r(s.id),o(null))}:null,fields:[{name:"name",label:"Bezeichnung"},{name:"color",label:"Farbe",type:"color"},{name:"default_duration",label:"Dauer in Minuten",type:"number"},{name:"category",label:"Kategorie",type:"select",disabled:s.system_key==="private_block",options:[{value:"student",label:"Fahrschueler"},{value:"private",label:"Privat"},{value:"work",label:"Sonstige Arbeiten"},{value:"theory",label:"Theorie"},{value:"blocked",label:"Sperre"}]},{name:"requirement_key",label:"Pflichtbaustein-Key"},{name:"sort_order",label:"Sortierung",type:"number"},{name:"is_billable",label:"Abrechenbar",type:"checkbox"},{name:"is_counted",label:"Zaehlt fuer Pflichtstunden",type:"checkbox"},{name:"is_rest_relevant",label:"Ruhezeit-relevant",type:"checkbox"},{name:"fixed_duration",label:"Feste Dauer",type:"checkbox"}]})]}),w.length>0&&t.length>0&&a.jsxs("section",{className:"list-panel",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Sichtbarkeit"}),a.jsx("h2",{children:"Klassenmatrix"}),a.jsx("p",{className:"meta-line",children:"Fuehrerscheinklassen stehen vertikal, schuelerbezogene Stundenarten horizontal."})]}),a.jsx("button",{className:"primary-button",onClick:async()=>{h(!0);try{await i(u)}finally{h(!1)}},disabled:g,children:g?"Speichert ...":"Matrix speichern"})]}),a.jsx("div",{className:"visibility-matrix-wrap",children:a.jsxs("table",{className:"visibility-matrix",children:[a.jsx("thead",{children:a.jsxs("tr",{children:[a.jsx("th",{children:"Fuehrerscheinklasse"}),w.map(c=>a.jsx("th",{children:c.name},c.id))]})}),a.jsx("tbody",{children:t.map(c=>{const p=u.find(y=>y.template_id===c.id)||{lesson_type_ids:[]};return a.jsxs("tr",{children:[a.jsx("th",{children:c.name}),w.map(y=>a.jsx("td",{children:a.jsx("input",{type:"checkbox",checked:p.lesson_type_ids.includes(y.id),onChange:()=>v(c.id,y.id)})},y.id))]},c.id)})})]})})]})]})}function Yp({instructors:e,onSave:t}){const[n,r]=F.useState(null);return a.jsx(Vl,{title:"Fahrlehrer",subtitle:"Mehrere Ressourcen werden direkt im Kalender abgebildet.",items:e,columns:l=>`${l.first_name} ${l.last_name} · ${l.is_active?"aktiv":"inaktiv"} · Anfahrt ${Number(l.pre_start_buffer_minutes||0)} min`,onEdit:l=>r(l),onCreate:()=>r({first_name:"",last_name:"",color:"#0E5D80",notes:"",is_active:1,pre_start_buffer_minutes:0}),form:n&&a.jsx(Hl,{title:"Fahrlehrer",initial:n,onClose:()=>r(null),onSubmit:async l=>{await t(l,n.id),r(null)},fields:[{name:"first_name",label:"Vorname"},{name:"last_name",label:"Nachname"},{name:"color",label:"Farbe",type:"color"},{name:"pre_start_buffer_minutes",label:"Anfahrtszeit vor erstem Termin (Minuten)",type:"number"},{name:"notes",label:"Notiz",type:"textarea"},{name:"is_active",label:"Aktiv",type:"checkbox"}]})})}function Xp({templates:e,lessonTypes:t,onSave:n}){const[r,l]=F.useState(null);return a.jsx(Vl,{title:"Klassenvorlagen",subtitle:"Kombinationen wie C + CE sind eigene Vorlagen mit festen Sollwerten.",items:e,columns:i=>`${i.code} · ${Zr(i,t).map(s=>`${s.label}: ${s.required_units}`).join(", ")||"keine Pflichtstunden"}`,onEdit:i=>{const s=Bp(i);l({...s,requirements:Zr(s,t)})},onCreate:()=>l({code:"",name:"",is_combination:0,lesson_type_ids:t.filter(i=>i.category==="student").map(i=>i.id),requirements:Zr({lesson_type_ids:t.filter(i=>i.category==="student").map(i=>i.id),requirements:[]},t)}),form:r&&a.jsx(em,{template:r,setTemplate:l,lessonTypes:t,onClose:()=>l(null),onSubmit:async()=>{await n(r,r.id),l(null)}})})}function Gp({users:e,tenants:t,currentUser:n,onSave:r}){const[l,i]=F.useState(null);return a.jsx(Vl,{title:"Benutzer",subtitle:"Rollen und Zugriff werden serverseitig geprueft.",items:e,columns:s=>`${s.first_name} ${s.last_name} · ${s.role} · ${s.email}${s.tenant_name?` · ${s.tenant_name}`:""}`,onEdit:s=>i(s),onCreate:()=>{var s;return i({tenant_id:n.tenant_id||((s=t[0])==null?void 0:s.id)||"",role:"dispatcher",email:"",password:"",first_name:"",last_name:"",is_active:1})},form:l&&a.jsx(Hl,{title:"Benutzer",initial:l,onClose:()=>i(null),onSubmit:async s=>{await r(s,l.id),i(null)},fields:[...n.role==="superadmin"?[{name:"tenant_id",label:"Mandant",type:"select",options:t.map(s=>({value:s.id,label:s.name}))}]:[],{name:"role",label:"Rolle",type:"select",options:[{value:"tenant_admin",label:"Mandanten-Admin"},{value:"dispatcher",label:"Disponent"},{value:"instructor",label:"Fahrlehrer"},...n.role==="superadmin"?[{value:"superadmin",label:"Superadmin"}]:[]]},{name:"email",label:"E-Mail"},{name:"password",label:"Passwort"},{name:"first_name",label:"Vorname"},{name:"last_name",label:"Nachname"},{name:"is_active",label:"Aktiv",type:"checkbox"}]})})}function Zp({tenants:e,onSave:t}){const[n,r]=F.useState(null);return a.jsx(Vl,{title:"Mandanten",subtitle:"Superadmin-Verwaltung fuer Mandantenstatus und Zeitzone.",items:e,columns:l=>`${l.name} · ${l.timezone} · ${l.is_active?"aktiv":"inaktiv"}`,onEdit:l=>r(l),onCreate:()=>r({name:"",timezone:"Europe/Berlin",is_active:1}),form:n&&a.jsx(Hl,{title:"Mandant",initial:n,onClose:()=>r(null),onSubmit:async l=>{await t(l,n.id),r(null)},fields:[{name:"name",label:"Name"},{name:"timezone",label:"Zeitzone"},{name:"federal_state",label:"Bundesland",type:"select",options:Wc},{name:"location_label",label:"Ort / Filiale"},{name:"latitude",label:"Breitengrad",type:"number"},{name:"longitude",label:"Laengengrad",type:"number"},{name:"is_active",label:"Aktiv",type:"checkbox"}]})})}function Jp({tenant:e,onSave:t}){const[n,r]=F.useState(e),[l,i]=F.useState((e==null?void 0:e.location_label)||""),[s,o]=F.useState([]),[u,d]=F.useState(!1),[g,h]=F.useState("");F.useEffect(()=>{r(e),i((e==null?void 0:e.location_label)||""),o([]),h("")},[e]);const m=Number(n==null?void 0:n.latitude),x=Number(n==null?void 0:n.longitude),w=Number.isFinite(m)&&Number.isFinite(x),v=.025,D=w?`https://www.openstreetmap.org/export/embed.html?bbox=${x-v}%2C${m-v}%2C${x+v}%2C${m+v}&layer=mapnik&marker=${m}%2C${x}`:"";async function f(){const c=l.trim();if(!c){o([]),h("Bitte einen Ort oder eine Adresse eingeben.");return}d(!0),h("");try{const p=await fetch(`https://nominatim.openstreetmap.org/search?format=jsonv2&limit=5&q=${encodeURIComponent(c)}`);if(!p.ok)throw new Error(`Ortssuche fehlgeschlagen (${p.status})`);const y=await p.json();o(y),y.length||h("Kein passender Ort gefunden.")}catch(p){h(p.message),o([])}finally{d(!1)}}return a.jsxs("section",{className:"list-panel",children:[a.jsx("div",{className:"section-head",children:a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Fahrschule"}),a.jsx("h2",{children:"Standort und Zeit"}),a.jsx("p",{className:"meta-line",children:"Ort, Bundesland und Zeitzone steuern Sonnenzeiten, Feiertage und die Hell-Dunkel-Markierung im Kalender."})]})}),a.jsxs("form",{className:"stack-form",onSubmit:async c=>{c.preventDefault(),await t(n)},children:[a.jsxs("label",{children:["Name",a.jsx("input",{value:n.name||"",onChange:c=>r({...n,name:c.target.value})})]}),a.jsxs("label",{children:["Zeitzone",a.jsx("input",{value:n.timezone||"",onChange:c=>r({...n,timezone:c.target.value})})]}),a.jsxs("label",{children:["Bundesland",a.jsx("select",{value:n.federal_state||"BE",onChange:c=>r({...n,federal_state:c.target.value}),children:Wc.map(c=>a.jsx("option",{value:c.value,children:c.label},c.value))})]}),a.jsxs("label",{children:["Ort / Filiale",a.jsxs("div",{className:"location-search",children:[a.jsx("input",{value:l,onChange:c=>{const p=c.target.value;i(p),r({...n,location_label:p})},placeholder:"z. B. Duisburg, Bahnhofstrasse 12"}),a.jsx("button",{type:"button",className:"ghost-button",onClick:f,disabled:u,children:u?"Suche...":"Ort suchen"})]})]}),g&&a.jsx("p",{className:"form-error",children:g}),s.length>0&&a.jsx("div",{className:"location-result-list",children:s.map(c=>a.jsxs("button",{type:"button",className:"location-result",onClick:()=>{r({...n,location_label:c.display_name,latitude:Number(c.lat),longitude:Number(c.lon)}),i(c.display_name),o([]),h("")},children:[a.jsx("strong",{children:c.display_name}),a.jsxs("span",{children:[Number(c.lat).toFixed(6)," / ",Number(c.lon).toFixed(6)]})]},`${c.place_id}-${c.lat}-${c.lon}`))}),a.jsxs("div",{className:"coordinate-grid",children:[a.jsxs("label",{children:["Breitengrad",a.jsx("input",{type:"number",step:"0.000001",value:n.latitude??"",onChange:c=>r({...n,latitude:c.target.value})})]}),a.jsxs("label",{children:["Laengengrad",a.jsx("input",{type:"number",step:"0.000001",value:n.longitude??"",onChange:c=>r({...n,longitude:c.target.value})})]})]}),w&&a.jsxs("div",{className:"map-preview",children:[a.jsx("iframe",{title:"Standortkarte",src:D,loading:"lazy",referrerPolicy:"no-referrer-when-downgrade"}),a.jsx("p",{className:"meta-line",children:"Karte zeigt den derzeit gespeicherten Standort."})]}),a.jsxs("div",{className:"location-hint",children:[a.jsx("strong",{children:"Hinweis"}),a.jsx("span",{children:"Feiertage richten sich nach dem Bundesland. Sonnenaufgang und Sonnenuntergang richten sich nach dem gespeicherten Standort."})]}),a.jsx("button",{className:"primary-button",type:"submit",children:"Speichern"})]})]})}function Vl({title:e,subtitle:t,items:n,columns:r,onEdit:l,onCreate:i,form:s}){return a.jsxs(a.Fragment,{children:[a.jsxs("section",{className:"list-panel",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Verwaltung"}),a.jsx("h2",{children:e}),a.jsx("p",{className:"meta-line",children:t})]}),a.jsx("button",{className:"primary-button",onClick:i,children:"Neu"})]}),a.jsx("div",{className:"entity-list",children:n.map(o=>a.jsx("button",{className:"entity-item",onClick:()=>l(o),children:r(o)},o.id))})]}),s]})}function Hl({title:e,initial:t,fields:n,onSubmit:r,onClose:l,onDelete:i=null}){async function s(o){o.preventDefault();const u=new FormData(o.currentTarget),d=Object.fromEntries(u.entries());for(const g of n)g.type==="checkbox"&&(d[g.name]=u.get(g.name)?1:0);await r(d)}return a.jsx("div",{className:"modal-shell",children:a.jsxs("form",{className:"modal-card stack-form",onSubmit:s,children:[a.jsxs("div",{className:"section-head",children:[a.jsx("h2",{children:e}),a.jsx("button",{type:"button",className:"ghost-button",onClick:l,children:"Schliessen"})]}),n.map(o=>a.jsxs("label",{children:[o.label,o.type==="textarea"?a.jsx("textarea",{name:o.name,defaultValue:t[o.name]||"",rows:"4",disabled:o.disabled}):o.type==="select"?a.jsx("select",{name:o.name,defaultValue:String(t[o.name]??""),disabled:o.disabled,children:o.options.map(u=>a.jsx("option",{value:u.value,children:u.label},u.value))}):o.type==="checkbox"?a.jsx("input",{name:o.name,type:"checkbox",defaultChecked:!!t[o.name],disabled:o.disabled}):a.jsx("input",{name:o.name,type:o.type||"text",defaultValue:t[o.name]||"",disabled:o.disabled})]},o.name)),a.jsxs("div",{className:"modal-actions",children:[i&&a.jsx("button",{type:"button",className:"danger-button",onClick:i,children:"Loeschen"}),a.jsx("button",{className:"primary-button",type:"submit",children:"Speichern"})]})]})})}function em({template:e,setTemplate:t,lessonTypes:n,onClose:r,onSubmit:l}){function i(s){const o={...e,is_combination:s?1:0};t({...o,requirements:Zr(o,n)})}return a.jsx("div",{className:"modal-shell",children:a.jsxs("form",{className:"modal-card stack-form",onSubmit:s=>{s.preventDefault(),l()},children:[a.jsxs("div",{className:"section-head",children:[a.jsx("h2",{children:"Klassenvorlage"}),a.jsx("button",{type:"button",className:"ghost-button",onClick:r,children:"Schliessen"})]}),a.jsxs("label",{children:["Code",a.jsx("input",{value:e.code,onChange:s=>t({...e,code:s.target.value})})]}),a.jsxs("label",{children:["Name",a.jsx("input",{value:e.name,onChange:s=>t({...e,name:s.target.value})})]}),a.jsxs("label",{children:["Kombination",a.jsx("input",{type:"checkbox",checked:!!e.is_combination,onChange:s=>i(s.target.checked)})]}),pa(e.requirements).map(s=>a.jsxs("div",{className:"requirement-phase",children:[s.phase_label&&a.jsx("h3",{children:s.phase_label}),a.jsx("div",{className:"requirements-grid",children:s.rows.map(o=>{const u=e.requirements.findIndex(d=>d.requirement_key===o.requirement_key);return a.jsxs("div",{className:"requirement-editor",children:[a.jsxs("strong",{className:"requirement-title",children:[a.jsx("span",{className:"color-dot",style:{"--dot-color":o.color||"#67625a"}}),o.label]}),a.jsx("input",{type:"number",min:"0",value:o.required_units,onChange:d=>{const g=[...e.requirements];g[u]={...g[u],required_units:Number(d.target.value)},t({...e,requirements:g})}})]},o.requirement_key)})})]},s.phase_label||"default")),a.jsx("button",{className:"primary-button",type:"submit",children:"Speichern"})]})})}function Nl({label:e,value:t,onChange:n}){const[r,l]=F.useState(!1),[i,s]=F.useState("hour"),o=Number((t||"00:00").split(":")[0]||0),u=Number((t||"00:00").split(":")[1]||0);F.useEffect(()=>{r&&s("hour")},[r]);const d=Array.from({length:12},(v,D)=>D),g=Array.from({length:12},(v,D)=>D+12),h=Array.from({length:12},(v,D)=>D*5);function m(v,D){const f=v/12*Math.PI*2-Math.PI/2,c=Math.cos(f)*D,p=Math.sin(f)*D;return{left:`calc(50% + ${c}px)`,top:`calc(50% + ${p}px)`}}function x(v){n(`${String(v).padStart(2,"0")}:${String(u).padStart(2,"0")}`),s("minute")}function w(v){n(`${String(o).padStart(2,"0")}:${String(v).padStart(2,"0")}`),l(!1)}return a.jsxs("div",{className:"time-dial-field",children:[a.jsxs("button",{type:"button",className:"time-dial-button",onClick:()=>l(v=>!v),children:[a.jsx("span",{children:e}),a.jsx("strong",{children:t})]}),r&&a.jsxs("div",{className:"time-dial-popover",children:[a.jsxs("div",{className:"time-dial-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Uhrzeit"}),a.jsx("strong",{children:t})]}),a.jsx("button",{type:"button",className:"ghost-button compact",onClick:()=>l(!1),children:"Schliessen"})]}),a.jsxs("div",{className:"mode-switch",children:[a.jsx("button",{type:"button",className:i==="hour"?"tab-button active compact":"tab-button compact",onClick:()=>s("hour"),children:"Stunde"}),a.jsx("button",{type:"button",className:i==="minute"?"tab-button active compact":"tab-button compact",onClick:()=>s("minute"),children:"Minute"})]}),a.jsxs("div",{className:"time-dial-face",children:[a.jsx("div",{className:"time-dial-center",children:i==="hour"?String(o).padStart(2,"0"):String(u).padStart(2,"0")}),i==="hour"?a.jsxs(a.Fragment,{children:[g.map((v,D)=>a.jsx("button",{type:"button",className:["time-dial-point","outer-ring",v===o?"active":""].filter(Boolean).join(" "),style:m(D,112),onClick:()=>x(v),children:String(v).padStart(2,"0")},`outer-${v}`)),d.map((v,D)=>a.jsx("button",{type:"button",className:["time-dial-point","inner-ring",v===o?"active":""].filter(Boolean).join(" "),style:m(D,72),onClick:()=>x(v),children:String(v).padStart(2,"0")},`inner-${v}`))]}):h.map((v,D)=>a.jsx("button",{type:"button",className:["time-dial-point",v===u?"active":""].filter(Boolean).join(" "),style:m(D,108),onClick:()=>w(v),children:String(v).padStart(2,"0")},`${i}-${v}`))]}),a.jsxs("label",{className:"stack-form",children:["Direkt eingeben",a.jsx("input",{type:"time",step:"300",value:t,onChange:v=>n(v.target.value)})]})]})]})}function tm({draft:e,setDraft:t,lessonTypes:n,instructors:r,students:l,templates:i,onPickStudent:s,onSubmit:o,onDelete:u,onClose:d,isEditing:g}){const h=Up(n,e.student_id,l),m=n.find(N=>Number(N.id)===Number(e.lesson_type_id)),x=h.some(N=>Number(N.id)===Number(e.lesson_type_id))?h:m?[m,...h]:h,w=l.find(N=>Number(N.id)===Number(e.student_id)),v=i.find(N=>Number(N.id)===Number(w==null?void 0:w.template_id)),D=r.find(N=>Number(N.id)===Number(e.instructor_id)),f=v&&Number(v.is_combination)?Kc(v):[],c=ys(m,e.start_at,e.end_at),p=Si(e.start_at),y=Br(e.start_at),j=Br(e.end_at),M=Rp(w,D,e.start_at,e.end_at),S=Fp(w==null?void 0:w.phone),T=S&&M?`https://wa.me/${S}?text=${encodeURIComponent(M)}`:"",$=S&&M?`sms:${S}?body=${encodeURIComponent(M)}`:"",L=w!=null&&w.email&&M?`mailto:${encodeURIComponent(w.email)}?subject=${encodeURIComponent("Fahrstunde")}&body=${encodeURIComponent(M)}`:"";F.useEffect(()=>{x.length&&(x.some(N=>Number(N.id)===Number(e.lesson_type_id))||t(N=>({...N,lesson_type_id:x[0].id})))},[e.lesson_type_id,x,t]),F.useEffect(()=>{e.end_at&&Ul(e.start_at,e.end_at)>0||t(N=>({...N,end_at:_i(N.start_at,Math.max(1,Number((m==null?void 0:m.default_duration)||45)))}))},[m==null?void 0:m.default_duration,e.end_at,e.start_at,t]);function W(N){N&&t(V=>({...V,start_at:jt(N,Br(V.start_at)),end_at:jt(N,Br(V.end_at))}))}function C(N){t(V=>{const J=jt(Si(V.start_at),N);return{...V,start_at:J,end_at:_i(J,ys(m,V.start_at,V.end_at).duration)}})}function R(N){t(V=>({...V,end_at:jt(Si(V.start_at),N)}))}function A(N){const V=Number((m==null?void 0:m.fixed_duration)||0)===1?Math.max(1,Number((m==null?void 0:m.default_duration)||45)):Math.max(1,Number((m==null?void 0:m.default_duration)||45))*N;t(J=>({...J,units:N,end_at:_i(J.start_at,V)}))}return a.jsx("div",{className:"modal-shell",children:a.jsxs("form",{className:"modal-card stack-form",onSubmit:o,children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Termin"}),a.jsx("h2",{children:g?"Termin bearbeiten":"Termin einplanen"})]}),a.jsx("button",{type:"button",className:"ghost-button",onClick:d,children:"Schliessen"})]}),a.jsxs("label",{children:["Fahrschueler",a.jsxs("select",{name:"student_id",value:e.student_id,onChange:N=>t({...e,student_id:N.target.value}),children:[a.jsx("option",{value:"",children:"Kein Fahrschueler"}),l.map(N=>a.jsxs("option",{value:N.id,children:[N.first_name," ",N.last_name]},N.id))]})]}),a.jsxs("label",{children:["Stundenart",a.jsx("select",{name:"lesson_type_id",value:e.lesson_type_id,onChange:N=>t({...e,lesson_type_id:N.target.value}),children:x.map(N=>a.jsx("option",{value:N.id,children:N.name},N.id))})]}),f.length>0&&a.jsxs("label",{children:["Ausbildungsteil",a.jsx("select",{name:"requirement_phase",value:e.requirement_phase||f[0],onChange:N=>t({...e,requirement_phase:N.target.value}),children:f.map(N=>a.jsx("option",{value:N,children:N},N))})]}),a.jsxs("label",{children:["Fahrlehrer",a.jsx("select",{name:"instructor_id",value:e.instructor_id,onChange:N=>t({...e,instructor_id:N.target.value}),children:r.map(N=>a.jsxs("option",{value:N.id,children:[N.first_name," ",N.last_name]},N.id))})]}),a.jsxs("div",{className:"time-editor-grid",children:[a.jsxs("label",{children:["Datum",a.jsx("input",{type:"date",value:p,onChange:N=>W(N.target.value)})]}),a.jsxs("div",{className:"stack-form",children:[a.jsx("span",{children:"Startzeit"}),a.jsx(Nl,{label:"Start",value:y,onChange:C})]}),a.jsxs("div",{className:"stack-form",children:[a.jsx("span",{children:"Endzeit"}),a.jsx(Nl,{label:"Ende",value:j,onChange:R})]})]}),a.jsxs("div",{className:"duration-preset-panel",children:[a.jsxs("div",{className:"section-head",children:[a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Dauer"}),a.jsx("h2",{children:"Fahrstunden"})]}),a.jsxs("span",{className:"badge",children:[c.duration," min · ",c.units," FS"]})]}),a.jsxs("div",{className:"duration-preset-row",children:[[1,2,3,4].map(N=>a.jsxs("button",{type:"button",className:c.units===N?"tab-button active compact":"tab-button compact",onClick:()=>A(N),children:[N," FS"]},N)),Number((m==null?void 0:m.fixed_duration)||0)===1&&a.jsxs("button",{type:"button",className:"ghost-button compact",onClick:()=>A(1),children:["Standard ",m==null?void 0:m.default_duration," min"]})]}),a.jsx("p",{className:"hint-line",children:"Start und Ende lassen sich direkt ueber die Uhr setzen. Die angerechneten Fahrstunden werden daraus automatisch berechnet."})]}),a.jsxs("div",{className:"share-panel",children:[a.jsx("div",{className:"section-head",children:a.jsxs("div",{children:[a.jsx("p",{className:"eyebrow",children:"Benachrichtigung"}),a.jsx("h2",{children:"Termin teilen"})]})}),a.jsxs("div",{className:"share-actions",children:[a.jsxs("a",{className:T?"share-button":"share-button disabled",href:T||void 0,target:"_blank",rel:"noreferrer","aria-disabled":!T,onClick:N=>{T||N.preventDefault()},children:[a.jsx("span",{className:"share-badge whatsapp",children:"WA"}),"WhatsApp"]}),a.jsxs("a",{className:$?"share-button":"share-button disabled",href:$||void 0,"aria-disabled":!$,onClick:N=>{$||N.preventDefault()},children:[a.jsx("span",{className:"share-badge sms",children:"SMS"}),"SMS"]}),a.jsxs("a",{className:L?"share-button":"share-button disabled",href:L||void 0,"aria-disabled":!L,onClick:N=>{L||N.preventDefault()},children:[a.jsx("span",{className:"share-badge email",children:"@"}),"E-Mail"]})]}),a.jsx("p",{className:"hint-line",children:w?"Die Nachricht wird mit Datum, Start- und Endzeit vorbefuellt.":"Fahrschueler waehlen, damit Kontakt-Buttons aktiv werden."})]}),a.jsxs("label",{children:["Status",a.jsxs("select",{name:"status",value:e.status,onChange:N=>t({...e,status:N.target.value}),children:[a.jsx("option",{value:"planned",children:"geplant"}),a.jsx("option",{value:"completed",children:"erledigt"}),a.jsx("option",{value:"cancelled",children:"abgesagt"})]})]}),a.jsxs("label",{children:["Notiz",a.jsx("textarea",{name:"notes",rows:"4",value:e.notes,onChange:N=>t({...e,notes:N.target.value})})]}),a.jsxs("div",{className:"modal-actions",children:[g&&a.jsx("button",{type:"button",className:"danger-button",onClick:u,children:"Termin loeschen"}),a.jsx("button",{type:"button",className:"ghost-button",onClick:()=>s==null?void 0:s(e.student_id),disabled:!e.student_id,children:"Fahrschueler waehlen"}),a.jsx("button",{className:"primary-button",type:"submit",children:"Speichern"})]})]})})}ki.createRoot(document.getElementById("root")).render(a.jsx(md.StrictMode,{children:a.jsx(Vp,{})})); diff --git a/www/fahrschultermin.de/public/zeiterfassung.html b/www/fahrschultermin.de/public/zeiterfassung.html new file mode 100644 index 0000000..0c4f137 --- /dev/null +++ b/www/fahrschultermin.de/public/zeiterfassung.html @@ -0,0 +1,541 @@ + + + + + + Arbeitszeitbericht – DriveTime Planner + + + + +
+ DriveTime Planner + › Arbeitszeitbericht + +
+ +
+

Arbeitszeitbericht

+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + + + + +
+ + + + diff --git a/www/fahrschultermin.de/storage/database/app.sqlite b/www/fahrschultermin.de/storage/database/app.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..aff7010de53509f2ca087989483c95f35217dfc2 GIT binary patch literal 126976 zcmeHw33wdGb!H#aGt+Z5L4q7WAjpA05`=iofm@^gSVj*r-iH(5Jz;$3H7 zRrOWXJu@Ip%TiKfnT46EdhgZ$UcEZ{RrT4^$4WIlHMd+@D%MgtVVxj~!huvu5C%^P zg0L0-Z+$x9kAU?BQqlEygFk`B-s_hYR9O2SDx;C4L2>>wM}Q;15#R`L1ULd50geDi zfFr;W;0SO8I08Qe1QOvmP?fZCLHkAchkrN%90861M}Q;15#R`L1ULd50geDifFr;W z@Izo$iV9tQYHqwM6lz;4&d%$VrNU}uc}3qhsaFSgqER%!(cBLUBeQ>!_t?A_hM?U*cfAqOcPaWv`AD36((DRlP=N}S7Lat98A3s!F zs8nYbRu}cuiRH>{rBt48z-S^x zqF-JuRrDqFcePwxsV*$ns^Xn(io9J~)T`Cy@^tOmieB9t(XI>V894d0MIgVpva(z% z*HGQ6@a{W9Kz6Y-qnE4tbWLAcSuECcH~p&c_DxZFpS#-hYPkgLE_DUvyG5(+%wn-x zwJB7?+HVQH7T*SGRFu0!*byj#B$5yd%iW^cz-m(awy>VArUm3)u~e?sDyuU!*h-L9 zSx$=;y`TFj~%*B%s<*r$ZVhZC{*jP`Q)lYck@90861M}Q;1 z5#R`L1ULd50gk|n8v!F3j0xiQU53&Xhv%^pSvE}(JKea z=F{B0HN9Fh<-7mziA&2DD%E|5i`mQb*VC2kTK9U|fqThFJ z;?ms8<;PZ!6h|%}FII{Nf&8*p$W{-mjcpPLxjhF$c7c#?HDg`-3VGzn#p1E0`Qw$* zBlo3O9(=sEdidN4{p@hAcJ1i!m3=F7XVTTbM}Q;15#R`L1ULd5 z0geDifFr;W;0U}#5!fK_5}6&U;AVNh*l2qw&?yg#zFRVKQqGA!yC4#(;k)Iapx6gl zTPJ8A(q0Y;|8N920vrL307rl$z!BgGa0EC490861N8sND0>>Z(LQKXXhGcwP5GDLr zy*I57&W@y=$LdoM;E@|1%dkNJ15@cyILAMhGas(sa}urxP&<=^+A)2(T+)wLigUHz ziQ$2f?2wxxIj-L-MB}802yVm#MJLvyH(eM!NK3djdI7Zl-ywWR(B7>LC!S3##Q!<| zw)jx&xmYC@j{ZvYP~>kSuaESpA6Fj;e>rS~wa~jmqruMxmx3YX7nI?^9|azf|5<)Y zPD&q^j*5RHzDDc>Vn4jk{EePJhZ&5gKB^yaeznY4}AM{k)+ap^`EaLILv8*Mu~y9`HbO7nVZs;EPHNiWrUhx56K z!ohp4E>|wezjpK7P`&A8u(`_ejXQwfb{D_gd3|P~sxP8lz*=u^a(H5Jcn@au-dki; zs}#%CIlVFs_l8{2AHC5D9Fs1Ny_2gTNbktx;6!$kwCQcP$ZC0R4o*xh-{=4q{Vo>! zP+;11eRY0iZV@7%Ot(dNz2O#lUC|dxGmHB4OtGTR-ADq*&MvVF)D}kWq3;wlwpi;O znjFrKr0-e5-*ox4H_yzhI(>0*dFIjrn1@KYA<6O%&HPYqDm^@w9U2?VFn);5aQx6L z7=}o>A)8ZUKg5!=Tgzscd}^~`3=Oz6_CqYOR$OLh!5$iLY3zq?mdng6*gyj=js4Kg za=AFWsUerfe&}Yo6w4cd3+0BSwfmtpb94OAN;~kQ+>o>eKXh}9Xg^fk030bdBsKFx zH^++jp_TQ(f^tLBn*7ks@pAj2`8ME4xgn{QA6hdr$`8%014dvUU4p8rVG-u6z^Hhs zR)Sli^wg=!Gas9Sz}?K4%^t4Z1t(;8@ zhdl!U)4)hBtA`@rhz7QgN!%DTwTPcHanpxz-_fzbbc+@&L^MIY8_|bxQw2jh3!%y|RF`V0eW^(_FmT!vn*4Jo58svy&skqqz&nw}x(>!*mc( zA{G@^kv>jUR&~^utHhutMsn~uhQ}_uK-PR_7p&%wqPy<&R6dj11^*P> zGOlgidsqR28+rg!H?>@IK-rhm#(KGWGA zf+JPX!xc~c&JB>&1$|rb)Jv{MwV+okbdL^fZG((%=+BCOUk-JyGx-I){4|qaz{@XT z@(Zy1;wHauvUr14~F<*L&BtX=&)gBRO29gc~Lcg&I%JhN8%#p>PS6~*(c81ON zNlg1>xie(8Ph#4qNXektK8a?pY*ozmN&Y+|9f1&>%mw{<*mcN|gVe`x3rL$JR0p)j zFxw)+??`$6yu$J5JB9V(?+3M4XyMqOCFT+xkznNiMvuo{8_PyM5P2xRBmN2X!|KD~ z???X^12g~15#R`L1ULd50geDi;3bH_!<*uQC~n*nk9Urb$HP)spD!-XSM>SiN(xTU zFD^qAD?Yq=MJJ(^a5R2+Dw7>cXU5W5cL1+X#?(eke5*w~gos~)_jA>*18sC9)I$}5 zbQ@94nkTr=wqvT@7FAQ^XFghnS35T#nxihNvPFf9s?3bjp(x;>^I|qn}K+$aVNtGseys~^1lyj-%D`MJv z-Njdq3{FusuM|MFj}W=Fnf-! z&cNYrw_JmJGMTB&Fx-#D=)ouBR2XFj^P$XFjyAl9tkHEBq#_T3LBqqH8* zmzEd7R4%Q!MYIWK#&YJJ5e#%g_B9qKWjDdlo)btwFch63h!4vTj4*8`oFEo|*v>f7 zh6$23OJOkPnM+H>a+#q=>|wxem|H-g=VS;%;=c&Te_+*G1Ws?jKEY8j@-!EhufjX) z-4ePwewMm`$S31i5Vx3+gzkG5-YDV2>OSAix=z8K(l6T94&lP83&G^zxj( zIO~=VfVU{7*&8>ku9mU9fgqf+ntwae%jl*)G1=LBH;e><`UlU)uUFEU;cRMj zbQG@Fn5-06;bw=_99+c!x2urrD)hbHCamp@gCU{I{KSo&+g}De<>RHr;(c&KmtF>z z=@eZ3HZV9ee707s)bv`aTtwG_)%4W)WpuO465Pw--oLzMArt^l(!PTr+^l_%{Bb+* zo(bX-B}|A6)lB+yn^|No=* z_v8!ufqysx90861M}Q;15#R`L1ULd50geDifFr;Wcqt;#WuW{1?wEsrl1Jd5O?Sn^ zuMxt5r-bOkiFd|6l6WleRQN+dBb*L=PWoeUB=P2#Vk^1K9088NOA3J}t}3BqZ?E{& zV6Av@Q7^%(7AvbW5OY>_{Ta-kDNIZiQd1L?#|kM|Mrvmy1)tJv>d1+y!r{W1)TuK^ zj!&F4o#dpHkIn@BkW4mX%a+c%WpAESw%CX zdLHtZfX_cnBC2_$l@laRx0{*6$P42UtFC-Z-|7~Fs&kKEjT^m zq=`6(PM#?oIeY@cywe7?KXs;XsBormB40S0BEMEsJ4>_usgoz5LB|T9!~DeA{KUaR zr2mQCp-}SfyTvD$vF0K06QUO3ZI0*$;pv*bw6X|Isy6sJOj$&OLbu&IW|n7t?V^#r zfO=<^moApd#ad~(+!EAV)G;>fb0?0RK37ONy4amU()QmrbsY}}lf}D5p#<+%dc1mh z5niS>U4)mZ;qTK8G%%fMkfFi^BYiWD>dF3KD7k&R_!Aq6wdmFAa(TLTZ3Rrk^>>K2 z3s=r3Yv={C zIE~T@G=IGv-ItoXTQf@cWY`+$NN4>4`r^uh?Q^Zs*qY)unf)WrwrZ{nEKwX+O%?{G zkG|FM&d)))ButfceYQG1kKfwy0?hh-XQO1M4|<`N4-F=ccsT)!hs9_pdHAsSy7O+E zf|-dkKdP3CE7gT%+tBU-YI;(zNi#7Uve%p3Yg8K(KNKOnZbO8D`zt>D6+n z=2aajw6+-)ECWl+6|_`YS=7-Y)1Rbmn(5uC6&TQT*Jy4FXBV>UpBg|ML?~TbuAuQ+ z@u*7FZO;D9wW8gpT|n9^M&HZ`rEbf{e2d<+x@{^AcNbh@I#bXeg}!f1d#(mj>a$>E zmhKI;X$-9oB~Nb`>oQ(EmS!KHcCX=J@sBrYoVmI?ccZC6bHRJ$#KFRYumRMH1loac zlc8414yXRq`TJl4!@W*8a@IEPC+=7mN*+BbKDi5f9Tz=&!(#eMsak@?SE+WbMfL{T z6rH82A1%`u-6R`YnM+Ncbrf+6?oG0tG|k>7wSz9o9`DmkQrdw>AqoXU>sBnb$Q$TyYJAr30uZ@!tN3Yf35TQ)`X_{VD7kN+_~bqe zC>#uiy`&o6+Q8$F=ZAF^#GdS%#@bDxVQ2Dn+13oo**A6#)80(n92j2Lw#(9dsz>!j zx&>5UbY(T_i;@1PuEp@C+E0_scXu84-|)1A2DcCq-wje8dkSZy?BkPYHo4?7CtO3tQ*#e z^rQHHQTrFP)Zapq+q>%L)Hnhh0geDifFr;W;0SO8I0762jsQo1Bft^(5hKuT>_pH1 zMeT0|_>X@$0vrL307rl$z!BgGa0EC490861M}Q;15qQZX&@L*1y0sG`diK^9;Otnn zG+!<*qWb_u?H>gAkAFA<90861M}Q;15#R`L1ULd50geDifFr;W_&0+?Og!$ z{6C%en1J5>r+rKNy7ndQ^V*+lf1-U#`+e=VwU21OuKlw19_{Vg4edW@Pin8$u4*e< zNxP^$qMg%DXos}}+NhS(c5BUBh_+H}MiRTkvNqiyk7m3d#o=rRh4ETp5 zz!BgGa0EC490861M}Q;15#R`L1ULe>h=7unMR8qN$)F^pq#@D5%05UEVP!8Q@vyQ7 zl2};T4M{Yt?1DrKDR)7V2q}G##6!vsNMa#nJ0#JNvJH};r1YXhQSL-ZQ0YNQKuMuQ z4s4Z0HK=TXFN&meqeM|Qqa>(wp(LQ(ff6~;2?Z6U1HJ?#C5aM6*@TjyvJoW#r5z=5 zU;`8kDC^;iEGca$QIvHk2`U;&0!jiUav%-`WhDk*BuR;)L{TCr2`VZ|0!kPqav%f+ zr9e^&~+IzL@+B;zNU)O#@t7=EJOWKTfR(n`GsBPBn(FU|_u=d}WcwUQZO5!_-|Cjii z#P1|NpZL#-KTCWt@#(}bCO(mPXX4GkfPXjw90861M}Q;15#R`L1ULd50geDifFocb z5DJKBL7ydQD1%cioW^M)ybq`G@Lrt8!h3KU4e!RO7TSf=MCdM@#zTELjfHmLG#c8D z)1b7Cq)IPIgLjfN&_hx=6%2^5R^E!gD$*8`D%~UvZYF7o$t=g|>&spl?DMx@Kz!BgGa0EC490861M}Q;15#R`L1eznTE}-^E za0(sXZ#sSP(wy3p2&moG4>dLA{yM8}b$^{vx46GvRJ+|@i|S@f8{W2fX?htrRMf7{ zfSQx=54B3MTt)9Uo~@Lw=;|G90kxOpEYHos%cRuKWI)|Vez~GAlx7y;Ma0F5u6Aq) zsC^Q=AL|Od5n}oxyvOpAnp_tUdqhiUXhl~dAa>h7)b60Z&UR8?XF8~_7n9W2;wD1d zX~o9QfS42gTG8GX5PQuW(uxhqfVj{6#odba8vF}TosRxy#E_!l-w-~l9u=3BUs2TH7vxFlm!)4;;>xcnZGo#$?Zx^rIy=`3u#qJ8 zca4vWUGcCej*m;K9DC;HFTx8?i{;t5;zFfnzW*Vc&JOKK5A4Z~PNhf3(rNf_7yj3k zkx7r`oD9i)0Y@-s7s*YfhsUy`V*?{h-2s#_Fg%tUCK*O&myc|6YuPf{vCN>aY`L)k zC!>*UIjElVl`S)twPj2C$QExU+sIUU$QL)r7;y_NH^OabY%t>|TXtY9ZDtr9{Y17t zw@%yS_yoLr`eJdR?5EOPdMcB1YtGUa%0QUec>2ROG6pTnvgQCPg^Ud3nJVqr=_6V~ z*1+6W(W{|qtMIOD#@dhslu@}wqht>Fq6QKSjt!3bp_a?pstz6$cfVMB>5EM+V{+20k)rk@ z7RyL>I0{v*!Ch|dZIQJPxQ8NJDUd-81isD|5AD`SWLG6adv+{4Kze~-bll;HX3smK z&20@$JBECFC}e<_Z#722)FK1_c?SX*dZDf%#ZHq7`n|KSrf3M0EYfV-LnlQnR7*+EmI!i(3h zCEE#V+uie_gd9CloLQ*h$&e1?7Jjw1t9@CRwm25$&VWgSgJwg0IukNiT?85swPumY z4L*{^TS*26a@rWCCmA$6K0mq_yu0g=xFmXwqWaiKuxk;(m@At0oqVH7%P1T z(G09O%t@S)T<3#Zv^j1t`G8r1qc~f#+!WyETC+5i45t4+>jFIW7_=t1+cY9s57?U8 zwFe$7!+I(rCkmzVRlQV2lU&BqKrVmJ0U5(?#7uu%s8nlu>JTi;$ZVONA!T8%Y`QX6 z6Tv^`kXt9c)#yl2x+P+&Ep!V4`Rf)pVGX(k1q`r`1TP0&8AeBx5{;}#L#;&vyYcC+ zSZ0@U{bwP=$S`syIBwLSyFzC(mo@R=y~0N6t%Cen<$a0@y8!#*Z;X96_DJ;iqkWN| zRKFSiwQx)x4lIW*2S1~pmfo6pR{SUNWl-t&Uc{3GF4{{S0i*jq(qnSi=a{RpxV-LE zrFf}Us?IFvsZ*6_J~qcXpTRxpp{WdbHMZZ7j4bpB=u^aFcqe;gP6u;z%Gp3w$bebe zfW1*+bdD4Gs6D1=tK}HXPW(q;$Nz;LeH|x@@Af0lm;4wRacIc z%vz3Fa2mt+^y1n2`)>(&B4sXz2mF66~Y}1~zCfR5xu;m%%-M(5Gv_WE6gF)-{NJ zVNj(8F3>L?xCUWtu?B~ZF(RM2$xonbwMzj4P)iCMImn=PDSWyHZWRpc)?U7=E;u^0 zxG<**M#rd+Xsu@OpgaHJ9x_@F_ecU5M5ta1#aPKJ>jaYVBXuBm9GZ^2Z;QJdW# zVAF>}&1-L#02y{jWiyPsTC>qR6ePKh(b;}KR4p6XJBSbR~0vg4}<~QJD z*jyQn=5J^!L(N5i=n&q7bnQ}fzFeBKt&JVm8dw|j@BykHU<*d0 z7j%7lkMoF+M%88mD}L$=?#l&^ZET~ci@HSi(I|1eAez799d-uRjtnK5)e)LJy~GXl z+e9H5RNwUx4Vycv&OTSN`lz)Z6&K$pq=mm$6CX;vF7Zermx#u{8h<+eM)A9WQ}Hda z@5G*s{X*>V@cYBB44(=2g}xs&g4=_l{EYG;<#oXam2}{_z%K>5qiN{AE2rifGq!QzOc83DnOuwUxh*Hp>A0IdcLslF`GeRZ44G{2Hpxj3<7y!Tcg&we&4+c zWcT%pVpxmho3~Su@;mQ<(*1o9B@qu&kmRT*>XIx8!d(?4a8KA5kpX8HQ2tuJu;Vh4 zqDKw;NZ~4)&#fSuITdJnK^XE`<;&0K3+ZJL~f$WyEGX65Y!tVL^_0{_BNA1!f3J~AYyXI{{JYX||NO@uR%Wc{; z*uJ!t&j~v%WSXUYk07eZIv5qQrm^#3R1jGPQ_y1;!Lw^)edj}nBGNKL(WTBh{aa8$?4oFjUKiEZO=!Z5i7v{zy*oFfg4pxW z1znye@tkn_w!+xoX*YzPn)@`Q!)^#&lxaxd&ZOB8b^_&U$c{Et2D@W=lxI}Zw~jCc zw@sI0HL_Df1@UBsF6e1UUxJVXh0rCLb_9Fls30hWE$C{83~#jKVXtd6$++|(;XdJu zg7#VM{o3Q&{aQ!jONrl3{B+`CB9oBfe-eLhyb>?OH^sgb`(W&~vGcM1=y#)^ioP{k zijGEOk28rh=$Z}sEqPpdCe_l5s8{HNjfg;&C3;kMA1Lmv%28G0bJE%=YY zKM1}(_*if_C@No6KCZk?xu!gzXd*Xi+-ytrF_lOz@)aZX}Y?oDR zw}F@Ecm=q@vplCQ8?68hy!1kyK-<7BG3nB<1T@G>9<2erwCUb}*MNq()bJxt7l%dQ zJ_E1S@Xm#oI@5*l&c$v6uh>wB^9s*YfOX?uV-`9S+3D~yV0s?f>A1_7feL8N=0l$@ zAIjflTtw9IQqxP_-CfbHfq@oyWZS?4yQ^$o5Tp@HwLIkrtQCQ+#*_+gKivvlqYZh1 z%4(&!cuqIZ1Ic>6&}W>565CPd!6jBI#hDtM34=n;DUNb*HeW~?XArF(t7N$}zfg0w zlB9W5&U5F9(#9l_UZ^fRtA928**u#M?jXd#AL>uqMd1=$Gm3ZPlL>zC&d5F`+ zu_xYvsAEs;rOr53(-Ut;^sy)Q&}S-OPrMC_kM^9s3~0}a_ndbby+nT4J@e3~%ZGyC zitj|^u`BkHcez)*>u2;J(rC`>CG9E;hJ&W?#Bky_VK`_$PYfr%R>MIvdtx|VMzkql z!$GrqVmKZSbZKNbXogP=$4i^G1Y$U7f0G!Fhd5mv8xGoKCx+vt&e#DqoIM7b{3F8= zH4lBJf+C?Y9IXJz81CgjTY_mhXeB`QT|Erw@}U4&&L%`2TaK5!%NmH~Y(%87<#JrUG_3F)TiEIbH^|A((~}CGvyI z@zAHshZHcJ2qKRS$4lO248(9$L>dey>?Q3g3x*R$RI%aULAvFzL&mBQB99HHZLNkA zM2xWEcp1^AfDK1M9Pn7>;Xs#0h7&-v@mS@hOeYxiHXAQ-mnmSgc@-jz z-HnH^t7ty=N<iSeq8=X`2w5(_(Y-~?g98# z>_YVEXm8{x^?CJl_-VLg@ka1_(#Mr&g#Rndif@!>q))AN^3BWrTR{M{|FbWuwiq3A zRAdc_m1=Ju#)U$qP~@8(&TEGwK?XiHW5xNhVcQUVgM$_!?8u7Fw(3HJB#I=PpevcdsT3$ElbN0WABS+toZ zp<$p6Dv;6=4P{VA8ohyKBzg-@Z?TeLnh?=y-TYv5-%li?BFdv{37bV{QPRE#1n?mh zSEzX-`5+dPiWjsjZ~&t7?XY8!?}^=lS2EglqE~XR(L7_+-tb%_fu0J+wmLuZ3RLH0 z)M;rAg6*w?ckZgtF|&4tk(?!BQ3p{rH9v>~{Hds27j-y@;)?Ta=+PjI-6uX9LLlK} ztrcOZ&46U4*RZ>&uh)Q(*iqm6bZ`cW7QmYkL$M%6Ir46M(0Bynt5prD3rjqrb}ylgaghniJ{EUt5R&JXl(VF zV*r>=hOz!ZBHMPjP{%elx&6F8vryF+@f`*1O*$}pVvi&cp{P*oMnXXk@E6L-Fp}qq zE-B}#HgT@NAG)|dT7&46pG#uaxk6n$^UA_OY&H-ZXTT{xYdkQzA0U!ZCtibVnq}Za zU@kfA9tx6E2T**GgDD&w(DWX@LgDo=+SwNnoh9;7ey#b5S8yI60d;1>EgGQWi~>GA z6hj0Jur4Tb)rJ0nj%}IagVBA4!j5zUbxqje6AnHHn>Y1*FL#-+o;2Jr)dzXcj1L6nbZ;8Jp&>_E4`jWU#sKWpF=Y>ST=rB}b zM${#E8=4Q_8QcS1#$xp%ltCRMVb2A@SVh*faO~YY)@0X(Q(|!Q5e%_r4~+h$^w0~5 z1?Tne1OcOa-p94Kt*%<-*BVf$4_E_-6rLN2tl@-ci?U@u&F^#fuJP_umKltjI7LxZ(Z(NUue zY)!kusQ36@fPyk8%WH%fy0e4Xv9PEuGku^imcA=XI@xbS4IQ8 zV2W*N%Q69?Uss0Fv2gQvdG@?)@^~}KC7BOiP=suH`AnT515{~QSf>#rfT4R?Z4+y; z#KpMzO`ffcX5A7wGMIORG8y4W26QWHRt?Tyxh3J)JpcDVe}BT*hsy z4h!34K@07%WU~B}UDwL6cUqH11Z08_wNJ63$v(y4o-BA!pY+K2rl@TTPd_9+6s>^R4PmlJ=VU-1G&){Q5Th1u$rcMYn2J#|E?Xs#K~1z8SwFgW z-a8(ltwk8YO|j$ZmxbM-rv>fP@NU1dHlejc{QpN1^~6+STl}BmpNzjbJ{|dfmvu%kElab!P^ZA`8?l6rR?MRk+wG&Z&jp;dO&QCv4U7)lps z0u}q}D6X4aV?h^W!wz@WQ7kt(8cLV+1f1@uqZn>o|5BM}ux!V+3&>2XNzXP%k4II1~Fj~du4(s@k1C9#zTDePh6cv0|9fxk?!d~i3;T?4xxQ%0oJ(9c1 z!ns3X@7nurx{lA#V0-ak-5`3nj!)BId-1lBwg7A|_!teg7Y_%zG_n_bfCdK;duh`O z!U4o}e1Zl?5POKz#Zkn09Uq~=_Tr`PZ!h=|O+Px5=AqA2KoRG4e2NC!i0Nn0GsYem%tL;`qb@ zzFrL5kC!^50rSMdWf&63i3JaRrUE{(umZ)&87VIVT4nge!d=EPqL1yzL!T~>&N-J6 zb?k4v)LlM=oOD@2l(7@`Px4(j-6RsseGhJ+JsdBBJw5+K4IED2^_>>*)2Gn_C~EQL6}nI*wU!L$U< zT3IjbJYWP(G5T2=O~r7%_4dGPObSPCf&x6XaxTT5c$L|XPL=}G4$7)93F~dj2VP+c zv7RN-M5jQ(dHdl0$ALnBzdC-1G6_Zkw=$qQJXAI#neMrYs`shm9aMFiyQjAGl=t2% zn7KIKMb&1Qv(0znq4(D?cX50ZRiEY_pxL`#Z+M^r)sZdkY>pmF!FhN%zJw@1r}nI6 z8h2`PUc-!B8!urI)bV_C5$HC+>wK)Y_8gcu+t9}nXky3^iRQe9bwWoJ`o(e2P649_ zsyks4R&e0>gC-1~HCJO8s5cYda}*KiQOD60J(Mn8YeE|Xa)mnzc_ZO;2ktj97-MVG zmf#qYO~H&I*?$-)AjddLF>hYoc>~2ghj4Xue56TrMtpdhZ=P%1Uoe?xsM?M6M`VXb z_NEEldk|Fz{S8yq8Rao;cfAN=|D?%&h^pVT?XH(K?7!QrzMHDvRC;u{&3yd6f45mZ zN>y*d9r_O(EHD*!U>#IPUAV=~)sF4r*Z~;StT%PVgMTgzNO3vw9eC=$lGq!6KK_CD ze0*!{FJo_xor!7DXQQu+PDcJE@}DA)MYgM7Qs1u@)z0weLVu+_6TYDRSon_6&jsJ4 z?GK&|Js6BBpH!X-wFg!MyXCLS?^6y2{x$G)`0etve1~*HIwAdc$+;-}1)aihW@|#= z-uk7GAnofB$Ax#ZzQpB;OlyKL893huVKT@W5?3@)UqA%FAcc3a1ZbOfXy5=K*IR%l z>fqA}jdwPuVGc|nhegAlB-J0YX#6}&!?$a?Xjm^AnXEsGXnCCEEiA~YcG#z52?9GY}MJ!8>$GfSgUV{p_~4q~27KQmcBZ8hdiEDfeHrmF-* zEFxB{`=R&@i}>q@(*r>zmR10%n$1 zsqW!2Da#~1LG<_3dl7r6`zn^bPjwR83d=j|kcV(jeT!ND6>F$(vWJjf6TrLc4*`Jv zs7Ji5k?aio18}>783`%~`|C1lI=F_1sp>Rzwgs8-oapVAWGE}tB_uvLy8A0>tDHv6g&Y4@HV;#yZ7MY3~r1%o@*h9?Kj{=G4&p{1I9%}0u25jOQOMS#eBQ6^CHqo1EyZs z-uB;2Q;;0a+c+l;n0jF%58NyfXIL#57f@rsNbX}B)6{4QD9-z$Ckz;#v5`E=(qW9m zR4_jNO|JE@eA>8i5269njA@nz?MaAgfC78{#=XWcf&|QL7X=9;f@!Bf0QxZX`sQ&X zXNquajUr%Q=DJL2!oXuYh%nAlV5D$>z@7x6^sVXRy~aMX8ILq;hMNEy7M-g;#*Kc| z4Dg8SSRwp8BH7P|>M&I@M+cZT1O?$hJ%iy8$7?JdS|fI^^_&MgP~U2jc;y;N*dwKN zm~pJ$jo^Th@^K0d*Vth5w?}zv?ur)V8v=D9+8=qd`p@d4;ZKBjYSW3Q6Fu=KV_%Ow z5dEFdPX@mgJgfYs(gnr%ha_CHz%4MZe}m+vL++uhLm%l z%jcvUyJyAJbvCo$zKtzD$F^ErE}4akpL`EELk4}e(?>S4j|IzE>LxGO=9G0=70A|o zYqI&=r!TQqay6}(%5h(>w0O-;NTI>$>L0=MOYi~+Y5`Y06hhsT@NqkgL{k=V6i z2*wt7Ftgomt3#<^Q~vjZKn8V5T0_}DpC&epY|cg`9LbJ>Z01dqwrsNhy^dM`D|5l6 zKnCo{IX2cvCO9c>9qFfKLK&7K*BQ~Z%Y+6Z%Dip5kg+zIU^&csGpt-Bler23UX}SanUfoTm^V+eS(@ z%egjvK|EUC!|L2IZ;L+4pz!oLAdXGR^?=B#YkD^4$q_jIb|M=!VzzYrAOm%2qs71@ zbra#NEjFvJEu58MbQ>FpaMaz*n_6EFHMA!Ua~{gz2^W$9GdKs=237}8EUX;~MnES6 i1YA!ENQU$cYZZ{Pez$-yO?A3zBLSTUWsHDM#{UO?Jj4?K literal 0 HcmV?d00001 diff --git a/www/fahrschultermin.de/storage/database/app.sqlite.bak-20260422203538 b/www/fahrschultermin.de/storage/database/app.sqlite.bak-20260422203538 new file mode 100644 index 0000000..e69de29 diff --git a/www/fahrschultermin.de/storage/database/app.sqlite.bak-users-20260422204004 b/www/fahrschultermin.de/storage/database/app.sqlite.bak-users-20260422204004 new file mode 100644 index 0000000000000000000000000000000000000000..1627d63e73f4b482685efca50352f1d5b41e7bf3 GIT binary patch literal 73728 zcmeI5-EZ606~IZ6`k-uyN!wHrJTJ_gEHRwK^0%F|NM)OL9DgM72l4@QDlJ`EtR+(B zM;v<(MVh9?p7yX69R?J88ul|Iw(Eb6#3al>$h5-ZGr)}s`#bku-gE9HxpHekHi%GEwTfg2L)0;frl}VMfug7x`2Q6AcehU1 zVckFAH{Gxwv^zsx+T99UC-eWZ&f#mnf=gi=HKUk%fHC)@;`?|xFG=~fCP{L5NiJWObUkN~O0_H*M30<~!u4*Vd$>`M zeiA4r_egifm~L009y%6g&eCO~>#CA7cB({=^DNUt%ZhGjX5LUWJ;X6giZ*qk>1;=c z>E?7|7!Wbg<22L78AOp3L$~Jt*+ib=?<9Yf?1UZMkN^@u0!RP}AOR$R1dsp{KmthM zpadR%o9(2y)6YK)r%%8f?w*HMgxTCODQQG|L2+htFM(%hoEh9XGcb7O`s~uCdQa2) zXQjcdjopkkIC*d9(!%wH`@^^IRrw-_sD;#Og+aBE$i%Z$#EYirrfVs-0fb50uHT9hn~S#x7^QlTO%_7C4o&dse$3zdyUZDQ_Prh4arVa~2Ck(IF_V`qNset)&NoY5Dz zN5sPJFqtlv?+o9bSf0HuDblki_s!;68lIOwJpo3ySn6h#Xmo+I#mFm51W!faGGnvgHbMo5O`uO7bsIpw%91+QGwKzO` z=h}mnDHOs$6@qggo;@{!_a?nbS*C7EUfCP{L5wg^E@3C9%9qX80{O9VN%Qx?GptFrI`uNKk#79|A+YRQ2a092X06J2_OL^ zfCP{L5>a{Q5BiYYf{nZ$&L+=5017@*YCGP z;|6UBJYfr@_ttv!WW~q!HVeSALVxXbFi%!+c zqQX2nNutX<=|6$A@5>ah^Yqw6CBN1_hSEQyj;|K*6*` zDizztVDV{F*(Bu>!0H*E92yy&yy$3Bd?kr+%%_kn*pHP8p%{Jiz9J0eUC2z~HFY~Syl4THd^Psc2Qd(sXdNMu3K@l&>- zdal~f&pZQSPFSztpX!^cXUHnuLGl!;=cz900Ai+S5W{y{MRQBme!;Bg|0DeS6#q~D zZ~Vvb{{KJnpZS^?4Fn1dsp{Kmter2_OL^fCP{L5@;ciJ{hIx z)M=WI*sJH4BzQi+Ud7Ka=@cY&bwiS~@ZDV&9}cCSwXQlva}j%yIxH}UPrSoI_QT3d&HqP&=KsS?(ENWiX#PL8&-{PV^8cx0`w_r&B!C2v01`j~NB{{S z0VIF~kN^@u0!ZMiOd!#bpknNMRPxj0+nw(uUuNHny%KpC%dme6eL#;V-~1|<4fBo! zkigL;@Mtm|P4)E9ug@FOdYQ=Zksr;>8>*%^>_(=R#q6pmtY#+{M4=%;I3E{alMBM! z(yBNsE(_@y)m~0*Di`ns~7Au;k!YSV&obwSh}$)EUhgp)Y6Nx zrW-j$s=(4P@eZVTQsIrb!HIdbtitz?TCY-6gXpz{X>lgIwy-LkJ6BUD>$%#>vwHqr zg@SwPK<&)DM&SEOg`8ygsW7Vr|5U-$iPl(LSL|MqXKpNubF)h}vGa94y~46MBQA?e zQ{swHJE{xkBX))gW=t4A_x^jhntlCB_!*_CE4O}j7y+Jr< zkV>@-xzhs<$2`UjIO)(0omUIKCJr(^SSqhp)@4O9WK{_^SLO?u%dVqqOLMo@L}AYs z7X&M}y*0^)V6MejBqd#;DH%R<^g!P#!`HNO5`41^wOMmZf z&H6;wRV8QaRKes8`%$l*8&VG4xIsn?Y@!8Hl+3b`E5N6ueC_L46{_)us_pnSL5*yg zvPLQdx|dDjoYLCbpdPKu<+9b+?OTcLI%j>3Y_EH3=^mW=2B&HSIwy^k$$jYI_LW_f zA3$#sSl)W~%X<&i1bqah}mN~P%6 zMZ43mZd1DbgY|S1_MyY*AL@5_O@)@{bC)I+NiKU6gj!ErYx`BWmu*7}$dx3$9#>Jw}CL?2hi zm2-IROg|q)rZJOEzmw!3}c-p5RLGXX9S(pX#aC^;+Vo()Hn_Zycz>_~Qv=h` zHG31Bq84&?{~S>1+X%gQYRq2FJyvrLx759QF!!}++#YVcAvK!CwSeB?2OFdvayRTo zjf1<+FW_%iOLZU4?c3dm{1f`LCwGlC2j1x!-4^5D)3|NJKh;J#b?je>P2_OL^fCP{L5