From 2a3440ebe93d178e3750ae03896ea1be60041928 Mon Sep 17 00:00:00 2001
From: "[Valentino Heman Budiarto]" <[hemanvalentino@gmail.com]>
Date: Fri, 6 Mar 2026 13:53:39 +0700
Subject: [PATCH] 6 maret 2026
---
backend/cmd/main.go | 13 ++++---
backend/controllers/bookingcontroller.go | 48 +++++++++++++++++++-----
frontend/app/history/page.tsx | 29 +++++++++++---
3 files changed, 69 insertions(+), 21 deletions(-)
diff --git a/backend/cmd/main.go b/backend/cmd/main.go
index 2799723..b8feca2 100644
--- a/backend/cmd/main.go
+++ b/backend/cmd/main.go
@@ -5,7 +5,7 @@ import (
"s-class-backend/config"
"s-class-backend/controllers"
"s-class-backend/middleware"
- "s-class-backend/models" // <--- Pastikan ini ada!
+ "s-class-backend/models"
"github.com/gin-gonic/gin"
)
@@ -15,7 +15,6 @@ func main() {
config.ConnectDatabase()
// 2. AutoMigrate (Membuat tabel jika belum ada)
- // Kita masukkan semua model di sini agar relasi terbentuk rapi
config.DB.AutoMigrate(&models.User{}, &models.Room{}, &models.Booking{})
r := gin.Default()
@@ -34,8 +33,10 @@ func main() {
auth.POST("/login", controllers.Login)
}
+ r.POST("/api/verify-code", controllers.VerifyRedeemCode)
+
protected := r.Group("/api")
- protected.Use(middleware.AuthMiddleware())
+ protected.Use(middleware.AuthMiddleware())
{
protected.GET("/profile", func(c *gin.Context) {
userID, _ := c.Get("user_id")
@@ -48,9 +49,9 @@ func main() {
protected.POST("/rooms", controllers.CreateRoom)
// Bookings
- protected.POST("/bookings", controllers.CreateBooking)
- protected.GET("/bookings", controllers.GetUserBookings)
- protected.PATCH("/bookings/:id", controllers.UpdateBookingStatus)
+ protected.POST("/bookings", controllers.CreateBooking)
+ protected.GET("/bookings", controllers.GetUserBookings)
+ protected.PATCH("/bookings/:id", controllers.UpdateBookingStatus)
}
r.Run(":8080")
diff --git a/backend/controllers/bookingcontroller.go b/backend/controllers/bookingcontroller.go
index 5490711..ae5494c 100644
--- a/backend/controllers/bookingcontroller.go
+++ b/backend/controllers/bookingcontroller.go
@@ -4,7 +4,7 @@ import (
"fmt"
"net/http"
"s-class-backend/config"
- "s-class-backend/helpers" // Import helper pembuat kode acak
+ "s-class-backend/helpers"
"s-class-backend/models"
"time"
@@ -26,7 +26,7 @@ func CreateBooking(c *gin.Context) {
return
}
- // --- Handle UUID ---
+ //Handle UUID
userIDInterface, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "User ID tidak ditemukan"})
@@ -51,7 +51,7 @@ func CreateBooking(c *gin.Context) {
return
}
- // CEK BENTROK (Overlap Check)
+ // (Overlap Check)
var count int64
config.DB.Model(&models.Booking{}).Where("room_id = ? AND status != 'Cancelled' AND ((start_time < ? AND end_time > ?) OR (start_time < ? AND end_time > ?) OR (start_time >= ? AND end_time <= ?))",
input.RoomID, input.EndTime, input.StartTime, input.EndTime, input.StartTime, input.StartTime, input.EndTime).Count(&count)
@@ -105,10 +105,10 @@ func GetUserBookings(c *gin.Context) {
}
type UpdateStatusInput struct {
- Status string `json:"status" binding:"required"`
+ Status string `json:"status" binding:"required"`
}
-// --- FUNGSI UPDATE STATUS (ADMIN) ---
+//UPDATE STATUS (ADMIN)
func UpdateBookingStatus(c *gin.Context) {
bookingID := c.Param("id")
@@ -126,14 +126,12 @@ func UpdateBookingStatus(c *gin.Context) {
booking.Status = input.Status
- // 👇 LOGIKA PEMBUATAN REDEEM CODE 👇
+ //REDEEM CODE
if input.Status == "Approved" {
- // Buat kode dari helper jika sebelumnya belum punya kode
if booking.RedeemCode == "" {
booking.RedeemCode = helpers.GenerateRedeemCode()
}
} else if input.Status == "Rejected" || input.Status == "Cancelled" {
- // Kosongkan kode jika peminjaman ditolak/dibatalkan
booking.RedeemCode = ""
}
@@ -143,4 +141,36 @@ func UpdateBookingStatus(c *gin.Context) {
"message": "Status booking berhasil diperbarui!",
"data": booking,
})
-}
\ No newline at end of file
+}
+
+//INPUT UNTUK ESP3
+type RedeemInput struct {
+ RedeemCode string `json:"redeem_code" binding:"required"`
+}
+
+//VERIFIKASI KODE DARI ESP32
+func VerifyRedeemCode(c *gin.Context) {
+ var input RedeemInput
+
+ if err := c.ShouldBindJSON(&input); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Format data salah"})
+ return
+ }
+
+ var booking models.Booking
+
+ if err := config.DB.Preload("Room").First(&booking, "redeem_code = ? AND status = 'Approved'", input.RedeemCode).Error; err != nil {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "Kode tidak valid, belum disetujui, atau sudah digunakan!"})
+ return
+ }
+
+ booking.Status = "Completed"
+ booking.RedeemCode = ""
+ config.DB.Save(&booking)
+
+ c.JSON(http.StatusOK, gin.H{
+ "message": "Kode valid!",
+ "room": booking.Room.Name,
+ "status": "success",
+ })
+}
diff --git a/frontend/app/history/page.tsx b/frontend/app/history/page.tsx
index c34d16d..edcf68e 100644
--- a/frontend/app/history/page.tsx
+++ b/frontend/app/history/page.tsx
@@ -3,7 +3,7 @@
import { useEffect, useState } from "react";
import axios from "axios";
import { useRouter } from "next/navigation";
-import { ArrowLeft, Calendar, Clock, MapPin, AlertCircle, CheckCircle, XCircle } from "lucide-react";
+import { ArrowLeft, Calendar, Clock, MapPin, AlertCircle, CheckCircle, XCircle, Key } from "lucide-react";
interface Booking {
booking_id: string;
@@ -15,6 +15,7 @@ interface Booking {
end_time: string;
purpose: string;
status: string;
+ redeem_code: string;
created_at: string;
}
@@ -45,7 +46,6 @@ export default function HistoryPage() {
}
};
- // Fungsi untuk memformat tanggal agar enak dibaca (Contoh: 10 Feb 2026, 08:00)
const formatDate = (isoString: string) => {
const date = new Date(isoString);
return date.toLocaleDateString("id-ID", {
@@ -54,23 +54,21 @@ export default function HistoryPage() {
});
};
- // Fungsi menentukan warna status
const getStatusBadge = (status: string) => {
switch (status) {
case "Approved":
return