This commit is contained in:
HANIF FEBRIANSYAH 2025-03-12 20:34:04 +07:00
parent 1472d41f2a
commit 8f0b98e62f
14 changed files with 348 additions and 31 deletions

View File

@ -0,0 +1,62 @@
<?php
namespace App\Http\Controllers;
use App\Models\Kunjungan;
use App\Models\DataKursus;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class KunjunganController extends Controller
{
// Fungsi untuk mencatat kunjungan
public function visitCourse(Request $request, $kursus_id)
{
// Ambil IP pengguna secara otomatis dari request
$ip_user = $request->ip();
// Cari kursus berdasarkan ID
$kursus = DataKursus::find($kursus_id);
if (!$kursus) {
return response()->json([
'status' => 'error',
'message' => 'Course not found',
], 404);
}
// Cek apakah IP pengguna sudah mengunjungi kursus ini pada hari ini
$today = now()->format('Y-m-d'); // Ambil tanggal hari ini dalam format Y-m-d
$existingVisit = Kunjungan::where('ip_user', $ip_user)
->where('kursus_id', $kursus_id)
->whereDate('created_at', $today) // Cek berdasarkan tanggal
->first();
if ($existingVisit) {
return response()->json([
'status' => 'info',
'message' => 'You have already visited this course today.',
], 200);
}
// Jika belum ada kunjungan, buat data kunjungan baru
try {
$kunjungan = Kunjungan::create([
'ip_user' => $ip_user,
'kursus_id' => $kursus_id,
]);
return response()->json([
'status' => 'success',
'message' => 'Successfully recorded visit.',
'data' => $kunjungan
], 201);
} catch (\Exception $e) {
return response()->json([
'status' => 'error',
'message' => 'Failed to record visit: ' . $e->getMessage(),
], 500);
}
}
}

View File

@ -2,8 +2,10 @@
namespace App\Http\Controllers;
use App\Models\Kunjungan;
use App\Models\DataKursus;
use App\Models\DataUlasan;
use Illuminate\Support\Str;
use App\Models\DataKategori;
use Illuminate\Http\Request;
use Dflydev\DotAccessData\Data;
@ -15,11 +17,12 @@ class PengunjungController extends Controller
// PENGUNJUNG
public function home()
{
// Ambil 3 data yang populer dari tabel DataKursus
$landingpage = DataKursus::where('popular', 'Popular')
->limit(3)
$landingpage = DataKursus::withCount('kunjungan') // Menghitung jumlah kunjungan tanpa filter waktu
->orderByDesc('kunjungan_count') // Mengurutkan berdasarkan jumlah kunjungan terbanyak
->limit(3) // Mengambil 4 kursus terpopuler
->get();
// Potong deskripsi agar hanya terdiri dari 22 kata
foreach ($landingpage as $item) {
$item->deskripsi = \Illuminate\Support\Str::words($item->deskripsi, 22, '...');
@ -48,7 +51,8 @@ public function kursus(Request $request)
}
// Ambil data kursus secara acak dengan kategori
$data_kursus = $query->with('kategoris')->inRandomOrder()->paginate(10);
$data_kursus = $query->with('kategoris')->paginate(12);
// Ambil daftar kategori untuk dropdown
$kategori = DataKategori::all();
@ -78,6 +82,30 @@ public function maps()
public function detail(string $id)
{
// Cek apakah sudah ada ID perangkat dalam cookie
$deviceId = request()->cookie('device_id');
if (!$deviceId) {
// Jika tidak ada, buat ID baru dan simpan dalam cookie
$deviceId = Str::uuid()->toString();
cookie()->queue('device_id', $deviceId, 60 * 24 * 365); // Simpan selama setahun
}
// Cek apakah deviceId sudah ada pada kursus_id yang sama pada hari yang sama
$existingVisit = Kunjungan::where('device_id', $deviceId)
->where('kursus_id', $id) // Cek apakah sudah ada kunjungan untuk kursus_id yang sama
->whereDate('created_at', today()) // Cek apakah ada kunjungan pada hari yang sama
->exists();
if (!$existingVisit) {
// Simpan kunjungan jika belum ada kunjungan dengan deviceId dan kursus_id yang sama pada hari ini
Kunjungan::create([
'device_id' => $deviceId,
'kursus_id' => $id,
]);
}
// Ambil data kursus beserta ulasan dan kategorinya
$data = DataKursus::with('kategoris', 'ulasan')->find($id);
@ -87,11 +115,11 @@ public function detail(string $id)
// Ambil data ulasan terkait, urutkan berdasarkan created_at terbaru
$imageNames = json_decode($data->img_konten, true);
$ulasan = $data->ulasan()->orderBy('created_at', 'desc')->paginate(3); // Membatasi hanya 3 ulasan per halaman, urutan terbaru
$ulasan = $data->ulasan()->orderBy('created_at', 'desc')->paginate(3);
// Hitung rata-rata rating
$averageRating = $ulasan->avg('rating'); // Mengambil rata-rata rating
$totalRatings = $ulasan->count(); // Menghitung total jumlah ulasan
$averageRating = $ulasan->avg('rating');
$totalRatings = $ulasan->count();
// Kirim data ke view
return view('user.detailKursus', compact('data', 'imageNames', 'ulasan', 'averageRating', 'totalRatings'));
@ -102,6 +130,9 @@ public function detail(string $id)
public function rute(string $id)
{
// Ambil kursus berdasarkan ID

View File

@ -2,6 +2,7 @@
namespace App\Models;
use App\Models\Kunjungan;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -34,6 +35,12 @@ public function kategoris(): BelongsTo
{
return $this->belongsTo(DataKategori::class, 'kategori_id', 'id'); // Menentukan foreign key dan local key
}
public function kunjungan(): HasMany
{
return $this->hasMany(Kunjungan::class, 'kursus_id', 'id'); // Menentukan foreign key dan local key
}
public function ulasan(): HasMany
{
return $this->hasMany(DataUlasan::class, 'kursus_id', 'id'); // Menentukan foreign key dan local key

21
app/Models/Kunjungan.php Normal file
View File

@ -0,0 +1,21 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Kunjungan extends Model
{
use HasFactory;
protected $table = 'kunjungan';
protected $fillable = ['device_id', 'kursus_id'];
// Relasi ke Kursus
public function kursuses(): BelongsTo
{
return $this->belongsTo(DataKursus::class, 'kursus_id', 'id');
}
}

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('kunjungan', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('device_id'); // Bisa menyimpan IPv4 (15 char) atau IPv6 (45 char)
$table->timestamps();
// Foreign key constraint
$table->foreignId('kursus_id')
->nullable()
->constrained('data_kursus')
->onDelete('cascade') // Hapus kunjungan jika kursus dihapus
->onUpdate('cascade');
});
}
public function down(): void
{
Schema::dropIfExists('kunjungan');
}
};

156
public/css/font.css Normal file
View File

@ -0,0 +1,156 @@
.pacifico-regular {
font-family: "Pacifico", cursive;
font-weight: 400;
font-style: normal;
}
.archivo-black-regular {
font-family: "Archivo Black", sans-serif;
font-weight: 400;
font-style: normal;
}
.archivo-uniquifier {
font-family: "Archivo", sans-serif;
font-optical-sizing: auto;
font-weight: weight;
font-style: normal;
font-variation-settings: "wdth" 100;
}
.rubik-mono-one-regular {
font-family: "Rubik Mono One", monospace;
font-weight: 400;
font-style: normal;
}
.playfair-display-uniquifier {
font-family: "Playfair Display", serif;
font-optical-sizing: auto;
font-weight: weight;
font-style: normal;
}
.dm-sans {
font-family: "DM Sans", serif;
font-optical-sizing: auto;
font-weight: weight;
font-style: normal;
}
.castoro-regular {
font-family: "Castoro", serif;
font-weight: 400;
font-style: normal;
}
.castoro-regular-italic {
font-family: "Castoro", serif;
font-weight: 400;
font-style: italic;
}
.poppins-thin {
font-family: "Poppins", serif;
font-weight: 100;
font-style: normal;
}
.poppins-extralight {
font-family: "Poppins", serif;
font-weight: 200;
font-style: normal;
}
.poppins-light {
font-family: "Poppins", serif;
font-weight: 300;
font-style: normal;
}
.poppins-regular {
font-family: "Poppins", serif;
font-weight: 400;
font-style: normal;
}
.poppins-medium {
font-family: "Poppins", serif;
font-weight: 500;
font-style: normal;
}
.poppins-semibold {
font-family: "Poppins", serif;
font-weight: 600;
font-style: normal;
}
.poppins-bold {
font-family: "Poppins", serif;
font-weight: 700;
font-style: normal;
}
.poppins-extrabold {
font-family: "Poppins", serif;
font-weight: 800;
font-style: normal;
}
.poppins-black {
font-family: "Poppins", serif;
font-weight: 900;
font-style: normal;
}
.poppins-thin-italic {
font-family: "Poppins", serif;
font-weight: 100;
font-style: italic;
}
.poppins-extralight-italic {
font-family: "Poppins", serif;
font-weight: 200;
font-style: italic;
}
.poppins-light-italic {
font-family: "Poppins", serif;
font-weight: 300;
font-style: italic;
}
.poppins-regular-italic {
font-family: "Poppins", serif;
font-weight: 400;
font-style: italic;
}
.poppins-medium-italic {
font-family: "Poppins", serif;
font-weight: 500;
font-style: italic;
}
.poppins-semibold-italic {
font-family: "Poppins", serif;
font-weight: 600;
font-style: italic;
}
.poppins-bold-italic {
font-family: "Poppins", serif;
font-weight: 700;
font-style: italic;
}
.poppins-extrabold-italic {
font-family: "Poppins", serif;
font-weight: 800;
font-style: italic;
}
.poppins-black-italic {
font-family: "Poppins", serif;
font-weight: 900;
font-style: italic;
}

View File

@ -23,7 +23,7 @@ class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:
required />
</div>
<div>
<div class="hidden">
<label for="popular" class="block mb-2 text-sm font-medium text-gray-900">Pilih
Popular</label>
<select id="popular" name="popular"

View File

@ -27,7 +27,7 @@
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
placeholder="Kampung Inggris LC - Language Center" required />
</div>
<div>
<div class="hidden">
<label for="countries"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Pilih
Popular</label>

View File

@ -2,7 +2,7 @@
<html lang="en">
<head>
@vite(['resources/css/app.css', 'resources/js/app.js'])
@vite(['resources/css/app.css', 'resources/js/app.js', 'public/css/font.css'])
@include('components.navbar')
@include('partials.font')

View File

@ -4,7 +4,7 @@
<div class="bg-[#4F7F81]">
<nav class="border-gray-200 container bg-[#4F7F81] ">
<div class="max-w-screen-2xl flex flex-wrap items-center justify-between mx-auto p-4">
<a href="#" class="flex items-center">
<a href="/" class="flex items-center">
<img src="{{ asset('img/Rectangle 65.png') }}" class="h-20 object-cover w-20" alt="Flowbite Logo" />
<span
class="self-center text-4xl text-white font-semibold whitespace-nowrap pt-4 aclonica-regular">LearnMap</span>

View File

@ -69,7 +69,7 @@ class="w-full h-auto object-contain" alt="Image {{ $index + 1 }}">
<p>Deskripsi</p>
</div>
<div>
<p class="poppins-medium text-black text-lg pb-2" id="deskripsi-text">
<p class="poppins-regular text-black text-lg pb-2" id="deskripsi-text">
{{ Str::limit($data->deskripsi, 500, '...') }}
</p>
@if (strlen($data->deskripsi) > 500)
@ -81,15 +81,15 @@ class="w-full h-auto object-contain" alt="Image {{ $index + 1 }}">
<div class="grid grid-cols-1 gap-4 py-10">
{{-- Bagian Paket --}}
<div class="w-auto xl:max-w-max space-y-2">
<div class="w-auto xl:max-w-max space-y-4">
<p class="poppins-semibold font-semibold text-2xl text-black underline">
Paket
</p>
<p class="poppins-medium text-lg text-black" id="paket-text">
<p class="pl-4 poppins-regular text-lg text-black" id="paket-text">
{!! htmlspecialchars_decode(Str::limit(strip_tags($data->paket, '<br><p><strong><em>'), 250, '...')) !!}
</p>
@if (strlen(strip_tags($data->paket)) > 250)
<button id="toggle-paket" class="text-blue-500 hover:underline poppins-medium text-sm mt-2">
@if (strlen(strip_tags($data->paket)) > 200)
<button id="toggle-paket" class="pl-4 text-blue-500 hover:underline poppins-medium text-sm mt-2">
Lihat Selengkapnya
</button>
@endif
@ -97,30 +97,30 @@ class="w-full h-auto object-contain" alt="Image {{ $index + 1 }}">
{{-- Bagian Metode Pembelajaran --}}
<div class="w-auto xl:max-w-max space-y-2">
<div class="w-auto xl:max-w-max space-y-4">
<p class="poppins-semibold font-semibold text-2xl text-black underline">
Metode Pembelajaran
</p>
<p class="poppins-medium text-lg text-black" id="metode-text">
<p class="pl-4 poppins-regular text-lg text-black" id="metode-text">
{!! htmlspecialchars_decode(Str::limit(strip_tags($data->metode, '<br><p><strong><em>'), 250, '...')) !!}
</p>
@if (strlen(strip_tags($data->metode)) > 250)
<button id="toggle-metode" class="text-blue-500 hover:underline poppins-medium text-sm mt-2">
<button id="toggle-metode" class="pl-4 text-blue-500 hover:underline poppins-medium text-sm mt-2">
Lihat Selengkapnya
</button>
@endif
</div>
{{-- Bagian Fasilitas --}}
<div class="w-auto xl:max-w-max space-y-2">
<div class="w-auto xl:max-w-max space-y-4">
<p class="poppins-semibold font-semibold text-2xl text-black underline">
Fasilitas
</p>
<p class="poppins-medium text-lg text-black" id="fasilitas-text">
<p class="pl-4 poppins-regular text-lg text-black" id="fasilitas-text">
{!! htmlspecialchars_decode(Str::limit(strip_tags($data->fasilitas, '<br><p><strong><em>'), 250, '...')) !!}
</p>
@if (strlen(strip_tags($data->fasilitas)) > 250)
<button id="toggle-fasilitas" class="text-blue-500 hover:underline poppins-medium text-sm mt-2">
<button id="toggle-fasilitas" class="pl-4 text-blue-500 hover:underline poppins-medium text-sm mt-2">
Lihat Selengkapnya
</button>
@endif
@ -128,15 +128,15 @@ class="w-full h-auto object-contain" alt="Image {{ $index + 1 }}">
{{-- Bagian Lokasi --}}
<div class="w-auto xl:max-w-max space-y-2">
<div class="w-auto xl:max-w-max space-y-4">
<p class="poppins-semibold font-semibold text-2xl text-black underline">
Lokasi
</p>
<p class="poppins-medium text-lg text-black" id="lokasi-text">
<p class="pl-4 poppins-regular text-lg text-black" id="lokasi-text">
{!! htmlspecialchars_decode(Str::limit(strip_tags($data->lokasi, '<br><p><strong><em>'), 250, '...')) !!}
</p>
@if (strlen(strip_tags($data->lokasi)) > 250)
<button id="toggle-lokasi" class="text-blue-500 hover:underline poppins-medium text-sm mt-2">
<button id="toggle-lokasi" class="pl-4 text-blue-500 hover:underline poppins-medium text-sm mt-2">
Lihat Selengkapnya
</button>
@endif

View File

@ -32,10 +32,11 @@ class="absolute top-0 end-0 px-4 h-full text-sm font-medium text-white bg-[#4F7F
</form>
</div>
{{ $data_kursus->links() }}
<div class="grid md:grid-cols-1 lg:grid-cols-3 justify-center items-center gap-4 !important">
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 justify-center py-4 items-center gap-4 !important">
@foreach ($data_kursus as $kursus)
<div class="kursus-item h-ful shadow-2xl rounded-lg ">
<div class="kursus-item max-w-xl h-ful mx-auto shadow-2xl rounded-lg ">
<div>
<div>
<img class=" flex justify-center items-center w-full h-64 object-cover"
@ -73,6 +74,10 @@ class="inline-flex items-center px-6 py-2 text-sm font-extrabold text-slate-700
</div>
@endforeach
</div>
{{ $data_kursus->links() }}
</div>
{{-- <script>

View File

@ -28,7 +28,7 @@ class="w-full h-56 sm:h-64 md:h-96 lg:h-[500px] xl:h-[650px] max-w-4xl rounded-l
document.addEventListener('DOMContentLoaded', function() {
// Inisialisasi peta dengan koordinat dan tingkat zoom
const map = L.map('map1').setView([-7.7517397, 112.1780461],
13); // Gunakan 'map1' untuk ID peta yang sesuai
15); // Gunakan 'map1' untuk ID peta yang sesuai
// Tambahkan lapisan ubin dari OpenStreetMap
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {

View File

@ -13,6 +13,7 @@
use App\Http\Controllers\AdminDataKursusController;
use App\Http\Controllers\Auth\NewPasswordController;
use App\Http\Controllers\Auth\PasswordResetLinkController;
use App\Http\Controllers\KunjunganController;
Route::get('/login', [LoginController::class, 'index'])->name('login'); // Halaman login
Route::post('/login', [LoginController::class, 'authenticate'])->name('login.process'); // Proses login
@ -64,6 +65,8 @@
Route::get('/kursus/{id}/rute', [PengunjungController::class, 'rute'])->name('user.rute');
Route::get('/kursus/{id}/detail', [PengunjungController::class, 'detail'])->name('kursus.detail');
Route::post('/store-ulasan', [PengunjungController::class, 'storeUlasann'])->name('storeUlasan');
Route::post('/kursus/{id}/detail', [KunjunganController::class, 'visitCourse'])->name('kursus.visit');
Route::middleware(['auth'])->group(function () {