• Tidak ada hasil yang ditemukan

Penulisan Laporan Akhir Team 3

N/A
N/A
Void Ni

Academic year: 2025

Membagikan "Penulisan Laporan Akhir Team 3"

Copied!
58
0
0

Teks penuh

(1)

UNIVERSITAS GUNADARMA FAKULTAS TEKNOLOGI INDUSTRI

LAPORAN AKHIR

PROYEK WEBSITE TOKO TAS SABLON EXPRESS Nama Anggota:

1. 50421549 | Geren Haekal Hafizh 2. 50421663 | Irfan Nur Sasmito 3. 50421769 | M Nur Luthfi

4. 51421091 | Muhammad Thariq El Deuzan 5. 51421341 | Rizki Faizal Iriansyah

Kelas : 4IA02

Jurusan : Informatika

Dosen : Dr. Dharmayanti S.T., MMSI

Ditulis Guna Melengkapi Sebagian Syarat Project Pengolahan Proyek Perangkat Lunak

Jakarta

2025

(2)

KATA PENGANTAR

Segala puji dan syukur penulis panjatkan ke hadirat Tuhan Yang Maha Esa atas limpahan rahmat dan karunia-Nya, sehingga tim 3 berhasil menyelesaikan proyek dan laporan akhir ini dengan baik dan lancar. Laporan akhir yang berjudul PROYEK WEBSITE TOKO TAS SABLON EXPRESS disusun sebagai salah satu syarat untuk menyelesaikan tugas proyek dalam pengolahan perangkat lunak.

Laporan ini disajikan menggunakan konsep dan bahasa yang sederhana agar dapat memudahkan pembaca dalam memahami isi laporan.

Kami juga menyampaikan rasa terima kasih yang sebesar-besarnya kepada semua pihak yang telah memberikan dukungan, ide, dan kontribusi dalam penyusunan laporan ini. Harapan kami, laporan ini dapat memberikan manfaat bagi pembaca dan menjadi referensi yang berguna. Kami sangat mengapresiasi kritik, saran, serta masukan dari berbagai pihak demi perbaikan di masa depan.

Jakarta, 10 Januari 2025

Tim 3

(3)

DAFTAR ISI

KATA PENGANTAR ... 2

DAFTAR ISI ... 3

DAFTAR GAMBAR ... 5

DAFTAR TABEL ... 6

DAFTAR LAMPIRAN ... 7

BAB 1 PENDAHULUAN ... 8

1.1 Latar Belakang ... 8

1.2 Tujuan ... 8

1.3 Rumusan Masalah ... 9

1.4 Batasan Masalah ... 9

BAB 2 PEMBAHASAN ... 10

2.1 Gambaran Umum Aplikasi ... 10

2.2 Perancangan Aplikasi ... 10

2.2.1 Perancangan Struktur navigasi... 10

2.2.1.1 Struktur Navigasi Admin ... 10

2.2.1.2 Struktur Navigasi Pengguna ... 11

2.2.2 Perancangan Database ... 12

2.2.2.1 Struktur Database Admin ... 12

2.2.2.2 Struktur Database Testimoni Pengguna ... 13

2.2.2.3 Struktur Database Items ... 14

2.2.2.4 Struktur Database Categories ... 14

2.2.3 Desain tampilan aplikasi ... 15

2.2.3.1 Desain tampilan Landing page ... 15

2.2.3.2 Desain tampilan categories ... 17

2.2.3.3 Desain tampilan About ... 18

2.2.3.4 Desain tampilan Header ... 19

2.2.3.5 Desain tampilan Footer ... 20

2.2.3.6 Desain tampilan Login Admin ... 21

2.2.4 Uji Coba ... 23

(4)

BAB 3 PENUTUP ... 25

3.1 Kesimpulan ... 25

3.2 Saran ... 25

LAMPIRAN ... 26

(5)

DAFTAR GAMBAR

Gambar 2. 1 Struktur Navigasi Admin ... 11

Gambar 2. 2 Struktur Navigasi Pengguna ... 12

Gambar 2. 3 Struktur Database Admin ... 13

Gambar 2. 4 Struktur Database Testimoni Pengguna ... 13

Gambar 2. 5 Struktur Database Items ... 14

Gambar 2. 6 Struktur Database Categories ... 14

Gambar 2. 7 Desain Tampilan Aplikasi ... 15

Gambar 2. 8 Desain tampilan categories ... 17

Gambar 2. 9 Desain tampilan About ... 18

Gambar 2. 10 Desain tampilan Header ... 19

Gambar 2. 11 Desain tampilan Footer ... 20

Gambar 2. 12 Desain tampilan Login Admin ... 21

Gambar 2. 13 Desain tampilan Dashboard Admin ... 21

(6)

DAFTAR TABEL

Tabel 2. 1 Uji Coba ... 24

(7)

DAFTAR LAMPIRAN

Lampiran 1 code App.Jsx ... 27

Lampiran 2 code Main.Jsx ... 27

Lampiran 3 code HomePage.Jsx ... 36

Lampiran 4 code Categories.Jsx ... 38

Lampiran 5 code Catalogue.Jsx ... 39

Lampiran 6 code About.Jsx ... 43

Lampiran 7 code Dashboard.Jsx ... 50

Lampiran 8 code Login.Jsx ... 52

Lampiran 9 code Server.js ... 58

(8)

BAB 1 PENDAHULUAN

1.1 Latar Belakang

Di era globalisasi saat ini, perkembangan teknologi semakin pesat seiring waktu. Kemajuan teknologi informasi memiliki peran yang sangat penting dalam berbagai aspek kehidupan manusia. Beragam aktivitas yang sebelumnya dilakukan secara langsung kini dapat dijalankan hanya dengan memanfaatkan teknologi yang ada di genggaman kita. Salah satu sektor yang sangat dipengaruhi oleh perkembangan teknologi ini adalah sektor bisnis.

Toko Tas sablon Express Layanan produksi berbagai jenis tas custom yang dapat digunakan untuk berbagai keperluan, seperti seminar, souvenir, ulang tahun, wedding, produksi promosi perusahaan, dan lainnya. Tas-tas ini dibuat dari berbagai jenis bahan berkualitas, dengan berbagai ukuran, dan bisa dipesan sesuai kebutuhan pelanggan, termasuk untuk keperluan khusus seperti tas sekolah, tas bidan, tas wedding, dan sebagainya.

1.2 Tujuan

Tujuan yang hendak dicapai dalam proyek ini adalah :

1. Mempermudah Toko Tas Sablon Express dalam memperluas

jangkauan pemasaran untuk menjangkau lebih banyak wilayah pembelian.

(9)

2. Mendukung Toko Tas Sablon Express dalam menghemat biaya promosi serta meningkatkan efisiensi waktu dalam memperkenalkan produk tas sablon berbahan beragam.

3. Membantu Toko Tas Sablon Express dalam mempromosikan berbagai jenis tas sablon dengan pilihan bahan yang beragam kepada calon pelanggan.

4. Memudahkan calon pelanggan yang berminat terhadap produk Toko Tas Sablon Express untuk mendapatkan informasi terkait pemesanan dan kontak usaha.

1.3 Rumusan Masalah

Dalam upaya meningkatkan jangkauan pemasaran dan efisiensi operasional, Toko Tas Sablon Express menghadapi beberapa tantangan, yaitu:

1. Kesulitan dalam menjangkau calon pelanggan di wilayah yang lebih luas tanpa dukungan teknologi pemasaran yang memadai.

2. Pengeluaran biaya promosi yang tinggi dan memakan waktu dalam memperkenalkan produk kepada pelanggan.

3. Keterbatasan media promosi untuk memberikan informasi lengkap tentang produk, bahan, harga, dan cara pemesanan kepada calon pelanggan.

4. Kurangnya platform yang memungkinkan calon pelanggan untuk dengan mudah mendapatkan informasi terkait kontak usaha dan melakukan pemesanan secara langsung.

1.4 Batasan Masalah

Agar proyek ini lebih terarah, terdapat beberapa batasan masalah yang ditetapkan, yaitu:

1. Proyek ini hanya mencakup pembuatan website berbasis web untuk mempromosikan produk Toko Tas Sablon Express.

2. Website yang dikembangkan dirancang untuk operasional offline, tanpa fitur

e-commerce atau transaksi online.

(10)

3. Fungsi website dibatasi pada penyajian katalog produk, informasi detail produk, dan kontak usaha.

4. Perancangan antarmuka pengguna difokuskan pada kemudahan navigasi untuk admin dan pengguna.

5. Pengembangan hanya mencakup pengelolaan data produk, testimoni, dan informasi pelanggan tanpa integrasi dengan sistem eksternal lainnya.

BAB 2 PEMBAHASAN

2.1 Gambaran Umum Aplikasi

Aplikasi ini dirancang sebagai platform berbasis web untuk mendukung operasional Toko Tas Sablon Express. Dengan tampilan antarmuka yang user- friendly, aplikasi ini memungkinkan pengguna untuk melihat katalog produk, memesan tas custom, serta mendapatkan informasi lengkap mengenai bahan, harga, dan waktu produksi.

2.2 Perancangan Aplikasi

Pada tahap ini dijelaskan mengenai proses perancangan website toko sablon express dalam mencapai tujuan yang diharapkan. Terdapat 3 tahapan perancangan yang dimulai dari perancangan struktur navigasi, perancangan database dan desain tampilan aplikasi.

2.2.1 Perancangan Struktur navigasi

Pada tahap ini menjelaskan perancangan struktur navigasi dalam membuat aplikasi website toko sablon express. Pada tahap ini struktur navigasi dibedakan menjadi struktur navigasi admin dan pengguna

2.2.1.1 Struktur Navigasi Admin

Struktur navigasi admin yang ditujukan untuk mempermudah admin dalam

mengolah data pada website. Terdapat 3 menu dalam struktur navigasi admin

yaitu dashboard, data produk dan data testimoni.

(11)

Gambar 2. 1 Struktur Navigasi Admin

Gambar tersebut menjelaskan bahwa ketiga struktur navigasi memiliki fungsi yang berbeda. Pada bagian data pengguna, admin dapat mengelola akun admin dengan melakukan penambahan, perubahan, dan penghapusan data admin.

Navigasi data produk digunakan untuk mengelola produk yang ada di website, termasuk penambahan, pengubahan, dan penghapusan produk. Sementara itu, navigasi data pengguna digunakan untuk mengelola testimoni yang diberikan oleh pengguna di website, dengan opsi untuk menambah, mengubah, dan menghapus testimoni.

2.2.1.2 Struktur Navigasi Pengguna

Struktur navigasi pengguna yang ditujukan untuk mempermudah pengguna

dalam menggunakan website. Terdapat 4 menu dalam struktur navigasi admin

yaitu home, detail produk, testimoni dan about us.

(12)

Gambar 2. 2 Struktur Navigasi Pengguna

Pada gambar menjelaskan mengenai struktur navigasi pengguna yaitu pada menu home terdapat penjelasan mengenai toko sablon express. Menu detail product terdapat penjelasan produk berupa harga,ukuran dan bahan produk toko sablom express. Pada Menu testimoni terdapat testimoni dari beberapa konsumen mengenai pelayanan dan produk dari toko sablon express. Menu about terdapat informasi , alamat dan kontak toko sablon express.

2.2.2 Perancangan Database

pada tahap ini menjelaskan proses perancangan backend untuk pembuatan website toko sablon express. pada tahap ini dibagi menjadi 3 bagian yaitu struktur database admin, testimoni pengguna dan categories.

2.2.2.1 Struktur Database Admin

Struktur Database Admin digunakan untuk menyimpan data akun admin pada

website, pada struktur ini terdapat empat field yaitu id,username, password, dan

created_at

(13)

Gambar 2. 3 Struktur Database Admin

pada gambar menjelaskan mengenai struktur database admin yang memiliki empat field yaitu id dengan type int, panjang 11 karakter dan auto increment, username dengan type varchar dan panjang 25 karakter, password dengan type varchar dan panjang 10 karakter dan created_At

2.2.2.2 Struktur Database Testimoni Pengguna

Struktur Database Testimoni Pengguna digunakan untuk menyimpan data testimoni yang diberikan pengguna mengenai toko sablon express, pada struktur ini terdapat dua field yaitu isi_testimoni dan nama_pengguna.

Gambar 2. 4 Struktur Database Testimoni Pengguna

pada gambar menjelaskan mengenai struktur database testimoni pengguna yang

memiliki dua field yaitu isi_testimoni dengan type text dan nama_pengguna

dengan type varchar dan panjang 25 karakter.

(14)

2.2.2.3 Struktur Database Items

Struktur Database Items digunakan untuk menyimpan data produk di website, pada struktur ini terdapat tiga field yaitu id, name dan image.

Gambar 2. 5 Struktur Database Items

pada gambar menjelaskan mengenai struktur database items yang memiliki tiga field yaitu id dengan type int, panjang 11 karakter dan auto increment, name dengan type varchar dan panjang 25 karakter dan image dengan type vaarchar dan panjang 255 karakter.

2.2.2.4 Struktur Database Categories

Struktur Database Categories digunakan untuk menyimpan data untuk akun admin, pada struktur ini terdapat tiga field yaitu id, name dan image.

Gambar 2. 6 Struktur Database Categories

pada gambar menjelaskan mengenai struktur database categories yang memiliki

empat field yaitu id dengan type int, panjang 11 karakter dan auto increment, title

(15)

dengan type varchar dan panjang 255 karakter, description dengan type text dan image dengan type varchar dan panjang 255 karakter.

2.2.3 Desain tampilan aplikasi

Perancangan tampilan aplikasi adalah tahap yang menjelaskan proses-proses dalam merancang tampilan aplikasi. Pada tahap ini, tampilan dibuat dengan desain yang sederhana agar pengguna dapat mengoperasikan aplikasi dengan mudah dan sesuai dengan keinginan klien.

2.2.3.1 Desain tampilan Landing page

Gambar 2. 7 Desain Landing page

(16)

Halaman ini dibuat untuk memudahkan pengguna memahami produk yang tersedia, dengan menonjolkan personalisasi tas tote bag yang unik dan berkualitas.

Pengguna dapat menjelajahi pilihan kategori lainnya, seperti tote bag custom dan

pouch custom, yang dilengkapi dengan deskripsi singkat untuk mempermudah

dalam menentukan pilihan. Bagian keunggulan produk menampilkan poin-poin

penting, seperti proses cepat, harga terjangkau, dan kualitas yang terjamin,

disajikan secara visual dan informatif. Selain itu, testimoni pelanggan juga

disertakan sebagai bukti kepercayaan dan pengalaman positif dari pengguna

sebelumnya. Desain halaman ini mengutamakan kesederhanaan dan efektivitas,

dengan navigasi yang tertata rapi untuk kenyamanan pengguna.

(17)

2.2.3.2 Desain tampilan categories

Gambar 2. 8 Desain tampilan categories

Halaman kategori menyajikan berbagai produk yang tersedia disertai

deskripsi singkat untuk setiap kategori. Pengguna dapat melihat gambar yang

menggambarkan masing-masing kategori, dilengkapi informasi detail seperti

material, dimensi, dan fitur produk. Semua kategori diatur dengan rapi untuk

memudahkan pengguna dalam menjelajahi dan menemukan produk yang

diinginkan.

(18)

2.2.3.3 Desain tampilan About

Gambar 2. 9 Desain tampilan About

(19)

Halaman ini dirancang untuk memudahkan pengguna memahami layanan Goodie Bag Tas Sablon Express, yang menyediakan berbagai jenis tas custom untuk keperluan seminar, souvenir, acara perusahaan, hingga pernikahan. Produk yang ditawarkan hadir dengan bahan berkualitas, desain menarik, dan dapat disesuaikan dengan kebutuhan pengguna. Bagian pilihan bahan memberikan informasi detail mengenai jenis bahan seperti spunbond, baby canvas, drill, mika, dan dacron, lengkap dengan keunggulan masing-masing.

Bagian keunggulan produk menyoroti aspek penting, seperti kualitas bahan yang tahan lama, opsi kustomisasi untuk memenuhi preferensi pelanggan, dan harga yang terjangkau sesuai anggaran. Selain itu, informasi lokasi juga disertakan dengan peta interaktif untuk memudahkan pengguna menemukan alamat kami.

Desain halaman ini mengutamakan kesederhanaan dan kejelasan, dengan elemen informatif yang disusun secara terorganisir untuk kenyamanan pengguna.

2.2.3.4 Desain tampilan Header

Gambar 2. 10 Desain tampilan Header

Bagian navbar diisi dengan paduan warna antara putih dan warna beige digunakan

disetiap halaman sebagai header, yang memiliki isi seperti logo toko, home,

categories dan about.

(20)

2.2.3.5 Desain tampilan Footer

Gambar 2. 11 Desain tampilan Footer

Footer tersebut dirancang untuk memberikan kesan toko yang memiliki layanan pembuatan tas custom, seperti pouch, tote bag, dan goodie bag. Dengan slogan

"Your Promotional Packaging Solution," mereka menawarkan desain kreatif dan

kualitas terbaik untuk memenuhi kebutuhan pelanggan. Terdapat informasi layanan

yang mencakup pembuatan pouch, tote bag, dan goodie bag, serta kontak untuk

informasi lebih lanjut, seperti nomor telepon (+6289534151742), email

([email protected]), dan alamat fisik (Jalan Arthayasa No. 50, Kecamatan Limo,

Depok 16515). Footer ini juga menonjolkan tautan ke Instagram mereka,

memperlihatkan pendekatan visual untuk menarik pelanggan dan menampilkan

portofolio. Desainnya sederhana namun informatif, dengan latar warna coklat yang

memberi kesan hangat dan profesional.

(21)

2.2.3.6 Desain tampilan Login Admin

Gambar 2. 12 Desain tampilan Login Admin

Halaman login ditampilkan memiliki desain sederhana namun estetik.

Tampilan ini terbagi menjadi dua bagian utama: di sebelah kiri, terdapat gambar tas kanvas putih yang diletakkan di atas latar rerumputan kering dan elemen alam seperti ranting dan daun, yang mencerminkan tema ramah lingkungan atau natural.

Di sebelah kanan, terdapat formulir login untuk administrator yang mencakup judul

"Login Admin," pesan selamat datang yang berbunyi "Selamat datang di portal admin, login untuk memulai," serta dua kolom input untuk memasukkan username dan password.

Kolom input memiliki ikon masing-masing untuk membantu identifikasi (username dan password), yang memudahkan pengguna memahami fungsinya.

Tombol "LOGIN" berwarna coklat yang konsisten dengan elemen desain

sebelumnya memberikan sentuhan profesional. Keseluruhan desain memberikan

kesan minimalis dengan fokus pada fungsi serta daya tarik visual, terutama melalui

penggunaan elemen gambar yang relevan dengan konteks bisnis tas custom atau

ramah lingkungan.

(22)

2.2.3.7 Desain tampilan Dashboard admin

Gambar 2. 13 Desain tampilan Dashboard Admin

Halaman ini merupakan dashboard admin untuk mengelola kategori dan item produk pada aplikasi berbasis web. Di bagian atas terdapat pesan sambutan dengan latar biru muda. Tengah halaman menampilkan dua bagian utama: Jumlah Kategori:

3 dan Jumlah Items: 12. Masing-masing berisi daftar data dengan ID unik, dilengkapi tombol Edit dan Hapus untuk mempermudah pengelolaan.

Footer memuat informasi kontak, alamat, serta layanan seperti "Sablon Pouch" dan

"Sablon GoodieBag". Dengan desain yang sederhana dan terstruktur, halaman ini

memprioritaskan kemudahan admin dalam mengelola data, meskipun akan lebih

baik jika dilengkapi fitur pencarian atau elemen visual tambahan untuk

meningkatkan kenyamanan navigasi.

(23)

2.2.4 Uji Coba Aktivitas

Pengujian

Realisasi Yang Diharapkan

Hasil Pengujian Kesimpulan

Dapat menuju halaman “Home”

Sistem akan menuju ke halaman “Home”

sebagai halaman awal

Menampilkan halaman “Home”

Valid

Dapat menuju halaman

“Categories”

Sistem akan menuju ke halaman

“Categories”

Menampilkan halaman

“Categories”

Valid

Dapat menuju halaman “About”

Sistem akan menuju ke halaman “About”

Menampilkan halaman “About”

Valid

Klik tombol

“Pesan Sekarang”

Membuka formulir

pemesanan Toko Tas Sablon Express

Membuka formulir pemesanan

Valid

Klik tombol

“Explore”

Sistem akan menuju ke halaman

“Categories”

Menampilkan halaman

“Categories”

Valid

Input nama dan review pada form review

Saat review dikirim sistem akan

menampilkan nama dan review

Nama dan review tampil di text field review

Valid

(24)

yang telah di input

Klik tombol “See our Instagram”

Membuka halaman

Instagram Toko Tas Sablon Express

Membuka halaman

Instagram Toko Tas Sablon Express

Valid

Klik tombol

“Catalogue”

Sistem akan menuju ke halaman

“Catalogue”

Menampilkan halaman

“Catalogue”

Valid

Klik tombol

“Lihat peta lebih besar” di peta lokasi

Membuka Google Maps lokasi Toko Tas Sablon Express

Membuka lokasi Toko Tas Sablon Express di Google Maps

Valid

Klik icon login admin

Sistem akan menuju ke halaman login admin

Menampilkan halaman login admin

Valid

Memasukan username dan password untuk masuk ke

dashboard admin

Sistem akan menuju ke halaman

dashboard admin jika username dan password benar

Menampilkan halaman

dashboard admin jika username dan password yang dimasukan benar

Valid

Tabel 2. 1 Uji Coba

(25)

BAB 3 PENUTUP

3.1 Kesimpulan

Proyek pengembangan website Toko Tas Sablon Express berhasil memberikan solusi praktis untuk mempromosikan produk tas custom. Fitur-fitur yang dirancang mempermudah pelanggan dalam menjelajahi katalog produk, mendapatkan informasi detail, dan menghubungi toko. Website ini juga membantu meningkatkan efisiensi promosi dan memperluas jangkauan pemasaran. Selain itu, proses pengembangan memanfaatkan navigasi dan desain antarmuka yang sederhana, sehingga website ini mudah digunakan baik oleh admin maupun pengguna.

3.2 Saran

Disarankan untuk menambahkan fitur e-commerce agar transaksi online

lebih mudah, memperkuat keamanan website pada login admin, serta

mengoptimalkan desain agar responsif di berbagai perangkat. Selain itu, visibilitas

website dapat ditingkatkan melalui SEO, dan pengembangan aplikasi mobile juga

dapat dipertimbangkan untuk memperluas aksesibilitas pengguna.

(26)

LAMPIRAN LAMPIRAN

App.Jsx

import { useState } from "react";

import { Routes, Route, useLocation } from "react-router- dom"

import NavbarComponent from "./components/NavbarComponent"

import FooterComponent from "./components/FooterComponent"

import HomePage from "./pages/HomePage"

import Categories from "./pages/Categories"

import About from "./pages/About"

import Login from "./auth/login"

import Dashboard from './pages/Dashboard';

import Catalogue from "./pages/Catalogue";

function App() {

const [isLoggedIn, setIsLoggedIn] = useState(false);

const location = useLocation();

return <div>

{location.pathname !== "/login" && <NavbarComponent isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} />}

<Routes>

<Route path="/" element={<HomePage />} />

<Route path="/homepage" element={<HomePage />} />

<Route path="/categories" element={<Categories />} />

<Route path="/about" element={<About/>} />

<Route path="/login" element={<Login setIsLoggedIn={setIsLoggedIn} />} />

<Route path="/dashboard" element={<Dashboard isLoggedIn={isLoggedIn} />} />

(27)

<Route path="/catalogue" element={<Catalogue />} />

</Routes>

{location.pathname !== "/login" && <FooterComponent/>}

</div>

}

export default App

Lampiran 1 code App.Jsx

Main.Jsx

import { StrictMode } from 'react'

import { createRoot } from 'react-dom/client' import App from './App.jsx'

import 'bootstrap/dist/css/bootstrap.min.css';

import "./dist/css/main.css"

import { BrowserRouter} from "react-router-dom"

createRoot(document.getElementById('root')).render(

<StrictMode>

<BrowserRouter>

<App />

</BrowserRouter>

</StrictMode>, )

Lampiran 2 code Main.Jsx

HomePage.Jsx

import React, { useState, useEffect } from 'react';

import '../dist/css/main.css';

import produkHeader from '../assets/img/produk- jumbotron.jpg';

import totebagImg from '../assets/img/tote-bag.jpg';

import potchImg from '../assets/img/potch.jpg';

import bestImg from '../assets/img/best.jpg';

import fastImg from '../assets/img/fast.png';

import cheapImg from '../assets/img/cheap.png';

import qualityImg from '../assets/img/quality.png';

import tokoImg from '../assets/img/toko.jpg';

import { Container, Row, Col } from 'react-bootstrap';

(28)

import { FaArrowUpRightFromSquare, FaTrash } from "react- icons/fa6";

const HomePage = () => {

const [isHovered, setIsHovered] = useState(false);

const [isVisible, setIsVisible] = useState(false);

const [review, setReview] = useState('');

const [customerName, setCustomerName] = useState('');

const [testimonials, setTestimonials] = useState([]);

useEffect(() => {

const timer = setTimeout(() => setIsVisible(true), 500);

return () => clearTimeout(timer);

}, []);

useEffect(() => {

fetch('http://localhost:3000/testimoni') .then(response => {

if (!response.ok) throw new Error('Network response was not ok');

return response.json();

})

.then(data => setTestimonials(data))

.catch(error => console.error('Error fetching testimonials:', error));

}, []);

const handleReviewChange = (e) =>

setReview(e.target.value);

const handleNameChange = (e) =>

setCustomerName(e.target.value);

const handleReviewSubmit = () => {

const forbiddenWords = ['anjing', 'babi', 'jelek'];

const containsForbiddenWord = forbiddenWords.some(word

=> review.includes(word));

if (containsForbiddenWord) {

alert('Review Anda mengandung kata yang tidak diperbolehkan.');

return;

}

if (!review) {

alert('Masukkan review Anda terlebih dahulu');

return;

}

if (!customerName) {

alert('Masukkan nama Anda terlebih dahulu');

return;

}

(29)

fetch('http://localhost:3000/testimoni', { method: 'POST',

headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nama_pengguna: customerName, isi_testimoni: review }),

})

.then(response => {

if (!response.ok) throw new Error('Network response was not ok');

return response.json();

})

.then(data => {

setTestimonials([...testimonials, data]);

setReview('');

setCustomerName('');

})

.catch(error => console.error('Error submitting review:', error));

};

const handleDeleteTestimonial = (nama_pengguna, isi_testimoni) => {

fetch(`http://localhost:3000/testimoni`, { method: 'DELETE',

headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nama_pengguna, isi_testimoni }),

})

.then(response => { if (!response.ok) {

return response.json().then(err => {

throw new Error(err.message || 'Terjadi kesalahan saat menghapus testimoni.');

});

}

setTestimonials(testimonials.filter(testimonial =>

!(testimonial.nama_pengguna === nama_pengguna &&

testimonial.isi_testimoni === isi_testimoni) ));

})

.catch(error => {

console.error('Error deleting testimonial:', error);

alert(error.message);

});

};

return (

<div className="homepage">

<header className="w-100 min-vh-100 d-flex align- items-center">

(30)

<Container>

<Row style={{ display: 'flex', alignItems:

'flex-start', marginLeft: '0px', position: 'relative', zIndex: 1, marginTop: '10px' }}>

<Col style={{ textAlign: 'left', padding:

'20px', marginLeft: '-150px', marginTop: '-100px' }}>

<h1 style={{ fontWeight: 'bold', fontSize:

'50px' }}>

Personalisasi Gaya Anda dengan Tote Bag Unik dan Eksklusif

</h1>

<p style={{ fontWeight: 'inherit', fontSize:

'17px' }}>

Sablon Custom, Kualitas Premium - Buat Ceritamu Tertulis di Setiap Tas

</p>

<a

href="https://forms.gle/kg9hD6JwoTMpeqoe6" target="_blank"

rel="noopener noreferrer" style={{ textDecoration: 'none' }}>

<button

onMouseEnter={() => setIsHovered(true)}

onMouseLeave={() => setIsHovered(false)}

style={{

backgroundColor: isHovered ? '#7A5A3B' : '#A68B5B',

padding: '10px 12px', width: '133px',

height: '48px', display: 'flex',

justifyContent: 'center', alignItems: 'center', border: 'none',

borderRadius: '8px', color: '#FAFAFA', fontSize: '14px', fontWeight: 'inherit',

transition: 'background-color 0.5s' }}

>

Pesan Sekarang </button>

</a>

</Col>

<Col xs={12} md={6} style={{ display: 'flex', justifyContent: 'flex-end', marginLeft: '170px',

marginTop: '-100px' }}>

<img

src={produkHeader}

alt="Produk"

style={{

(31)

width: '350px', height: '380px', borderRadius: '40px',

boxShadow: '0 4px 20px rgba(0, 0, 0, 0.2)',

}}

/>

</Col>

</Row>

</Container>

<div className={`ellipse ${isVisible ? 'fade-in' : ''}`} style={{ marginLeft: '80px', position: 'absolute', zIndex: 0 }}>

<Row className="greeting" style={{ display:

'flex', alignItems: 'flex-start', marginLeft: '20px', position: 'relative', zIndex: 1, marginTop: '20px' }}>

<Col style={{ textAlign: 'left', padding:

'20px' }}>

<h1 style={{ fontWeight: 'bold', fontSize:

'50px' }}>

Personalisasi Gaya Anda dengan Tote Bag Unik dan Eksklusif

</h1>

<p style={{ fontWeight: 'inherit', fontSize:

'17px' }}>

Sablon Custom, Kualitas Premium - Buat Ceritamu Tertulis di Setiap Tas

</p>

</Col>

</Row>

</div>

</header>

<div className="custom-category w-2 min-vh-20">

<Container>

<Row>

<Col style={{ textAlign: 'left', padding:

'20px', marginLeft: '-150px' }}>

<h2 style={{ fontWeight: 'bold' }}>OTHER CUSTOM CATEGORY</h2>

</Col>

</Row>

<Row>

<Col style={{ display: 'flex', alignItems:

'center', padding: '20px' }} className="category-item">

<div style={{ border: '1px solid #A68B5B', borderRadius: '8px', padding: '10px', flex: 1, display:

'flex', alignItems: 'center' }}>

<div style={{ flex: 1 }}>

<h3 style={{ fontWeight: 'inherit' }}>Custom Tote Bag</h3>

(32)

<p>Ekspresikan diri dengan tote bag custom yang stylish dan fungsional, dibuat khusus untuk Anda.</p>

<button className="button-explore"

onClick={() => window.location.href='/categories#tote- bag'}>

EXPLORE TOTE BAG

<FaArrowUpRightFromSquare />

</button>

</div>

<img src={totebagImg} alt="Custom Tote Bag" style={{ width: '150px', height: '150px',

borderRadius: '8px', marginLeft: '5px', objectFit: 'cover' }} />

</div>

</Col>

<Col style={{ display: 'flex', alignItems:

'center', padding: '20px' }} className="category-item">

<div style={{ border: '1px solid #A68B5B', borderRadius: '8px', padding: '10px', flex: 1, display:

'flex', alignItems: 'center' }}>

<div style={{ flex: 1 }}>

<h3 style={{ fontWeight: 'inherit' }}>Custom Pouch</h3>

<p>Pouch multifungsi yang bisa

disesuaikan dengan selera Anda. Praktis, elegan, dan penuh karakter!</p>

<button className="button-explore"

onClick={() => window.location.href='/categories#pouch'}>

EXPLORE POUCH

<FaArrowUpRightFromSquare />

</button>

</div>

<img src={potchImg} alt="Custom Pouch"

style={{ width: '150px', height: '150px', borderRadius:

'8px', marginLeft: '5px', objectFit: 'cover' }} />

</div>

</Col>

</Row>

</Container>

</div>

<div className="best-choice w-100 min-vh-50 mt-5">

<Container>

<Row>

<Col style={{ textAlign: 'left', padding:

'20px', marginLeft: '-150px' }}>

<h2 style={{ fontWeight: 'bold' }}>WHAT MAKES US THE BEST CHOICE</h2>

</Col>

</Row>

<Row>

(33)

<Col xs={12} md={6} style={{ display: 'flex', alignItems: 'center', padding: '20px' }}>

<img

src={bestImg}

alt="Best Choice"

style={{

width: '527px', height: '449px', borderRadius: '24px', marginRight: '20px', objectFit: 'cover', marginLeft: '-150px',

transition: 'transform 0.3s' }}

onMouseEnter={(e) =>

e.currentTarget.style.transform = 'scale(1.05)'}

onMouseLeave={(e) =>

e.currentTarget.style.transform = 'scale(1)'}

/>

</Col>

<Col xs={12} md={6} style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>

<div style={{ display: 'flex', alignItems:

'center', marginBottom: '20px' }}>

<img src={fastImg} alt="Cepat" style={{

width: '120px', height: '120px', marginRight: '24px' }} />

<div>

<h3 style={{ fontWeight: 'bold' }}>Cepat</h3>

<p>Pesanan Anda diproses dengan cepat dan tepat waktu, tanpa mengurangi kualitas.</p>

</div>

</div>

<div style={{ display: 'flex', alignItems:

'center', marginBottom: '20px' }}>

<img src={cheapImg} alt="Murah" style={{

width: '120px', height: '120px', marginRight: '24px' }} />

<div>

<h3 style={{ fontWeight: 'bold' }}>Murah</h3>

<p>Dapatkan produk custom berkualitas dengan harga yang ramah di kantong.</p>

</div>

</div>

<div style={{ display: 'flex', alignItems:

'center' }}>

<img src={qualityImg} alt="Berkualitas"

style={{ width: '120px', height: '120px', marginRight:

'24px' }} />

<div>

(34)

<h3 style={{ fontWeight: 'bold' }}>Berkualitas</h3>

<p>Menggunakan material terbaik dan sablon awet untuk hasil maksimal yang tahan lama.</p>

</div>

</div>

</Col>

</Row>

</Container>

</div>

<div className="review-section w-100 min-vh-50 mt-5"

style={{ marginLeft: '-150px' }}>

<Container>

<Row>

<Col style={{ textAlign: 'left' }}>

<h1 style={{ fontWeight: 'bold', marginBottom:'50px' }}>Tinggalkan Review Anda</h1>

<div style={{

marginBottom: '10px', padding: '10px',

border: '1px solid #A68B5B', borderRadius: '8px',

backgroundColor: '#fff', maxHeight: '150px',

overflowY: 'auto', scrollbarWidth: 'thin',

scrollbarColor: '#A68B5B transparent' }} className="review-scroll">

{testimonials.map((testimonial) => ( <div

key={`${testimonial.nama_pengguna}-

${testimonial.isi_testimoni}`} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>

<p>

<strong>{testimonial.nama_pengguna}:</strong>

{testimonial.isi_testimoni}

</p>

<FaTrash

onClick={() =>

handleDeleteTestimonial(testimonial.nama_pengguna, testimonial.isi_testimoni)}

style={{ cursor: 'pointer', color: 'red' }}

/>

</div>

))}

</div>

<input

type="text"

value={customerName}

(35)

onChange={handleNameChange}

style={{ width: '100%', padding: '10px', borderRadius: '8px', border: '1px solid #A68B5B',

marginBottom: '10px' }}

placeholder="Masukkan nama Anda..."

/>

<textarea rows="4"

value={review}

onChange={handleReviewChange}

style={{ width: '100%', padding: '10px', borderRadius: '8px', border: '1px solid #A68B5B' }}

placeholder="Tulis review Anda di sini..."

/>

<button

onClick={handleReviewSubmit}

style={{ marginTop: '10px', padding: '10px 20px', backgroundColor: '#A68B5B', color: '#FAFAFA',

border: 'none', borderRadius: '8px' }}

>

Kirim Review </button>

</Col>

</Row>

</Container>

</div>

<div className="sosmed-section" style={{ position:

'relative', backgroundImage: `url(${tokoImg})`,

backgroundSize: 'cover', padding: '120px 0', marginTop:

'50px' }}>

<div style={{

position: 'absolute', top: 0,

left: 0, right: 0, bottom: 0,

backgroundColor: 'rgba(0, 0, 0, 0.5)', zIndex: 1

}} />

<Container style={{ position: 'relative', zIndex:

2 }}>

<Row>

<Col style={{ textAlign: 'center' }}>

<h2 style={{ color: '#fff', fontWeight:

'bold' }}>WHAT MAKES US THE BEST CHOICE</h2>

<p style={{ color: '#fff' }}>Temukan berbagai tote bag dan pouch custom yang siap mendukung gaya dan kebutuhan Anda. Dari desain unik hingga kualitas terbaik, kami hadir untuk Anda!</p>

<a

href="https://www.instagram.com/tas_sablon_express?utm_sou

(36)

rce=ig_web_button_share_sheet&igsh=ZDNlZDc0MzIxNw=="

target="_blank" rel="noopener noreferrer">

<button

onMouseEnter={(e) =>

e.currentTarget.style.backgroundColor = 'gray'}

onMouseLeave={(e) =>

e.currentTarget.style.backgroundColor = 'transparent'}

style={{ backgroundColor: 'transparent', color: '#FAFAFA', padding: '10px 20px', border: '2px solid

#FAFAFA', borderRadius: '8px' }}

>

SEE OUR INSTAGRAM

<FaArrowUpRightFromSquare style={{ marginLeft: '5px' }} />

</button>

</a>

</Col>

</Row>

</Container>

</div>

</div>

);

}

export default HomePage;

Lampiran 3 code HomePage.Jsx

Categories.Jsx

import '../dist/css/main.css';

import { Container, Row, Col, Card, ListGroup, Button } from 'react-bootstrap';

import { useEffect, useState } from 'react';

import { useNavigate } from 'react-router-dom';

const Categories = () => {

const [categories, setCategories] = useState([]);

const navigate = useNavigate();

useEffect(() => {

const fetchCategories = async () => { const response = await

fetch('http://localhost:3000/categories');

const data = await response.json();

setCategories(data);

};

fetchCategories();

}, []);

(37)

const handleCatalogueClick = () => { navigate('/catalogue');

};

const handleShowModal = (category) => { // Implementasi handleShowModal

};

const handleDeleteCategory = (id) => { // Implementasi handleDeleteCategory };

const renderCards = () => {

return categories.map((category) => ( <Col key={category.id} xs={12} style={{

marginBottom: '20px' }}>

<Card style={{ width: '100%', borderRadius:

'24px', border: 'none', padding: '20px' }}>

<Card.Img

variant="top"

src={category.image}

alt={category.title}

style={{ objectFit: 'cover', height: '350px', borderRadius: '24px' }}

/>

<Card.Body style={{ display: 'flex',

flexDirection: 'column', alignItems: 'flex-start' }}>

<Card.Title style={{ fontSize: '24px', fontWeight: 'bold' }}>{category.title}</Card.Title>

<Card.Text style={{ fontSize: '16px', marginBottom: '10px', whiteSpace: 'pre-line' }}>

Deskripsi: {category.description}

</Card.Text>

<Card.Text style={{ fontSize: '16px', marginBottom: '10px' }}>

{category.material}

</Card.Text>

<Card.Text style={{ fontSize: '16px', marginBottom: '10px' }}>

{category.size}

</Card.Text>

<button className="catalogue-button"

onClick={handleCatalogueClick} style={{

width: '244px', height: '50px',

backgroundColor: '#AF8E45', color: '#FFFFFF',

border: 'none',

borderRadius: '20px', cursor: 'pointer' }}>

(38)

Catalogue </button>

</Card.Body>

</Card>

</Col>

));

};

return (

<div className="categories w-100 min-vh-100" style={{

marginTop: '80px' }}>

<Container>

<h1>Categories</h1>

<Row>

<Col>

<Row style={{ padding: '40px' }}>

{renderCards()}

</Row>

</Col>

</Row>

</Container>

</div>

);

};

export default Categories;

Lampiran 4 code Categories.Jsx

Catalogue.Jsx

import React, { useEffect, useState } from 'react';

import { Container, Row, Col, Card } from 'react- bootstrap';

const Catalogue = () => {

const [items, setItems] = useState([]);

useEffect(() => {

const fetchItems = async () => { const response = await

fetch('http://localhost:3000/items');

const data = await response.json();

setItems(data);

};

fetchItems();

}, []);

return (

(39)

<Container>

<Row>

<Col xs={12} style={{ marginBottom: '20px', marginTop: '50px' }}>

<h1>Catalogue</h1>

</Col>

</Row>

<Row style={{ marginTop: '20px' }}>

{items.map(item => (

<Col key={item.id} md={4} className="mb-4">

<Card style={{ width: '300px', height: '350px' }}>

<Card.Img

variant="top"

src={item.image}

alt={item.name}

style={{ width: '100%', height: '250px', objectFit: 'fill' }}

/>

<Card.Body>

<Card.Title>{item.name}</Card.Title>

</Card.Body>

</Card>

</Col>

))}

</Row>

</Container>

);

};

export default Catalogue;

Lampiran 5 code Catalogue.Jsx

About.Jsx

import React from 'react';

import '../dist/css/main.css';

import { Container, Row, Col, Card } from 'react- bootstrap';

import aboutImg from '../assets/img/white-totebag.jpg';

import terbaikImg from '../assets/img/tas-about.jpg';

const About = () => { return (

<div className="aboutpage">

<Container style={{ textAlign: 'center', padding:

'20px', position: 'relative' }}>

<Row>

<Col>

(40)

<div style={{ borderRadius: '24px', overflow:

'hidden', height: '453px' }}>

<img src={aboutImg} alt="About" style={{

width: '100%', height: '100%', objectFit: 'cover' }} />

</div>

<div style={{ position: 'absolute', top:

'20%', left: '50%', transform: 'translateX(-50%)', zIndex:

1 }}>

<h1 style={{fontWeight:'bolder'}}>Tas Sablon Express</h1>

<p style={{ fontWeight: '500',marginTop:

'20px' }}>

Layanan produksi berbagai jenis tas custom yang dapat digunakan untuk berbagai keperluan, seperti seminar, souvenir, ulang tahun, wedding, produksi promosi perusahaan, dan lainnya.

Tas-tas ini dibuat dari berbagai jenis bahan berkualitas, dengan berbagai ukuran, dan bisa dipesan sesuai kebutuhan pelanggan, termasuk untuk keperluan khusus seperti tas sekolah, tas bidan, tas wedding, dan sebagainya.

</p>

</div>

</Col>

</Row>

</Container>

<div className='pilihan-bahan w-2 min-vh-20'>

<Container

style={{textAlign:'center',position:'relative'}}>

<Row style={{ marginTop: '40px', justifyContent:

'center' }}>

<h1>Pilihan Bahan</h1>

<Col md={3} style={{ margin: '10px' }}>

<Card style={{ height: '100%', border: '2px solid #A0855B' }}>

<Card.Body style={{ padding: '30px' }}>

<Card.Title style={{ textAlign: 'left', fontWeight: 'bold' }}>Spunbond (75 gram dan 100

gram)</Card.Title>

<Card.Text style={{ textAlign: 'left' }}>

Bahan ringan, ramah lingkungan, dan cocok untuk keperluan tas sekali pakai atau tas promosi.

</Card.Text>

</Card.Body>

</Card>

</Col>

<Col md={3} style={{ margin: '10px' }}>

<Card style={{ height: '100%', border: '2px solid #A0855B' }}>

<Card.Body style={{ padding: '30px' }}>

(41)

<Card.Title style={{ textAlign: 'left', fontWeight: 'bold' }}>Baby Canvas & Canvas</Card.Title>

<Card.Text style={{ textAlign: 'left' }}>

Tekstur kain yang kuat dan tahan lama, memberikan tampilan yang lebih mewah.

</Card.Text>

</Card.Body>

</Card>

</Col>

<Col md={3} style={{ margin: '10px' }}>

<Card style={{ height: '100%', border: '2px solid #A0855B' }}>

<Card.Body style={{ padding: '30px' }}>

<Card.Title style={{ textAlign: 'left', fontWeight: 'bold' }}>Blacu</Card.Title>

<Card.Text style={{ textAlign: 'left' }}>

Kain yang kuat dengan tampilan yang sederhana dan alami.

</Card.Text>

</Card.Body>

</Card>

</Col>

</Row>

<Row style={{ justifyContent: 'center' }}>

<Col md={3} style={{ margin: '10px' }}>

<Card style={{ height: '100%', border: '2px solid #A0855B' }}>

<Card.Body style={{ padding: '30px' }}>

<Card.Title style={{ textAlign: 'left', fontWeight: 'bold' }}>Mika</Card.Title>

<Card.Text style={{ textAlign: 'left' }}>

Transparan, cocok untuk pouch atau tas kosmetik.

</Card.Text>

</Card.Body>

</Card>

</Col>

<Col md={3} style={{ margin: '10px' }}>

<Card style={{ height: '100%', border: '2px solid #A0855B' }}>

<Card.Body style={{ padding: '30px' }}>

<Card.Title style={{ textAlign: 'left', fontWeight: 'bold' }}>Dinir (D300 & D600)</Card.Title>

<Card.Text style={{ textAlign: 'left' }}>

D300: Tekstur lembut, sertai kecil, tahan lama dan sulit robek.

(42)

D600: Tekstur lebih besar, wrinkle- free, lebih tahan lama, dan kuat.

</Card.Text>

</Card.Body>

</Card>

</Col>

</Row>

</Container>

</div>

<div className='pilihan-terbaik w-2 min-vh-20 mb-5'>

<Container style={{ textAlign: 'center', position:

'relative' }}>

<Row style={{ marginTop: '40px', justifyContent:

'flex-start' }}>

<h1>Pilihan Terbaik Untuk Kebutuhan Anda</h1>

</Row>

<Row style={{ marginTop: '20px' }}>

<Col md={6} style={{ margin: '10px', display:

'flex', justifyContent: 'center' }}>

<div style={{ borderRadius: '24px', overflow: 'hidden' }}>

<img src={terbaikImg} alt="Pilihan Terbaik" style={{ width: '651px', height: '815px', objectFit: 'cover', borderRadius: '24px' }} />

</div>

</Col>

<Col md={3} style={{ margin: '100px', textAlign: 'left', display: 'flex', flexDirection:

'column', justifyContent: 'center', alignItems: 'center' }}>

<div style={{ marginBottom: '80px' }}>

<h2 style={{ color: '#806A49' }}>Kualitas</h2>

<p>Bahan kuat, tahan lama, dan tersedia berbagai pilihan sesuai kebutuhan dan anggaran.</p>

</div>

<div style={{ marginBottom: '80px' }}>

<h2 style={{ color: '#806A49' }}>Customizable</h2>

<p>Bisa disesuaikan ukuran, warna, bahan, dan jenis sablon atau bordiran sesuai preferensi.</p>

</div>

<div style={{ marginBottom: '80px' }}>

<h2 style={{ color: '#806A49' }}>Harga Terjangkau</h2>

<p>Menawarkan berbagai pilihan harga yang bisa disesuaikan dengan budget pelanggan.</p>

</div>

</Col>

</Row>

</Container>

(43)

</div>

<div className='google-maps w-2 min-vh-20 mb-5' style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>

<h1 style={{ marginBottom: '40px', textAlign:

'center' }}>Lokasi Kami</h1>

<iframe

src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1 d4726.794481922771!2d106.77336628844577!3d-

6.368575505378205!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!

3m3!1m2!1s0x2e69eed94194c9fd%3A0xc56241d049ee54c8!2sJl.%20 Kec.%20No.50%2C%20Limo%2C%20Kec.%20Limo%2C%20Kota%20Depok%

2C%20Jawa%20Barat%2016514!5e0!3m2!1sid!2sid!4v173436394752 3!5m2!1sid!2sid"

width="1152px"

height="453px"

style={{ border: 0, borderRadius: '24px', marginBottom: '20px' }}

allowFullScreen loading="lazy"

referrerPolicy="no-referrer-when-downgrade">

</iframe>

<h5 style={{ textAlign: 'left', alignSelf: 'flex- start', marginLeft: '20%' }}>

LOCATION<br />

Jalan Arthyasa<br />

No. 50, Kec. Limo, Depok 16515 </h5>

</div>

</div>

);

}

export default About;

Lampiran 6 code About.Jsx

Dashboard.Jsx

import React, { useEffect, useState } from 'react';

import { useNavigate } from 'react-router-dom';

import { Container, Row, Col, ListGroup, Alert, Button, Modal, Form } from 'react-bootstrap';

const Dashboard = ({ isLoggedIn }) => {

const [categories, setCategories] = useState([]);

const [items, setItems] = useState([]);

const [showModal, setShowModal] = useState(false);

(44)

const [currentCategory, setCurrentCategory] = useState(null);

const [title, setTitle] = useState('');

const [description, setDescription] = useState('');

const [image, setImage] = useState(null);

const [showItemModal, setShowItemModal] = useState(false);

const [currentItem, setCurrentItem] = useState(null);

const [itemName, setItemName] = useState('');

const [itemImage, setItemImage] = useState(null);

const navigate = useNavigate();

useEffect(() => { if (!isLoggedIn) {

window.alert('Anda harus login terlebih dahulu!');

navigate('/login');

} else {

// Mengambil data kategori

fetch('http://localhost:3000/categories') .then(response => response.json()) .then(data => setCategories(data))

.catch(error => console.error('Error fetching categories:', error));

// Mengambil data items

fetch('http://localhost:3000/items') .then(response => response.json()) .then(data => setItems(data))

.catch(error => console.error('Error fetching items:', error));

}

}, [isLoggedIn, navigate]);

const handleShowModal = (category) => { if (category) {

setTitle(category.title);

setDescription(category.description);

setImage(null);

setCurrentCategory(category);

} else {

setTitle('');

setDescription('');

setImage(null);

setCurrentCategory(null);

}

setShowModal(true);

};

const handleCloseModal = () => { setCurrentCategory(null);

setShowModal(false);

(45)

};

const handleSaveCategory = () => { const formData = new FormData();

formData.append('title', title);

formData.append('description', description);

if (image) {

formData.append('image', image);

}

if (currentCategory) {

// Update kategori yang sudah ada

fetch(`http://localhost:3000/categories/${currentCategory.

id}`, {

method: 'PUT', body: formData, })

.then(response => response.json()) .then(data => {

setCategories(categories.map(cat => (cat.id ===

data.id ? data : cat)));

handleCloseModal();

})

.catch(error => console.error('Error updating category:', error));

} else {

// Tambah kategori baru

fetch('http://localhost:3000/categories', { method: 'POST',

body: formData, })

.then(response => response.json()) .then(data => {

setCategories([...categories, data]);

handleCloseModal();

})

.catch(error => console.error('Error adding category:', error));

} };

const handleDeleteCategory = (id) => {

fetch(`http://localhost:3000/categories/${id}`, { method: 'DELETE',

})

.then(response => { if (response.ok) {

setCategories(categories.filter(category =>

category.id !== id));

} else {

(46)

console.error('Error deleting category');

} })

.catch(error => console.error('Error deleting category:', error));

};

const handleShowItemModal = (item) => { if (item) {

setItemName(item.name);

setItemImage(null);

setCurrentItem(item);

} else {

setItemName('');

setItemImage(null);

setCurrentItem(null);

}

setShowItemModal(true);

};

const handleCloseItemModal = () => { setCurrentItem(null);

setShowItemModal(false);

};

const handleSaveItem = () => { const formData = new FormData();

formData.append('name', itemName);

if (itemImage) {

formData.append('image', itemImage);

}

if (currentItem) {

// Update item yang sudah ada

fetch(`http://localhost:3000/items/${currentItem.id}`, { method: 'PUT',

body: formData, })

.then(response => response.json()) .then(data => {

setItems(items.map(item => (item.id === data.id ? data : item)));

handleCloseItemModal();

})

.catch(error => console.error('Error updating item:', error));

} else {

// Tambah item baru

fetch('http://localhost:3000/items', { method: 'POST',

(47)

body: formData, })

.then(response => response.json()) .then(data => {

setItems([...items, data]);

handleCloseItemModal();

})

.catch(error => console.error('Error adding item:', error));

} };

const handleDeleteItem = (id) => {

fetch(`http://localhost:3000/items/${id}`, { method: 'DELETE',

})

.then(response => { if (response.ok) {

setItems(items.filter(item => item.id !== id));

} else {

console.error('Error deleting item');

} })

.catch(error => console.error('Error deleting item:', error));

};

return (

<Container className="mt-4">

<Alert variant="info">Selamat datang di dashboard admin!</Alert>

<h1 className="text-center mb-4">Dashboard Admin</h1>

<h2 className="text-primary">Jumlah Kategori:

{categories.length}</h2>

<Button onClick={() => handleShowModal(null)}>Tambah Kategori</Button>

<ListGroup className="mb-4">

{categories.map(category => (

<ListGroup.Item key={category.id} className="d- flex justify-content-between align-items-center">

<div>

<strong>{category.title}</strong>

<div className="text-muted">ID:

{category.id}</div>

</div>

<div>

<Button variant="warning" onClick={() =>

handleShowModal(category)}>Edit</Button>

<Button variant="danger" onClick={() =>

handleDeleteCategory(category.id)}>Hapus</Button>

(48)

</div>

</ListGroup.Item>

))}

</ListGroup>

<h2 className="text-success">Jumlah Items:

{items.length}</h2>

<Button onClick={() =>

handleShowItemModal(null)}>Tambah Item</Button>

<ListGroup className="mb-4">

{items.map(item => (

<ListGroup.Item key={item.id} className="d-flex justify-content-between align-items-center">

<div>

<strong>{item.name}</strong>

<div className="text-muted">ID:

{item.id}</div>

</div>

<div>

<Button variant="warning" onClick={() =>

handleShowItemModal(item)}>Edit</Button>

<Button variant="danger" onClick={() =>

handleDeleteItem(item.id)}>Hapus</Button>

</div>

</ListGroup.Item>

))}

</ListGroup>

<Modal show={showModal} onHide={handleCloseModal}>

<Modal.Header closeButton>

<Modal.Title>{currentCategory ? 'Edit Kategori' : 'Tambah Kategori'}</Modal.Title>

</Modal.Header>

<Modal.Body>

<Form>

<Form.Group controlId="formCategoryTitle">

<Form.Label>Judul Kategori</Form.Label>

<Form.Control type="text"

value={title}

onChange={(e) => setTitle(e.target.value)}

/>

</Form.Group>

<Form.Group

controlId="formCategoryDescription">

<Form.Label>Deskripsi Kategori</Form.Label>

<Form.Control as="textarea"

rows={3}

value={description}

onChange={(e) =>

setDescription(e.target.value)}

(49)

/>

</Form.Group>

<Form.Group controlId="formCategoryImage">

<Form.Label>Unggah Gambar</Form.Label>

<Form.Control type="file"

onChange={(e) =>

setImage(e.target.files[0])}

/>

</Form.Group>

</Form>

</Modal.Body>

<Modal.Footer>

<Button variant="secondary"

onClick={handleCloseModal}>

Tutup </Button>

<Button variant="primary"

onClick={handleSaveCategory}>

Simpan </Button>

</Modal.Footer>

</Modal>

<Modal show={showItemModal}

onHide={handleCloseItemModal}>

<Modal.Header closeButton>

<Modal.Title>{currentItem ? 'Edit Item' : 'Tambah Item'}</Modal.Title>

</Modal.Header>

<Modal.Body>

<Form>

<Form.Group controlId="formItemName">

<Form.Label>Nama Item</Form.Label>

<Form.Control type="text"

value={itemName}

onChange={(e) =>

setItemName(e.target.value)}

/>

</Form.Group>

<Form.Group controlId="formItemImage">

<Form.Label>Unggah Gambar</Form.Label>

<Form.Control type="file"

onChange={(e) =>

setItemImage(e.target.files[0])}

/>

</Form.Group>

</Form>

</Modal.Body>

(50)

<Modal.Footer>

<Button variant="secondary"

onClick={handleCloseItemModal}>

Tutup </Button>

<Button variant="primary"

onClick={handleSaveItem}>

Simpan </Button>

</Modal.Footer>

</Modal>

</Container>

);

};

export default Dashboard;

Lampiran 7 code Dashboard.Jsx

Login.Jsx

import React, { useState } from 'react';

import { Container, Row, Col, Form, Button } from 'react- bootstrap';

import loginImg from '../assets/img/login.jpg';

import loginBg from '../assets/img/bg-login.jpg';

import { useNavigate } from "react-router-dom";

import { FaEye, FaEyeSlash } from 'react-icons/fa';

const Login = ({ setIsLoggedIn }) => { const navigate = useNavigate();

const [showPassword, setShowPassword] = useState(false);

const handleTogglePassword = () => { setShowPassword(!showPassword);

};

const handleSubmit = (event) => { event.preventDefault();

const username = event.target.username.value;

const password = event.target.password.value;

fetch(`http://localhost:3000/login-

admin?username=${username}&password=${password}`) .then(response => response.json())

.then(data => {

if (data.message === 'Login admin berhasil') { alert(data.message);

setIsLoggedIn(true);

navigate('/dashboard');

(51)

} else {

alert(data.message);

} })

.catch(error => {

console.error('Error:', error);

alert('Terjadi kesalahan saat login');

});

};

return (

<div className="login" style={{ backgroundImage:

`url(${loginBg})`, backgroundSize: 'cover', backgroundPosition: 'center' }}>

<Container className="mt-5">

<Row className="justify-content-center align- items-center">

<Col md={6} className="login-image">

<img src={loginImg} alt="Login"

className="img-fluid" />

</Col>

<Col md={6} className="login-container">

<h2 className="text-center login-title font- weight-bold">Login Admin</h2>

<p className="text-center">Selamat datang di portal admin, login untuk memulai.</p>

<Form onSubmit={handleSubmit}>

<Form.Group controlId="formBasicUsername"

className="mb-3">

<Form.Label>Masukan username</Form.Label>

<Form.Control type="text" name="username"

placeholder="Masukan username" />

</Form.Group>

<Form.Group controlId="formBasicPassword"

className="mb-3">

<Form.Label>Masukan password</Form.Label>

<div className="input-group">

<Form.Control

type={showPassword ? "text" :

"password"}

name="password"

placeholder="Masukan password"

/>

<span

className="input-group-text"

onClick={handleTogglePassword}

style={{ cursor: 'pointer', border:

'none' }}

>

{showPassword ? <FaEyeSlash /> :

<FaEye />}

(52)

</span>

</div>

</Form.Group>

<Button type="submit" className="w-100 login-button custom-login-button">

LOGIN </Button>

</Form>

<Button

variant="link"

onClick={() => navigate('/')}

className="mt-3"

>

Kembali ke Homepage </Button>

</Col>

</Row>

</Container>

</div>

);

}

export default Login;

Lampiran 8 code Login.Jsx

Server.js

const express = require('express');

const mysql = require('mysql');

const bodyParser = require('body-parser');

const cors = require('cors');

const path = require('path');

const multer = require('multer');

const app = express();

const port = 3000;

// Middleware untuk parsing JSON app.use(bodyParser.json());

// Middleware untuk CORS app.use(cors());

// Middleware untuk menyajikan file statis

app.use(express.static(path.join(__dirname, 'public')));

// Konfigurasi multer untuk menyimpan file const storage = multer.diskStorage({

destination: (req, file, cb) => {

(53)

cb(null, path.join(__dirname, '../sablon- app/src/assets/img')); // Simpan di src/assets/img },

filename: (req, file, cb) => { cb(null, Date.now() +

path.extname(file.originalname)); // Menyimpan dengan nama unik

} });

const upload = multer({ storage });

// Konfigurasi koneksi ke database MySQL const db = mysql.createConnection({

host: 'localhost', user: 'root', password: '',

database: 'tas_sablon' });

// Koneksi ke database db.connec

Referensi

Dokumen terkait