mirror of
https://github.com/Dvorinka/PPve.git
synced 2026-06-03 20:12:59 +00:00
test rezervace - aut
This commit is contained in:
+109
-100
@@ -912,9 +912,6 @@
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-icon {
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-box:hover .upload-icon {
|
.upload-box:hover .upload-icon {
|
||||||
color: #6c757d;
|
color: #6c757d;
|
||||||
}
|
}
|
||||||
@@ -1208,6 +1205,51 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Add this new card before the existing cards -->
|
||||||
|
<div class="card" style="margin: 2rem auto; max-width: 1000px;">
|
||||||
|
<h3>Správa rezervací vozidel</h3>
|
||||||
|
|
||||||
|
<div class="flex justify-between items-center mb-6">
|
||||||
|
<div class="space-x-2">
|
||||||
|
<button class="btn btn-primary" onclick="location.href='/rezervace-aut'">
|
||||||
|
<i class="fas fa-calendar-plus mr-2"></i>Nová rezervace
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary" onclick="exportReservations()">
|
||||||
|
<i class="fas fa-file-export mr-2"></i>Export
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
<select id="vehicleFilter" class="form-control" onchange="filterReservations()">
|
||||||
|
<option value="">Všechna vozidla</option>
|
||||||
|
<option value="VW Caddy">VW Caddy</option>
|
||||||
|
<option value="VW Golf">VW Golf</option>
|
||||||
|
<option value="Škoda Fabia">Škoda Fabia</option>
|
||||||
|
<option value="BMW 218d">BMW 218d</option>
|
||||||
|
<option value="Škoda Superb">Škoda Superb</option>
|
||||||
|
</select>
|
||||||
|
<input type="date" id="dateFilter" class="form-control" onchange="filterReservations()">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Řidič</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Vozidlo</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Od</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Do</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Účel</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Akce</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="reservationsTable" class="bg-white divide-y divide-gray-200">
|
||||||
|
<!-- Reservations will be populated here -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Icon Picker Modal -->
|
<!-- Icon Picker Modal -->
|
||||||
@@ -1592,37 +1634,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle drop
|
// Handle drop
|
||||||
dropArea.addEventListener('drop', handleDrop, false);
|
|
||||||
|
|
||||||
function handleDrop(e) {
|
|
||||||
const dt = e.dataTransfer;
|
|
||||||
const files = dt.files;
|
|
||||||
if (files.length > 0) {
|
|
||||||
handleFileSelect(files[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle drag and drop
|
|
||||||
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
|
||||||
dropArea.addEventListener(eventName, preventDefaults, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
function highlight() {
|
|
||||||
dropArea.classList.add('dragover');
|
|
||||||
}
|
|
||||||
|
|
||||||
function unhighlight() {
|
|
||||||
dropArea.classList.remove('dragover');
|
|
||||||
}
|
|
||||||
|
|
||||||
['dragenter', 'dragover'].forEach(eventName => {
|
|
||||||
dropArea.addEventListener(eventName, highlight, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
['dragleave', 'drop'].forEach(eventName => {
|
|
||||||
dropArea.addEventListener(eventName, unhighlight, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
dropArea.addEventListener('drop', function(e) {
|
dropArea.addEventListener('drop', function(e) {
|
||||||
const dt = e.dataTransfer;
|
const dt = e.dataTransfer;
|
||||||
const file = dt.files[0];
|
const file = dt.files[0];
|
||||||
@@ -2809,81 +2820,79 @@ async function loadBanner() {
|
|||||||
const positionButtons = document.querySelectorAll('.position-btn');
|
const positionButtons = document.querySelectorAll('.position-btn');
|
||||||
if (positionButtons) {
|
if (positionButtons) {
|
||||||
positionButtons.forEach(btn => {
|
positionButtons.forEach(btn => {
|
||||||
if (btn) {
|
btn.classList.remove('active', 'btn-primary');
|
||||||
btn.classList.remove('active', 'btn-primary');
|
btn.classList.add('btn-outline-secondary');
|
||||||
btn.classList.add('btn-outline-secondary');
|
if (btn.dataset.position === currentImagePosition) {
|
||||||
if (btn.dataset.position === currentImagePosition) {
|
btn.classList.add('active', 'btn-primary');
|
||||||
btn.classList.add('active', 'btn-primary');
|
btn.classList.remove('btn-outline-secondary');
|
||||||
btn.classList.remove('btn-outline-secondary');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update coordinates if they exist
|
||||||
|
if (data.Style?.ImageX !== undefined && data.Style?.ImageY !== undefined) {
|
||||||
|
currentImageX = data.Style.ImageX;
|
||||||
|
currentImageY = data.Style.ImageY;
|
||||||
|
|
||||||
// Update coordinates if they exist
|
// Update image preview position if it exists
|
||||||
if (data.Style?.ImageX !== undefined && data.Style?.ImageY !== undefined) {
|
if (bannerImgElement) {
|
||||||
currentImageX = data.Style.ImageX;
|
bannerImgElement.style.left = `${currentImageX}px`;
|
||||||
currentImageY = data.Style.ImageY;
|
bannerImgElement.style.top = `${currentImageY}px`;
|
||||||
|
|
||||||
// Update image preview position if it exists
|
// Activate custom position button if it exists
|
||||||
if (bannerImgElement) {
|
const customPosBtn = document.getElementById('customPosBtn');
|
||||||
bannerImgElement.style.left = `${currentImageX}px`;
|
if (customPosBtn) {
|
||||||
bannerImgElement.style.top = `${currentImageY}px`;
|
customPosBtn.classList.add('active', 'btn-primary');
|
||||||
|
customPosBtn.classList.remove('btn-outline-secondary');
|
||||||
// Activate custom position button if it exists
|
|
||||||
const customPosBtn = document.getElementById('customPosBtn');
|
|
||||||
if (customPosBtn) {
|
|
||||||
customPosBtn.classList.add('active', 'btn-primary');
|
|
||||||
customPosBtn.classList.remove('btn-outline-secondary');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show remove button if it exists
|
|
||||||
const removeBtn = document.getElementById('removeImageBtn');
|
|
||||||
if (removeBtn) {
|
|
||||||
removeBtn.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set remove image input if it exists
|
|
||||||
const removeImageInput = document.getElementById('removeImage');
|
|
||||||
if (removeImageInput) {
|
|
||||||
removeImageInput.value = 'false';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// No image in the saved banner
|
|
||||||
currentImage = null;
|
|
||||||
|
|
||||||
// Hide image preview and show upload prompt
|
|
||||||
if (imagePreview) {
|
|
||||||
imagePreview.style.display = 'none';
|
|
||||||
}
|
|
||||||
if (uploadPrompt) {
|
|
||||||
uploadPrompt.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide remove button
|
|
||||||
const removeBtn = document.getElementById('removeImageBtn');
|
|
||||||
if (removeBtn) {
|
|
||||||
removeBtn.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set remove image input
|
|
||||||
const removeImageInput = document.getElementById('removeImage');
|
|
||||||
if (removeImageInput) {
|
|
||||||
removeImageInput.value = 'true';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update previews
|
// Show remove button if it exists
|
||||||
updateColorPreviews();
|
const removeBtn = document.getElementById('removeImageBtn');
|
||||||
updateBannerPreview();
|
if (removeBtn) {
|
||||||
|
removeBtn.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set remove image input if it exists
|
||||||
|
const removeImageInput = document.getElementById('removeImage');
|
||||||
|
if (removeImageInput) {
|
||||||
|
removeImageInput.value = 'false';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No image in the saved banner
|
||||||
|
currentImage = null;
|
||||||
|
|
||||||
|
// Hide image preview and show upload prompt
|
||||||
|
if (imagePreview) {
|
||||||
|
imagePreview.style.display = 'none';
|
||||||
|
}
|
||||||
|
if (uploadPrompt) {
|
||||||
|
uploadPrompt.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide remove button
|
||||||
|
const removeBtn = document.getElementById('removeImageBtn');
|
||||||
|
if (removeBtn) {
|
||||||
|
removeBtn.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set remove image input
|
||||||
|
const removeImageInput = document.getElementById('removeImage');
|
||||||
|
if (removeImageInput) {
|
||||||
|
removeImageInput.value = 'true';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error('Chyba při načítání banneru:', error);
|
// Update previews
|
||||||
showNotification('Chyba při načítání banneru', 'error');
|
updateColorPreviews();
|
||||||
|
updateBannerPreview();
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Chyba při načítání banneru:', error);
|
||||||
|
showNotification('Chyba při načítání banneru', 'error');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add submission flag at the top of the script
|
// Add submission flag at the top of the script
|
||||||
@@ -2938,7 +2947,7 @@ async function saveBanner(event) {
|
|||||||
const fontSize = bannerFontSize?.value || (currentTemplate && templateConfigs[currentTemplate]?.fontSize) || '16';
|
const fontSize = bannerFontSize?.value || (currentTemplate && templateConfigs[currentTemplate]?.fontSize) || '16';
|
||||||
const padding = bannerPadding?.value || (currentTemplate && templateConfigs[currentTemplate]?.padding) || '20';
|
const padding = bannerPadding?.value || (currentTemplate && templateConfigs[currentTemplate]?.padding) || '20';
|
||||||
const margin = bannerMargin?.value || (currentTemplate && templateConfigs[currentTemplate]?.margin) || '20';
|
const margin = bannerMargin?.value || (currentTemplate && templateConfigs[currentTemplate]?.margin) || '20';
|
||||||
const borderRadius = bannerBorderRadius?.value || (currentTemplate && templateConfigs[currentTemplate]?.borderRadius) || '8';
|
const borderRadius = bannerBorderRadius?.value || (currentTemplate && templateConfigs[currentTemplate]?.borderRadius) || '4';
|
||||||
const buttonBg = (currentTemplate && templateConfigs[currentTemplate]?.buttonBackground) || '#4a6cf7';
|
const buttonBg = (currentTemplate && templateConfigs[currentTemplate]?.buttonBackground) || '#4a6cf7';
|
||||||
const buttonTextColor = (currentTemplate && templateConfigs[currentTemplate]?.buttonTextColor) || '#ffffff';
|
const buttonTextColor = (currentTemplate && templateConfigs[currentTemplate]?.buttonTextColor) || '#ffffff';
|
||||||
const buttonBorder = (currentTemplate && templateConfigs[currentTemplate]?.buttonBorder) || 'none';
|
const buttonBorder = (currentTemplate && templateConfigs[currentTemplate]?.buttonBorder) || 'none';
|
||||||
@@ -3290,7 +3299,7 @@ function updateBannerPreview() {
|
|||||||
margin: `${bannerMargin}px`,
|
margin: `${bannerMargin}px`,
|
||||||
borderRadius: `${bannerBorderRadius}px`,
|
borderRadius: `${bannerBorderRadius}px`,
|
||||||
|
|
||||||
// Button styles
|
// Button styles (if template defines them)
|
||||||
buttonBackground: bannerButtonBackground,
|
buttonBackground: bannerButtonBackground,
|
||||||
buttonTextColor: bannerButtonTextColor,
|
buttonTextColor: bannerButtonTextColor,
|
||||||
buttonBorder: bannerButtonBorder
|
buttonBorder: bannerButtonBorder
|
||||||
|
|||||||
+13
@@ -381,6 +381,7 @@
|
|||||||
<a href="http://osticket/" class="hover:text-brand-light-blue">OSticket</a>
|
<a href="http://osticket/" class="hover:text-brand-light-blue">OSticket</a>
|
||||||
<a href="http://kanboard/" class="hover:text-brand-light-blue">Kanboard</a>
|
<a href="http://kanboard/" class="hover:text-brand-light-blue">Kanboard</a>
|
||||||
<a href="http://webportal:8080" class="hover:text-brand-light-blue">Kontakt</a>
|
<a href="http://webportal:8080" class="hover:text-brand-light-blue">Kontakt</a>
|
||||||
|
<a href="/rezervace-aut" class="hover:text-brand-light-blue">Rezervace aut</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -392,6 +393,7 @@
|
|||||||
<a href="http://osticket/" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">OSticket</a>
|
<a href="http://osticket/" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">OSticket</a>
|
||||||
<a href="http://kanboard/" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">Kanboard</a>
|
<a href="http://kanboard/" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">Kanboard</a>
|
||||||
<a href="http://webportal:8080" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">Kontakt</a>
|
<a href="http://webportal:8080" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">Kontakt</a>
|
||||||
|
<a href="/rezervace-aut" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">Rezervace aut</a>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
@@ -474,6 +476,17 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card bg-white rounded-xl shadow p-6 border-t-4 border-indigo-600" data-name="rezervace aut vozidel calendar">
|
||||||
|
<div class="rounded-full w-14 h-14 flex items-center justify-center bg-indigo-100 text-indigo-600 mb-4">
|
||||||
|
<i class="fas fa-calendar-alt text-2xl"></i>
|
||||||
|
</div>
|
||||||
|
<h2 class="text-xl font-bold text-gray-800 mb-2">Rezervace služebních vozů</h2>
|
||||||
|
<p class="text-gray-600 mb-4">Kalendář pro rezervaci a plánování služebních jízd</p>
|
||||||
|
<a href="/rezervace-aut" class="block text-center bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-4 rounded-lg transition-colors">
|
||||||
|
Otevřít aplikaci
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Dynamic apps will be loaded here -->
|
<!-- Dynamic apps will be loaded here -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,15 @@ type GeoCoords struct {
|
|||||||
Lng string `json:"lng"`
|
Lng string `json:"lng"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Reservation struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
DriverName string `json:"driverName"`
|
||||||
|
Vehicle string `json:"vehicle"`
|
||||||
|
StartDateTime time.Time `json:"startDateTime"`
|
||||||
|
EndDateTime time.Time `json:"endDateTime"`
|
||||||
|
Purpose string `json:"purpose"`
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||||
|
|
||||||
@@ -150,6 +159,11 @@ func main() {
|
|||||||
api.HandleFunc("/apps/{id}", UpdateAppHandler).Methods("PUT")
|
api.HandleFunc("/apps/{id}", UpdateAppHandler).Methods("PUT")
|
||||||
api.HandleFunc("/apps/{id}", DeleteAppHandler).Methods("DELETE")
|
api.HandleFunc("/apps/{id}", DeleteAppHandler).Methods("DELETE")
|
||||||
|
|
||||||
|
// Reservation system routes
|
||||||
|
api.HandleFunc("/reservations", handleGetReservations).Methods("GET")
|
||||||
|
api.HandleFunc("/reservations", handleCreateReservation).Methods("POST")
|
||||||
|
api.HandleFunc("/check-availability", handleCheckAvailability).Methods("GET")
|
||||||
|
|
||||||
// Admin routes - defined before the catch-all static file server
|
// Admin routes - defined before the catch-all static file server
|
||||||
r.HandleFunc("/admin", func(w http.ResponseWriter, r *http.Request) {
|
r.HandleFunc("/admin", func(w http.ResponseWriter, r *http.Request) {
|
||||||
http.ServeFile(w, r, "admin.html")
|
http.ServeFile(w, r, "admin.html")
|
||||||
@@ -282,6 +296,111 @@ func saveApps(apps []App) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reservation Handlers
|
||||||
|
func handleGetReservations(w http.ResponseWriter, r *http.Request) {
|
||||||
|
reservations, err := loadReservations()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to load reservations", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(reservations)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleCreateReservation(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var reservation Reservation
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&reservation); err != nil {
|
||||||
|
http.Error(w, "Invalid reservation data", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reservation.ID = fmt.Sprintf("res_%d", time.Now().UnixNano())
|
||||||
|
|
||||||
|
reservations, err := loadReservations()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to load reservations", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reservations = append(reservations, reservation)
|
||||||
|
|
||||||
|
if err := saveReservations(reservations); err != nil {
|
||||||
|
http.Error(w, "Failed to save reservation", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
json.NewEncoder(w).Encode(reservation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleCheckAvailability(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vehicle := r.URL.Query().Get("vehicle")
|
||||||
|
startStr := r.URL.Query().Get("start")
|
||||||
|
endStr := r.URL.Query().Get("end")
|
||||||
|
|
||||||
|
start, err := time.Parse(time.RFC3339, startStr)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Invalid start time", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
end, err := time.Parse(time.RFC3339, endStr)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Invalid end time", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reservations, err := loadReservations()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to load reservations", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
available := true
|
||||||
|
for _, res := range reservations {
|
||||||
|
if res.Vehicle == vehicle {
|
||||||
|
if start.Before(res.EndDateTime) && end.After(res.StartDateTime) {
|
||||||
|
available = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(map[string]bool{"available": available})
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadReservations() ([]Reservation, error) {
|
||||||
|
data, err := os.ReadFile("data/reservations.json")
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return []Reservation{}, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var reservations []Reservation
|
||||||
|
if err := json.Unmarshal(data, &reservations); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return reservations, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveReservations(reservations []Reservation) error {
|
||||||
|
data, err := json.MarshalIndent(reservations, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll("data", 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.WriteFile("data/reservations.json", data, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
// App Handlers
|
// App Handlers
|
||||||
func GetAppsHandler(w http.ResponseWriter, r *http.Request) {
|
func GetAppsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// Set CORS headers
|
// Set CORS headers
|
||||||
|
|||||||
@@ -0,0 +1,490 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="cs">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Poppe Potthoff - Rezervace služebních vozů</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" />
|
||||||
|
<link href='https://cdn.jsdelivr.net/npm/fullcalendar@5.11.3/main.css' rel='stylesheet' />
|
||||||
|
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@5.11.3/main.js'></script>
|
||||||
|
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@5.11.3/locales/cs.js'></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
tailwind.config = {
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
'brand-blue': '#004990',
|
||||||
|
'brand-light-blue': '#0072b0',
|
||||||
|
'brand-gray': '#f0f2f5'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.fc-event {
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-event:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-toolbar-title {
|
||||||
|
text-transform: capitalize;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2d3748;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc .fc-button-primary {
|
||||||
|
background-color: #004990;
|
||||||
|
border-color: #004990;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc .fc-button-primary:hover {
|
||||||
|
background-color: #0072b0;
|
||||||
|
border-color: #0072b0;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc .fc-button-primary:not(:disabled).fc-button-active,
|
||||||
|
.fc .fc-button-primary:not(:disabled):active {
|
||||||
|
background-color: #0072b0;
|
||||||
|
border-color: #0072b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-view-harness {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-scrollgrid {
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-col-header-cell {
|
||||||
|
background: #f8fafc;
|
||||||
|
padding: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-day-today {
|
||||||
|
background: #e8f4ff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-timegrid-slot {
|
||||||
|
height: 3rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-timegrid-slot-label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reservation-card {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reservation-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 16px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Vehicle badges */
|
||||||
|
.vehicle-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 9999px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vehicle-badge i {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Vehicle-specific colors */
|
||||||
|
.vehicle-vw-caddy { background-color: #e6f3ff; color: #1a73e8; }
|
||||||
|
.vehicle-vw-golf { background-color: #e6ffe6; color: #28a745; }
|
||||||
|
.vehicle-skoda-fabia { background-color: #fff3e6; color: #fd7e14; }
|
||||||
|
.vehicle-bmw-218d { background-color: #f3e6ff; color: #6f42c1; }
|
||||||
|
.vehicle-skoda-superb { background-color: #ffe6e6; color: #dc3545; }
|
||||||
|
|
||||||
|
/* Responsive calendar styles */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.fc .fc-toolbar {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc .fc-toolbar-title {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-header-toolbar {
|
||||||
|
padding: 0.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-view-harness {
|
||||||
|
height: 500px !important; /* Smaller height on mobile */
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc .fc-button {
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust calendar size for desktop */
|
||||||
|
.calendar-container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-view-harness {
|
||||||
|
height: 600px !important; /* Default height for desktop */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form container styles */
|
||||||
|
.form-container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 2rem;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form group spacing */
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive form layout */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.form-container {
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-cols-2 {
|
||||||
|
grid-template-columns: 1fr !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Improve button responsiveness */
|
||||||
|
.btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.btn {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="bg-brand-gray min-h-screen">
|
||||||
|
<!-- Nav bar copied from existing template -->
|
||||||
|
<nav class="bg-brand-blue text-white shadow-lg">
|
||||||
|
<div class="max-w-6xl mx-auto px-4 py-3 flex justify-between items-center">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<a href="http://webportal/index.html"><img src="http://pp-kunovice.cz/wp-content/uploads/2022/04/logo-retina-white.png" alt="Poppe Potthoff Logo" class="h-10"></a>
|
||||||
|
<div class="hidden md:block text-xl font-semibold">Poppe + Potthoff</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile menu button -->
|
||||||
|
<div class="md:hidden">
|
||||||
|
<button id="mobile-menu-button" class="text-white focus:outline-none">
|
||||||
|
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Desktop menu -->
|
||||||
|
<div class="hidden md:flex space-x-6">
|
||||||
|
<a href="http://webportal/index.html" class="hover:text-brand-light-blue">Rozcestník</a>
|
||||||
|
<a href="http://webportal/evidence-aut" class="hover:text-brand-light-blue">Evidence aut</a>
|
||||||
|
<a href="http://ppc-app/pwkweb2/" class="hover:text-brand-light-blue">Obědy</a>
|
||||||
|
<a href="http://osticket/" class="hover:text-brand-light-blue">OSticket</a>
|
||||||
|
<a href="http://kanboard/" class="hover:text-brand-light-blue">Kanboard</a>
|
||||||
|
<a href="http://webportal:8080" class="hover:text-brand-light-blue">Kontakt</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile menu -->
|
||||||
|
<div id="mobile-menu" class="hidden md:hidden px-2 pt-2 pb-3 space-y-1">
|
||||||
|
<a href="webportal/index.html" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">Rozcestník</a>
|
||||||
|
<a href="webportal/evidence-aut" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">Evidence aut</a>
|
||||||
|
<a href="http://ppc-app/pwkweb2/" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">Obědy</a>
|
||||||
|
<a href="http://osticket/" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">OSticket</a>
|
||||||
|
<a href="http://kanboard/" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">Kanboard</a>
|
||||||
|
<a href="http://webportal:8080" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">Kontakt</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Page Header -->
|
||||||
|
<div class="bg-gradient-to-r from-brand-blue to-brand-light-blue text-white py-6 mb-8">
|
||||||
|
<div class="max-w-6xl mx-auto px-4">
|
||||||
|
<h1 class="text-3xl font-bold">Poppe + Potthoff – Rezervace služebních vozů</h1>
|
||||||
|
<p class="text-gray-100 mt-2">Systém pro rezervaci služebních vozidel</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div class="container mx-auto px-4 py-8">
|
||||||
|
<div class="calendar-container">
|
||||||
|
<div id='calendar'></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Reservation Form -->
|
||||||
|
<div class="form-container">
|
||||||
|
<h2 class="text-2xl font-bold mb-6 text-gray-800">Nová rezervace</h2>
|
||||||
|
<form id="reservationForm" class="space-y-6">
|
||||||
|
<!-- Driver Name -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="driverName">Jméno řidiče</label>
|
||||||
|
<input type="text" id="driverName" name="driverName" required
|
||||||
|
class="w-full p-2 border border-gray-300 rounded-md">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Vehicle Selection -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="vehicle">Vozidlo</label>
|
||||||
|
<select id="vehicle" name="vehicle" required
|
||||||
|
class="w-full p-2 border border-gray-300 rounded-md">
|
||||||
|
<option value="">Vyberte vozidlo...</option>
|
||||||
|
<option value="VW Caddy">VW Caddy - 4Z1 8241</option>
|
||||||
|
<option value="VW Golf">VW Golf - 5Z5 8694</option>
|
||||||
|
<option value="Škoda Fabia">Škoda Fabia - 1Z3 5789</option>
|
||||||
|
<option value="BMW 218d">BMW 218d - 6Z5 4739</option>
|
||||||
|
<option value="Škoda Superb">Škoda Superb - 2BY 2398</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Date and Time Selection -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="startDateTime">Začátek</label>
|
||||||
|
<input type="datetime-local" id="startDateTime" name="startDateTime" required
|
||||||
|
class="w-full p-2 border border-gray-300 rounded-md">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="endDateTime">Konec</label>
|
||||||
|
<input type="datetime-local" id="endDateTime" name="endDateTime" required
|
||||||
|
class="w-full p-2 border border-gray-300 rounded-md">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Purpose -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="purpose">Účel jízdy</label>
|
||||||
|
<textarea id="purpose" name="purpose" rows="3" required
|
||||||
|
class="w-full p-2 border border-gray-300 rounded-md"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Submit Button -->
|
||||||
|
<div class="flex flex-col sm:flex-row gap-4 justify-end">
|
||||||
|
<button type="button" id="cancelBtn" class="btn bg-gray-500 text-white hover:bg-gray-600">
|
||||||
|
Zrušit
|
||||||
|
</button>
|
||||||
|
<button type="submit" class="btn bg-brand-blue text-white hover:bg-brand-light-blue">
|
||||||
|
Rezervovat
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer copied from existing template -->
|
||||||
|
<footer class="bg-gray-800 text-gray-400 py-8 mt-12">
|
||||||
|
<div class="max-w-6xl mx-auto px-4">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
|
<!-- Company Info -->
|
||||||
|
<div>
|
||||||
|
<h3 class="text-white text-lg font-semibold mb-4">Poppe + Potthoff CZ</h3>
|
||||||
|
<p class="mb-2">IČO: 26902214</p>
|
||||||
|
<p class="mb-2">DIČ: CZ26902214</p>
|
||||||
|
<p class="mb-2">Schránka: gfrk5qy</p>
|
||||||
|
<p>Na Záhonech 1086, 686 04 Kunovice</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Quick Links -->
|
||||||
|
<div>
|
||||||
|
<h3 class="text-white text-lg font-semibold mb-4">Rychlé odkazy</h3>
|
||||||
|
<ul class="space-y-2">
|
||||||
|
<li><a href="http://webportal/" class="hover:text-white">Rozcestník</a></li>
|
||||||
|
<li><a href="http://webportal/evidence-aut" class="hover:text-white">Evidence aut</a></li>
|
||||||
|
<li><a href="http://ppc-app/pwkweb2/" class="hover:text-white">Objednávka obědů</a></li>
|
||||||
|
<li><a href="http://osticket/" class="hover:text-white">Technická podpora</a></li>
|
||||||
|
<li><a href="http://webportal:8080" class="hover:text-white">Kontakty</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Copyright -->
|
||||||
|
<div class="md:text-right">
|
||||||
|
<img src="http://pp-kunovice.cz/wp-content/uploads/2022/04/logo-retina-white.png" alt="Poppe Potthoff Logo" class="h-10 mb-4 inline-block">
|
||||||
|
<p class="text-sm"> 2025 Poppe + Potthoff CZ</p>
|
||||||
|
<p class="text-xs mt-2">Všechna práva vyhrazena</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border-t border-gray-700 mt-8 pt-6 text-center text-sm">
|
||||||
|
<p>Created by <a href="https://tdvorak.dev" class="text-blue-400 hover:text-blue-300">TDvorak</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
let calendar;
|
||||||
|
const reservationForm = document.getElementById('reservationForm');
|
||||||
|
const statusMessage = document.getElementById('statusMessage');
|
||||||
|
|
||||||
|
// Initialize FullCalendar
|
||||||
|
const calendarEl = document.getElementById('calendar');
|
||||||
|
calendar = new FullCalendar.Calendar(calendarEl, {
|
||||||
|
initialView: 'timeGridWeek',
|
||||||
|
headerToolbar: {
|
||||||
|
left: 'prev,next today',
|
||||||
|
center: 'title',
|
||||||
|
right: 'dayGridMonth,timeGridWeek,timeGridDay'
|
||||||
|
},
|
||||||
|
locale: 'cs',
|
||||||
|
slotMinTime: '06:00:00',
|
||||||
|
slotMaxTime: '22:00:00',
|
||||||
|
allDaySlot: false,
|
||||||
|
slotDuration: '00:30:00',
|
||||||
|
businessHours: {
|
||||||
|
daysOfWeek: [1, 2, 3, 4, 5],
|
||||||
|
startTime: '06:00',
|
||||||
|
endTime: '22:00',
|
||||||
|
},
|
||||||
|
events: '/api/reservations',
|
||||||
|
eventClick: function(info) {
|
||||||
|
alert(`
|
||||||
|
Řidič: ${info.event.title}
|
||||||
|
Vozidlo: ${info.event.extendedProps.vehicle}
|
||||||
|
Účel: ${info.event.extendedProps.purpose}
|
||||||
|
Od: ${info.event.start.toLocaleString()}
|
||||||
|
Do: ${info.event.end.toLocaleString()}
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
calendar.render();
|
||||||
|
|
||||||
|
// Form submission
|
||||||
|
reservationForm.addEventListener('submit', async function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Validate dates
|
||||||
|
const startDate = new Date(document.getElementById('startDateTime').value);
|
||||||
|
const endDate = new Date(document.getElementById('endDateTime').value);
|
||||||
|
|
||||||
|
if (endDate <= startDate) {
|
||||||
|
showStatus('Čas ukončení musí být později než čas začátku', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check vehicle availability
|
||||||
|
const vehicle = document.getElementById('vehicle').value;
|
||||||
|
const isAvailable = await checkVehicleAvailability(vehicle, startDate, endDate);
|
||||||
|
|
||||||
|
if (!isAvailable) {
|
||||||
|
showStatus('Vozidlo není v tomto čase dostupné', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare reservation data
|
||||||
|
const reservationData = {
|
||||||
|
driverName: document.getElementById('driverName').value,
|
||||||
|
vehicle: vehicle,
|
||||||
|
startDateTime: startDate.toISOString(),
|
||||||
|
endDateTime: endDate.toISOString(),
|
||||||
|
purpose: document.getElementById('purpose').value
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/reservations', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(reservationData)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Chyba při vytváření rezervace');
|
||||||
|
}
|
||||||
|
|
||||||
|
showStatus('Rezervace byla úspěšně vytvořena', 'success');
|
||||||
|
calendar.refetchEvents();
|
||||||
|
reservationForm.reset();
|
||||||
|
} catch (error) {
|
||||||
|
showStatus(error.message, 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function checkVehicleAvailability(vehicle, startDate, endDate) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/check-availability?vehicle=${encodeURIComponent(vehicle)}&start=${startDate.toISOString()}&end=${endDate.toISOString()}`);
|
||||||
|
const data = await response.json();
|
||||||
|
return data.available;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking availability:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showStatus(message, type) {
|
||||||
|
statusMessage.textContent = message;
|
||||||
|
statusMessage.className = 'mt-4 p-4 rounded-md';
|
||||||
|
statusMessage.classList.remove('hidden');
|
||||||
|
|
||||||
|
if (type === 'success') {
|
||||||
|
statusMessage.classList.add('bg-green-100', 'text-green-700');
|
||||||
|
} else {
|
||||||
|
statusMessage.classList.add('bg-red-100', 'text-red-700');
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
statusMessage.classList.add('hidden');
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user