Autosave: 20260217-135934
This commit is contained in:
parent
12ab3d7339
commit
e17a272296
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,3 +1,23 @@
|
||||
from django.contrib import admin
|
||||
from .models import Category, Shop, Product
|
||||
|
||||
# Register your models here.
|
||||
@admin.register(Category)
|
||||
class CategoryAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'slug')
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
|
||||
@admin.register(Shop)
|
||||
class ShopAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'owner', 'whatsapp_number', 'created_at')
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
if not obj.pk:
|
||||
obj.owner = request.user
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
@admin.register(Product)
|
||||
class ProductAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'shop', 'category', 'price', 'stock', 'is_active')
|
||||
list_filter = ('shop', 'category', 'is_active')
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
|
||||
58
core/migrations/0001_initial.py
Normal file
58
core/migrations/0001_initial.py
Normal file
@ -0,0 +1,58 @@
|
||||
# Generated by Django 5.2.7 on 2026-02-17 04:38
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Category',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('slug', models.SlugField(blank=True, unique=True)),
|
||||
('icon', models.CharField(default='fa-tag', help_text='FontAwesome icon class (e.g., fa-shopping-basket)', max_length=50)),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Categories',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Shop',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('slug', models.SlugField(blank=True, unique=True)),
|
||||
('description', models.TextField(blank=True)),
|
||||
('logo', models.FileField(blank=True, null=True, upload_to='shop_logos/')),
|
||||
('whatsapp_number', models.CharField(help_text='Format: 628123456789', max_length=20)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='shops', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Product',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('slug', models.SlugField(blank=True, unique=True)),
|
||||
('description', models.TextField()),
|
||||
('price', models.DecimalField(decimal_places=2, max_digits=12)),
|
||||
('image', models.FileField(blank=True, null=True, upload_to='product_images/')),
|
||||
('stock', models.IntegerField(default=0)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('category', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='products', to='core.category')),
|
||||
('shop', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='core.shop')),
|
||||
],
|
||||
),
|
||||
]
|
||||
BIN
core/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
BIN
core/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
Binary file not shown.
@ -1,3 +1,63 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.text import slugify
|
||||
import urllib.parse
|
||||
|
||||
# Create your models here.
|
||||
class Category(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
slug = models.SlugField(unique=True, blank=True)
|
||||
icon = models.CharField(max_length=50, help_text="FontAwesome icon class (e.g., fa-shopping-basket)", default="fa-tag")
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.name)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = "Categories"
|
||||
|
||||
def __str__(self):
|
||||
return str(self.name)
|
||||
|
||||
class Shop(models.Model):
|
||||
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='shops')
|
||||
name = models.CharField(max_length=255)
|
||||
slug = models.SlugField(unique=True, blank=True)
|
||||
description = models.TextField(blank=True)
|
||||
logo = models.FileField(upload_to='shop_logos/', blank=True, null=True)
|
||||
whatsapp_number = models.CharField(max_length=20, help_text="Format: 628123456789")
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.name)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.name)
|
||||
|
||||
class Product(models.Model):
|
||||
shop = models.ForeignKey(Shop, on_delete=models.CASCADE, related_name='products')
|
||||
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name='products')
|
||||
name = models.CharField(max_length=255)
|
||||
slug = models.SlugField(unique=True, blank=True)
|
||||
description = models.TextField()
|
||||
price = models.DecimalField(max_digits=12, decimal_places=2)
|
||||
image = models.FileField(upload_to='product_images/', blank=True, null=True)
|
||||
stock = models.IntegerField(default=0)
|
||||
is_active = models.BooleanField(default=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.slug:
|
||||
self.slug = slugify(self.name)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.name)
|
||||
|
||||
@property
|
||||
def whatsapp_link(self):
|
||||
message = f"Halo {self.shop.name}, saya tertarik dengan produk *{self.name}* seharga Rp {self.price:,.0f}. Apakah masih tersedia?"
|
||||
encoded_message = urllib.parse.quote(message)
|
||||
return f"https://wa.me/{self.shop.whatsapp_number}?text={encoded_message}"
|
||||
|
||||
@ -1,25 +1,97 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="id">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{% block title %}Knowledge Base{% endblock %}</title>
|
||||
{% if project_description %}
|
||||
<meta name="description" content="{{ project_description }}">
|
||||
<meta property="og:description" content="{{ project_description }}">
|
||||
<meta property="twitter:description" content="{{ project_description }}">
|
||||
{% endif %}
|
||||
{% if project_image_url %}
|
||||
<meta property="og:image" content="{{ project_image_url }}">
|
||||
<meta property="twitter:image" content="{{ project_image_url }}">
|
||||
{% endif %}
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}Marketplace UMKM Pemagarsari{% endblock %}</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Poppins:wght@500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Bootstrap 5 -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- FontAwesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
|
||||
{% load static %}
|
||||
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
|
||||
<link rel="stylesheet" href="{% static 'css/custom.css' %}">
|
||||
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{% block content %}{% endblock %}
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar navbar-expand-lg sticky-top">
|
||||
<div class="container">
|
||||
<a class="navbar-brand fw-bold" href="{% url 'home' %}">
|
||||
<i class="fa-solid fa-shop text-emerald me-2"></i><span class="text-emerald">UMKM</span> Pemagarsari
|
||||
</a>
|
||||
<button class="navbar-toggler border-0 shadow-none" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto align-items-center">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link px-3" href="{% url 'home' %}">Beranda</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link px-3" href="{% url 'shop_list' %}">Toko</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link px-3" href="{% url 'home' %}#produk">Produk</a>
|
||||
</li>
|
||||
{% if user.is_authenticated %}
|
||||
<li class="nav-item dropdown ms-lg-3">
|
||||
<a class="nav-link dropdown-toggle btn btn-light rounded-pill px-4" href="#" role="button" data-bs-toggle="dropdown">
|
||||
<i class="fa-solid fa-user-circle me-2"></i>{{ user.username }}
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end border-0 shadow-sm rounded-3">
|
||||
<li><a class="dropdown-item" href="{% url 'dashboard' %}">Dashboard</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<form action="{% url 'logout' %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="dropdown-item text-danger">Logout</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item ms-lg-3">
|
||||
<a class="btn btn-emerald rounded-pill px-4" href="{% url 'login' %}">
|
||||
<i class="fa-solid fa-right-to-bracket me-2"></i>Masuk
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="py-5 mt-5">
|
||||
<div class="container text-center">
|
||||
<h5 class="fw-bold mb-3">UMKM Desa Pemagarsari</h5>
|
||||
<p class="text-muted small mb-4">Membangun ekonomi desa melalui digitalisasi marketplace mandiri.</p>
|
||||
<div class="social-links mb-4">
|
||||
<a href="#" class="mx-2"><i class="fa-brands fa-facebook text-muted"></i></a>
|
||||
<a href="#" class="mx-2"><i class="fa-brands fa-instagram text-muted"></i></a>
|
||||
<a href="#" class="mx-2"><i class="fa-brands fa-whatsapp text-muted"></i></a>
|
||||
</div>
|
||||
<hr class="w-25 mx-auto">
|
||||
<p class="text-muted small mt-4">© 2026 Desa Pemagarsari. Powered by Flatlogic.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
37
core/templates/core/auth/login.html
Normal file
37
core/templates/core/auth/login.html
Normal file
@ -0,0 +1,37 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Masuk - Marketplace UMKM Pemagarsari{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
<div class="row justify-content-center py-5">
|
||||
<div class="col-md-5">
|
||||
<div class="card border-0 rounded-4 shadow-sm p-4 p-md-5">
|
||||
<div class="text-center mb-4">
|
||||
<h2 class="fw-bold">Selamat Datang</h2>
|
||||
<p class="text-muted">Masuk untuk mengelola toko Anda</p>
|
||||
</div>
|
||||
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold">Username</label>
|
||||
<input type="text" name="username" class="form-control rounded-3 shadow-none py-2" required>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-bold">Password</label>
|
||||
<input type="password" name="password" class="form-control rounded-3 shadow-none py-2" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-emerald w-100 rounded-pill py-3 mb-3">
|
||||
Masuk Sekarang
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<p class="small text-muted mb-0">Belum punya akun? <br>Silakan hubungi Admin Desa untuk pendaftaran akun Penjual.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
167
core/templates/core/dashboard/index.html
Normal file
167
core/templates/core/dashboard/index.html
Normal file
@ -0,0 +1,167 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Dashboard Penjual - UMKM Pemagarsari{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
<div class="d-flex justify-content-between align-items-center mb-5">
|
||||
<div>
|
||||
<h1 class="fw-bold mb-1">Dashboard Penjual</h1>
|
||||
<p class="text-muted mb-0">Kelola toko dan produk Anda di sini.</p>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{% url 'shop_add' %}" class="btn btn-outline-emerald rounded-pill px-4">
|
||||
<i class="fa-solid fa-shop me-2"></i>Tambah Toko
|
||||
</a>
|
||||
<a href="{% url 'product_add' %}" class="btn btn-emerald rounded-pill px-4">
|
||||
<i class="fa-solid fa-plus me-2"></i>Tambah Produk
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats -->
|
||||
<div class="row g-4 mb-5">
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="card border-0 rounded-4 shadow-sm p-4">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<div class="bg-emerald bg-opacity-10 p-2 rounded-3 me-3">
|
||||
<i class="fa-solid fa-shop text-emerald"></i>
|
||||
</div>
|
||||
<span class="text-muted small">Toko Anda</span>
|
||||
</div>
|
||||
<h3 class="fw-bold mb-0">{{ shops.count }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="card border-0 rounded-4 shadow-sm p-4">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<div class="bg-primary bg-opacity-10 p-2 rounded-3 me-3">
|
||||
<i class="fa-solid fa-box text-primary"></i>
|
||||
</div>
|
||||
<span class="text-muted small">Total Produk</span>
|
||||
</div>
|
||||
<h3 class="fw-bold mb-0">{{ products.count }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Shops Table -->
|
||||
<div class="card border-0 rounded-4 shadow-sm overflow-hidden mb-5">
|
||||
<div class="card-header bg-white py-3 border-0">
|
||||
<h5 class="fw-bold mb-0">Toko Saya</h5>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light text-muted small uppercase">
|
||||
<tr>
|
||||
<th class="ps-4">Toko</th>
|
||||
<th>WhatsApp</th>
|
||||
<th class="text-end pe-4">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for shop in shops %}
|
||||
<tr>
|
||||
<td class="ps-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="rounded-3 bg-light me-3 overflow-hidden" style="width: 40px; height: 40px;">
|
||||
{% if shop.logo %}
|
||||
<img src="{{ shop.logo.url }}" class="w-100 h-100 object-fit-cover">
|
||||
{% else %}
|
||||
<div class="w-100 h-100 d-flex align-items-center justify-content-center">
|
||||
<i class="fa-solid fa-shop text-muted"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<span class="fw-bold">{{ shop.name }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ shop.whatsapp_number }}</td>
|
||||
<td class="text-end pe-4">
|
||||
<a href="{% url 'shop_edit' shop.pk %}" class="btn btn-light btn-sm rounded-pill px-3">
|
||||
<i class="fa-solid fa-pen-to-square me-1"></i> Edit
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="3" class="text-center py-4">
|
||||
<p class="text-muted mb-0">Anda belum memiliki toko.</p>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Products Table -->
|
||||
<div class="card border-0 rounded-4 shadow-sm overflow-hidden">
|
||||
<div class="card-header bg-white py-3 border-0">
|
||||
<h5 class="fw-bold mb-0">Produk Saya</h5>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light text-muted small uppercase">
|
||||
<tr>
|
||||
<th class="ps-4">Produk</th>
|
||||
<th>Toko</th>
|
||||
<th>Kategori</th>
|
||||
<th>Harga</th>
|
||||
<th>Stok</th>
|
||||
<th class="text-end pe-4">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for product in products %}
|
||||
<tr>
|
||||
<td class="ps-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="rounded-3 bg-light me-3 overflow-hidden" style="width: 48px; height: 48px;">
|
||||
{% if product.image %}
|
||||
<img src="{{ product.image.url }}" class="w-100 h-100 object-fit-cover">
|
||||
{% else %}
|
||||
<div class="w-100 h-100 d-flex align-items-center justify-content-center">
|
||||
<i class="fa-solid fa-image text-muted"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<span class="fw-bold">{{ product.name }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ product.shop.name }}</td>
|
||||
<td><span class="badge bg-light text-dark rounded-pill">{{ product.category.name }}</span></td>
|
||||
<td>Rp {{ product.price|floatformat:0 }}</td>
|
||||
<td>{{ product.stock }}</td>
|
||||
<td class="text-end pe-4">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-light btn-sm rounded-circle shadow-none" type="button" data-bs-toggle="dropdown">
|
||||
<i class="fa-solid fa-ellipsis-vertical"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end border-0 shadow-sm rounded-3">
|
||||
<li><a class="dropdown-item" href="{% url 'product_edit' product.pk %}"><i class="fa-solid fa-pen-to-square me-2 text-muted"></i> Edit</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<form action="{% url 'product_delete' product.pk %}" method="POST" onsubmit="return confirm('Yakin ingin menghapus produk ini?')">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="dropdown-item text-danger"><i class="fa-solid fa-trash me-2"></i> Hapus</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center py-5">
|
||||
<p class="text-muted mb-3">Anda belum menambahkan produk.</p>
|
||||
<a href="{% url 'product_add' %}" class="btn btn-emerald btn-sm rounded-pill px-4">Tambah Sekarang</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
74
core/templates/core/dashboard/product_form.html
Normal file
74
core/templates/core/dashboard/product_form.html
Normal file
@ -0,0 +1,74 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}{{ title }} - Dashboard{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="card border-0 rounded-4 shadow-sm p-4 p-md-5">
|
||||
<div class="d-flex align-items-center mb-4">
|
||||
<a href="{% url 'dashboard' %}" class="btn btn-light rounded-circle me-3">
|
||||
<i class="fa-solid fa-arrow-left"></i>
|
||||
</a>
|
||||
<h2 class="fw-bold mb-0">{{ title }}</h2>
|
||||
</div>
|
||||
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-bold">Pilih Toko</label>
|
||||
<select name="shop" class="form-select rounded-3 shadow-none" required>
|
||||
{% for shop in shops %}
|
||||
<option value="{{ shop.id }}" {% if product.shop.id == shop.id %}selected{% endif %}>{{ shop.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-bold">Kategori</label>
|
||||
<select name="category" class="form-select rounded-3 shadow-none" required>
|
||||
{% for cat in categories %}
|
||||
<option value="{{ cat.id }}" {% if product.category.id == cat.id %}selected{% endif %}>{{ cat.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold">Nama Produk</label>
|
||||
<input type="text" name="name" value="{{ product.name }}" class="form-control rounded-3 shadow-none" placeholder="Contoh: Kerupuk Jengkol Pedas" required>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold">Deskripsi</label>
|
||||
<textarea name="description" class="form-control rounded-3 shadow-none" rows="4" placeholder="Jelaskan keunggulan produk Anda..." required>{{ product.description }}</textarea>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-bold">Harga (Rp)</label>
|
||||
<input type="number" name="price" value="{{ product.price|floatformat:0 }}" class="form-control rounded-3 shadow-none" placeholder="0" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-bold">Stok</label>
|
||||
<input type="number" name="stock" value="{{ product.stock|default:0 }}" class="form-control rounded-3 shadow-none" required>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold">Foto Produk</label>
|
||||
{% if product.image %}
|
||||
<div class="mb-2">
|
||||
<img src="{{ product.image.url }}" class="rounded-3" style="height: 100px;">
|
||||
</div>
|
||||
{% endif %}
|
||||
<input type="file" name="image" class="form-control rounded-3 shadow-none">
|
||||
<small class="text-muted">Kosongkan jika tidak ingin mengubah foto.</small>
|
||||
</div>
|
||||
<div class="col-12 pt-3">
|
||||
<button type="submit" class="btn btn-emerald btn-lg w-100 rounded-pill py-3">
|
||||
<i class="fa-solid fa-save me-2"></i>Simpan Produk
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
54
core/templates/core/dashboard/shop_form.html
Normal file
54
core/templates/core/dashboard/shop_form.html
Normal file
@ -0,0 +1,54 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}{{ title }} - Dashboard{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="card border-0 rounded-4 shadow-sm p-4 p-md-5">
|
||||
<div class="d-flex align-items-center mb-4">
|
||||
<a href="{% url 'dashboard' %}" class="btn btn-light rounded-circle me-3">
|
||||
<i class="fa-solid fa-arrow-left"></i>
|
||||
</a>
|
||||
<h2 class="fw-bold mb-0">{{ title }}</h2>
|
||||
</div>
|
||||
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold">Nama Toko</label>
|
||||
<input type="text" name="name" value="{{ shop.name }}" class="form-control rounded-3 shadow-none" placeholder="Contoh: Dapur Sejahtera" required>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold">Deskripsi Toko</label>
|
||||
<textarea name="description" class="form-control rounded-3 shadow-none" rows="4" placeholder="Ceritakan tentang toko Anda..." required>{{ shop.description }}</textarea>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold">Nomor WhatsApp</label>
|
||||
<input type="text" name="whatsapp_number" value="{{ shop.whatsapp_number }}" class="form-control rounded-3 shadow-none" placeholder="Contoh: 628123456789" required>
|
||||
<small class="text-muted">Gunakan format internasional tanpa tanda plus (+). Contoh: 628123456789</small>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label fw-bold">Logo Toko (Opsional)</label>
|
||||
{% if shop.logo %}
|
||||
<div class="mb-2">
|
||||
<img src="{{ shop.logo.url }}" class="rounded-3" style="height: 100px;">
|
||||
</div>
|
||||
{% endif %}
|
||||
<input type="file" name="logo" class="form-control rounded-3 shadow-none">
|
||||
</div>
|
||||
<div class="col-12 pt-3">
|
||||
<button type="submit" class="btn btn-emerald btn-lg w-100 rounded-pill py-3">
|
||||
<i class="fa-solid fa-save me-2"></i>Simpan Toko
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -1,145 +1,135 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ project_name }}{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color-start: #6a11cb;
|
||||
--bg-color-end: #2575fc;
|
||||
--text-color: #ffffff;
|
||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'><path d='M-10 10L110 10M10 -10L10 110' stroke-width='1' stroke='rgba(255,255,255,0.05)'/></svg>");
|
||||
animation: bg-pan 20s linear infinite;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
@keyframes bg-pan {
|
||||
0% {
|
||||
background-position: 0% 0%;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: 100% 100%;
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--card-bg-color);
|
||||
border: 1px solid var(--card-border-color);
|
||||
border-radius: 16px;
|
||||
padding: 2.5rem 2rem;
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: clamp(2.2rem, 3vw + 1.2rem, 3.2rem);
|
||||
font-weight: 700;
|
||||
margin: 0 0 1.2rem;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
opacity: 0.92;
|
||||
}
|
||||
|
||||
.loader {
|
||||
margin: 1.5rem auto;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border: 4px solid rgba(255, 255, 255, 0.25);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.runtime code {
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
padding: 0.15rem 0.45rem;
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0;
|
||||
}
|
||||
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.75;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<main>
|
||||
<div class="card">
|
||||
<h1>Analyzing your requirements and generating your app…</h1>
|
||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||
<span class="sr-only">Loading…</span>
|
||||
<!-- Hero Section -->
|
||||
<section class="hero-section">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-lg-6">
|
||||
<span class="badge bg-emerald mb-3 px-3 py-2 rounded-pill">Digitalisasi UMKM Desa</span>
|
||||
<h1 class="hero-title mb-4">Majukan Ekonomi Desa Pemagarsari</h1>
|
||||
<p class="lead text-muted mb-4">Temukan produk unggulan dari warga desa kami. Pesan langsung via WhatsApp tanpa ribet, dukung produk lokal hari ini.</p>
|
||||
|
||||
<!-- Search Bar -->
|
||||
<form action="{% url 'home' %}" method="GET" class="mb-4">
|
||||
<div class="search-box p-2 bg-white rounded-pill shadow-sm d-flex">
|
||||
<input type="text" name="q" value="{{ query }}" class="form-control border-0 bg-transparent px-4 shadow-none" placeholder="Cari kerajinan, makanan, atau jasa...">
|
||||
<button type="submit" class="btn btn-emerald rounded-pill px-4">
|
||||
<i class="fa-solid fa-magnifying-glass me-2"></i>Cari
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="d-flex flex-wrap gap-3">
|
||||
<a href="#produk" class="btn btn-outline-emerald rounded-pill px-4">Jelajahi Produk</a>
|
||||
{% if query or selected_category %}
|
||||
<a href="{% url 'home' %}" class="btn btn-link text-muted text-decoration-none">Reset Filter</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 d-none d-lg-block text-center">
|
||||
<div class="position-relative p-4">
|
||||
<div class="bg-emerald rounded-circle position-absolute top-50 start-50 translate-middle opacity-10" style="width: 500px; height: 500px; filter: blur(60px);"></div>
|
||||
<div class="position-relative z-1 shadow-lg rounded-4 overflow-hidden border border-white border-4 transform-hover transition-all">
|
||||
<img src="https://images.unsplash.com/photo-1590632823277-285f21442df1?auto=format&fit=crop&q=80&w=800" alt="Pasar Tradisional Desa" class="img-fluid" style="max-height: 450px; width: 100%; object-fit: cover;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="hint">AppWizzy AI is collecting your requirements and applying the first changes.</p>
|
||||
<p class="hint">This page will refresh automatically as the plan is implemented.</p>
|
||||
<p class="runtime">
|
||||
Runtime: Django <code>{{ django_version }}</code> · Python <code>{{ python_version }}</code>
|
||||
— UTC <code>{{ current_time|date:"Y-m-d H:i:s" }}</code>
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC)
|
||||
</footer>
|
||||
{% endblock %}
|
||||
</section>
|
||||
|
||||
<!-- Categories -->
|
||||
<section id="kategori" class="py-5">
|
||||
<div class="container">
|
||||
<div class="text-center mb-5">
|
||||
<h2 class="fw-bold">Kategori Populer</h2>
|
||||
<p class="text-muted">Cari produk berdasarkan kategori</p>
|
||||
</div>
|
||||
<div class="row g-4 justify-content-center">
|
||||
<div class="col-6 col-md-2">
|
||||
<a href="{% url 'home' %}" class="category-card {% if not selected_category %}active{% endif %}">
|
||||
<div class="category-icon">
|
||||
<i class="fa-solid fa-border-all"></i>
|
||||
</div>
|
||||
<h6 class="fw-bold mb-0 text-dark">Semua</h6>
|
||||
</a>
|
||||
</div>
|
||||
{% for category in categories %}
|
||||
<div class="col-6 col-md-2">
|
||||
<a href="?category={{ category.slug }}{% if query %}&q={{ query }}{% endif %}" class="category-card {% if selected_category == category.slug %}active{% endif %}">
|
||||
<div class="category-icon">
|
||||
<i class="fa-solid {{ category.icon }}"></i>
|
||||
</div>
|
||||
<h6 class="fw-bold mb-0 text-dark">{{ category.name }}</h6>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Featured Products -->
|
||||
<section id="produk" class="py-5 bg-light">
|
||||
<div class="container">
|
||||
<div class="text-center mb-5">
|
||||
<h2 class="fw-bold">Produk Terbaru</h2>
|
||||
<p class="text-muted">Produk unggulan dari para pengrajin dan pedagang desa</p>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
{% for product in featured_products %}
|
||||
<div class="col-12 col-sm-6 col-lg-3">
|
||||
<div class="product-card">
|
||||
<div class="product-img-wrapper">
|
||||
{% if product.image %}
|
||||
<img src="{{ product.image.url }}" alt="{{ product.name }}" class="img-fluid w-100 h-100 object-fit-cover">
|
||||
{% else %}
|
||||
<i class="fa-solid fa-image"></i>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span class="shop-badge">{{ product.shop.name }}</span>
|
||||
<small class="text-muted">{{ product.category.name }}</small>
|
||||
</div>
|
||||
<h5 class="fw-bold mb-3">
|
||||
<a href="{% url 'product_detail' product.slug %}" class="text-decoration-none text-dark">{{ product.name }}</a>
|
||||
</h5>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="product-price">Rp {{ product.price|floatformat:0 }}</span>
|
||||
<a href="{{ product.whatsapp_link }}" target="_blank" class="btn btn-whatsapp btn-sm px-3">
|
||||
<i class="fa-brands fa-whatsapp me-2"></i>Pesan
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="col-12 text-center py-5">
|
||||
<div class="bg-white p-5 rounded-4 shadow-sm">
|
||||
<i class="fa-solid fa-box-open fa-3x text-muted mb-3"></i>
|
||||
<p class="text-muted">Belum ada produk untuk ditampilkan.</p>
|
||||
<a href="/admin/core/product/add/" class="btn btn-emerald mt-3">Tambah Produk Pertama</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Call to Action -->
|
||||
<section class="py-5">
|
||||
<div class="container">
|
||||
<div class="p-5 rounded-4 text-white text-center position-relative overflow-hidden shadow-lg" style="background: linear-gradient(135deg, rgba(6, 78, 59, 0.9) 0%, rgba(16, 185, 129, 0.9) 100%), url('https://images.unsplash.com/photo-1544333346-64e4fe18204b?auto=format&fit=crop&q=80&w=1200'); background-size: cover; background-position: center;">
|
||||
<div class="position-relative z-1">
|
||||
<h2 class="fw-bold mb-3">Punya Usaha di Desa Pemagarsari?</h2>
|
||||
<p class="mb-4 opacity-75">Daftarkan toko Anda dan mulai jualan online hari ini. Mudah, gratis, dan langsung ke WhatsApp!</p>
|
||||
<div class="d-flex justify-content-center gap-3">
|
||||
<a href="{% if user.is_authenticated %}{% url 'dashboard' %}{% else %}{% url 'login' %}{% endif %}" class="btn btn-emerald btn-lg px-5 rounded-pill shadow-lg">Mulai Jualan Sekarang</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
105
core/templates/core/product_detail.html
Normal file
105
core/templates/core/product_detail.html
Normal file
@ -0,0 +1,105 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{{ product.name }} - UMKM Pemagarsari{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="py-5">
|
||||
<div class="container">
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'home' %}" class="text-decoration-none text-emerald">Beranda</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{{ product.name }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div class="row g-5">
|
||||
<div class="col-md-6">
|
||||
<div class="bg-white rounded-4 p-2 shadow-sm">
|
||||
<div class="product-img-wrapper rounded-3" style="height: 450px;">
|
||||
{% if product.image %}
|
||||
<img src="{{ product.image.url }}" alt="{{ product.name }}" class="img-fluid w-100 h-100 object-fit-cover rounded-3">
|
||||
{% else %}
|
||||
<i class="fa-solid fa-image fa-4x"></i>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="ps-lg-4">
|
||||
<span class="badge bg-emerald mb-2 px-3 py-2 rounded-pill">{{ product.category.name }}</span>
|
||||
<h1 class="fw-bold mb-3">{{ product.name }}</h1>
|
||||
<div class="d-flex align-items-center mb-4">
|
||||
<div class="bg-light p-2 rounded-3 me-3">
|
||||
<i class="fa-solid fa-shop text-emerald"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-0 small text-muted">Dijual oleh</p>
|
||||
<h6 class="fw-bold mb-0">{{ product.shop.name }}</h6>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="product-price mb-4" style="font-size: 2.5rem;">Rp {{ product.price|floatformat:0 }}</h2>
|
||||
|
||||
<div class="mb-4">
|
||||
<h6 class="fw-bold">Deskripsi Produk</h6>
|
||||
<p class="text-muted">{{ product.description|linebreaks }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 bg-light rounded-4 p-4 mb-4">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<i class="fa-solid fa-circle-info text-emerald me-2"></i>
|
||||
<h6 class="fw-bold mb-0">Cara Pemesanan</h6>
|
||||
</div>
|
||||
<p class="small text-muted mb-0">Klik tombol di bawah untuk terhubung langsung dengan WhatsApp penjual. Pesan otomatis akan terisi dengan detail produk ini.</p>
|
||||
</div>
|
||||
|
||||
<div class="d-grid">
|
||||
<a href="{{ product.whatsapp_link }}" target="_blank" class="btn btn-whatsapp btn-lg py-3 rounded-pill">
|
||||
<i class="fa-brands fa-whatsapp me-2"></i>Pesan via WhatsApp Sekarang
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Related Products Placeholder -->
|
||||
<section class="py-5 bg-light mt-5">
|
||||
<div class="container text-center">
|
||||
<h4 class="fw-bold mb-4">Dukung UMKM Lainnya</h4>
|
||||
<div class="row g-4 opacity-50">
|
||||
<!-- Simulated related products -->
|
||||
<div class="col-md-3">
|
||||
<div class="bg-white p-4 rounded-4 shadow-sm">
|
||||
<div class="bg-light mb-3 rounded" style="height: 150px;"></div>
|
||||
<div class="bg-light w-75 h-25 mb-2 mx-auto"></div>
|
||||
<div class="bg-light w-50 h-25 mx-auto"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 d-none d-md-block">
|
||||
<div class="bg-white p-4 rounded-4 shadow-sm">
|
||||
<div class="bg-light mb-3 rounded" style="height: 150px;"></div>
|
||||
<div class="bg-light w-75 h-25 mb-2 mx-auto"></div>
|
||||
<div class="bg-light w-50 h-25 mx-auto"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 d-none d-md-block">
|
||||
<div class="bg-white p-4 rounded-4 shadow-sm">
|
||||
<div class="bg-light mb-3 rounded" style="height: 150px;"></div>
|
||||
<div class="bg-light w-75 h-25 mb-2 mx-auto"></div>
|
||||
<div class="bg-light w-50 h-25 mx-auto"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 d-none d-md-block">
|
||||
<div class="bg-white p-4 rounded-4 shadow-sm">
|
||||
<div class="bg-light mb-3 rounded" style="height: 150px;"></div>
|
||||
<div class="bg-light w-75 h-25 mb-2 mx-auto"></div>
|
||||
<div class="bg-light w-50 h-25 mx-auto"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
83
core/templates/core/shop_detail.html
Normal file
83
core/templates/core/shop_detail.html
Normal file
@ -0,0 +1,83 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}{{ shop.name }} - UMKM Pemagarsari{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="py-5">
|
||||
<div class="container">
|
||||
<!-- Shop Header -->
|
||||
<div class="bg-white rounded-4 shadow-sm p-4 p-md-5 mb-5 overflow-hidden position-relative">
|
||||
<div class="position-absolute top-0 end-0 p-4 opacity-10">
|
||||
<i class="fa-solid fa-shop fa-8x"></i>
|
||||
</div>
|
||||
<div class="row align-items-center position-relative z-1">
|
||||
<div class="col-md-auto mb-4 mb-md-0 text-center">
|
||||
<div class="bg-emerald rounded-circle d-flex align-items-center justify-content-center text-white mx-auto" style="width: 120px; height: 120px; overflow: hidden;">
|
||||
{% if shop.logo %}
|
||||
<img src="{{ shop.logo.url }}" alt="{{ shop.name }}" class="img-fluid w-100 h-100 object-fit-cover">
|
||||
{% else %}
|
||||
<i class="fa-solid fa-shop fa-3x"></i>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md ps-md-4">
|
||||
<h1 class="fw-bold mb-2">{{ shop.name }}</h1>
|
||||
<p class="text-muted mb-4">{{ shop.description }}</p>
|
||||
<div class="d-flex flex-wrap gap-3">
|
||||
<div class="d-flex align-items-center bg-light px-3 py-2 rounded-pill">
|
||||
<i class="fa-brands fa-whatsapp text-success me-2"></i>
|
||||
<span class="fw-bold">{{ shop.whatsapp_number }}</span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center bg-light px-3 py-2 rounded-pill">
|
||||
<i class="fa-solid fa-box text-emerald me-2"></i>
|
||||
<span>{{ products.count }} Produk</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-auto mt-4 mt-md-0">
|
||||
<a href="https://wa.me/{{ shop.whatsapp_number }}" target="_blank" class="btn btn-whatsapp rounded-pill px-4 py-2 w-100">
|
||||
<i class="fa-brands fa-whatsapp me-2"></i>Chat Penjual
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Shop Products -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4 class="fw-bold mb-0">Produk dari {{ shop.name }}</h4>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
{% for product in products %}
|
||||
<div class="col-12 col-sm-6 col-lg-3">
|
||||
<div class="product-card">
|
||||
<div class="product-img-wrapper">
|
||||
{% if product.image %}
|
||||
<img src="{{ product.image.url }}" alt="{{ product.name }}" class="img-fluid w-100 h-100 object-fit-cover">
|
||||
{% else %}
|
||||
<i class="fa-solid fa-image"></i>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<small class="text-muted d-block mb-2">{{ product.category.name }}</small>
|
||||
<h5 class="fw-bold mb-3">
|
||||
<a href="{% url 'product_detail' product.slug %}" class="text-decoration-none text-dark">{{ product.name }}</a>
|
||||
</h5>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="product-price">Rp {{ product.price|floatformat:0 }}</span>
|
||||
<a href="{{ product.whatsapp_link }}" target="_blank" class="btn btn-whatsapp btn-sm px-3">
|
||||
<i class="fa-brands fa-whatsapp me-2"></i>Pesan
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="col-12 text-center py-5">
|
||||
<p class="text-muted">Toko ini belum menambahkan produk.</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
47
core/templates/core/shop_list.html
Normal file
47
core/templates/core/shop_list.html
Normal file
@ -0,0 +1,47 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Daftar Toko UMKM - Pemagarsari{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="py-5 bg-light">
|
||||
<div class="container">
|
||||
<div class="text-center mb-5">
|
||||
<h1 class="fw-bold">Para Pelaku UMKM</h1>
|
||||
<p class="text-muted">Dukung usaha warga Desa Pemagarsari dengan berbelanja langsung dari toko mereka.</p>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
{% for shop in shops %}
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="card border-0 rounded-4 shadow-sm h-100 overflow-hidden">
|
||||
<div class="row g-0 h-100">
|
||||
<div class="col-4 bg-emerald d-flex align-items-center justify-content-center text-white">
|
||||
{% if shop.logo %}
|
||||
<img src="{{ shop.logo.url }}" alt="{{ shop.name }}" class="img-fluid w-100 h-100 object-fit-cover">
|
||||
{% else %}
|
||||
<i class="fa-solid fa-shop fa-3x opacity-50"></i>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<div class="card-body p-4">
|
||||
<h5 class="fw-bold mb-2">{{ shop.name }}</h5>
|
||||
<p class="text-muted small mb-3 text-truncate-2">{{ shop.description|default:"Pelaku UMKM Desa Pemagarsari" }}</p>
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<i class="fa-brands fa-whatsapp text-success me-2"></i>
|
||||
<span class="small text-muted">{{ shop.whatsapp_number }}</span>
|
||||
</div>
|
||||
<a href="{% url 'shop_detail' shop.slug %}" class="btn btn-outline-emerald btn-sm rounded-pill px-4">Kunjungi Toko</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="col-12 text-center py-5">
|
||||
<p class="text-muted">Belum ada toko yang terdaftar.</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
25
core/urls.py
25
core/urls.py
@ -1,7 +1,26 @@
|
||||
from django.urls import path
|
||||
|
||||
from .views import home
|
||||
from django.urls import path, include
|
||||
from django.contrib.auth import views as auth_views
|
||||
from .views import (
|
||||
home, product_detail, shop_list, shop_detail,
|
||||
dashboard, shop_add, shop_edit,
|
||||
product_add, product_edit, product_delete
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path("", home, name="home"),
|
||||
path("shops/", shop_list, name="shop_list"),
|
||||
path("shop/<slug:slug>/", shop_detail, name="shop_detail"),
|
||||
path("product/<slug:slug>/", product_detail, name="product_detail"),
|
||||
|
||||
# Dashboard
|
||||
path("dashboard/", dashboard, name="dashboard"),
|
||||
path("dashboard/shop/add/", shop_add, name="shop_add"),
|
||||
path("dashboard/shop/edit/<int:pk>/", shop_edit, name="shop_edit"),
|
||||
path("dashboard/product/add/", product_add, name="product_add"),
|
||||
path("dashboard/product/edit/<int:pk>/", product_edit, name="product_edit"),
|
||||
path("dashboard/product/delete/<int:pk>/", product_delete, name="product_delete"),
|
||||
|
||||
# Auth
|
||||
path("login/", auth_views.LoginView.as_view(template_name="core/auth/login.html"), name="login"),
|
||||
path("logout/", auth_views.LogoutView.as_view(next_page="home"), name="logout"),
|
||||
]
|
||||
|
||||
180
core/views.py
180
core/views.py
@ -2,24 +2,174 @@ import os
|
||||
import platform
|
||||
|
||||
from django import get_version as django_version
|
||||
from django.shortcuts import render
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.utils import timezone
|
||||
|
||||
from .models import Category, Shop, Product
|
||||
|
||||
def home(request):
|
||||
"""Render the landing screen with loader and environment details."""
|
||||
host_name = request.get_host().lower()
|
||||
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic"
|
||||
now = timezone.now()
|
||||
|
||||
"""Render the marketplace landing page with search and filter."""
|
||||
categories = Category.objects.all()
|
||||
|
||||
# Get query parameters
|
||||
query = request.GET.get('q', '')
|
||||
category_slug = request.GET.get('category', '')
|
||||
|
||||
products = Product.objects.filter(is_active=True).order_by('-created_at')
|
||||
|
||||
if query:
|
||||
products = products.filter(name__icontains=query)
|
||||
|
||||
if category_slug:
|
||||
products = products.filter(category__slug=category_slug)
|
||||
|
||||
featured_products = products[:12] # Show more products on home
|
||||
|
||||
context = {
|
||||
"project_name": "New Style",
|
||||
"agent_brand": agent_brand,
|
||||
"django_version": django_version(),
|
||||
"python_version": platform.python_version(),
|
||||
"current_time": now,
|
||||
"host_name": host_name,
|
||||
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
|
||||
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
|
||||
"categories": categories,
|
||||
"featured_products": featured_products,
|
||||
"query": query,
|
||||
"selected_category": category_slug,
|
||||
"current_time": timezone.now(),
|
||||
}
|
||||
return render(request, "core/index.html", context)
|
||||
|
||||
def product_detail(request, slug):
|
||||
"""Render the product detail page."""
|
||||
product = get_object_or_404(Product, slug=slug, is_active=True)
|
||||
return render(request, "core/product_detail.html", {"product": product})
|
||||
|
||||
def shop_list(request):
|
||||
"""Render a list of all shops."""
|
||||
shops = Shop.objects.all().order_by('name')
|
||||
return render(request, "core/shop_list.html", {"shops": shops})
|
||||
|
||||
def shop_detail(request, slug):
|
||||
"""Render a specific shop's page with its products."""
|
||||
shop = get_object_or_404(Shop, slug=slug)
|
||||
products = Product.objects.filter(shop=shop, is_active=True)
|
||||
return render(request, "core/shop_detail.html", {"shop": shop, "products": products})
|
||||
|
||||
@login_required
|
||||
def dashboard(request):
|
||||
"""Seller dashboard showing their shops and products."""
|
||||
shops = Shop.objects.filter(owner=request.user)
|
||||
products = Product.objects.filter(shop__owner=request.user).order_by('-created_at')
|
||||
|
||||
context = {
|
||||
"shops": shops,
|
||||
"products": products,
|
||||
}
|
||||
return render(request, "core/dashboard/index.html", context)
|
||||
|
||||
@login_required
|
||||
def shop_add(request):
|
||||
"""Add a new shop."""
|
||||
if request.method == "POST":
|
||||
name = request.POST.get('name')
|
||||
description = request.POST.get('description')
|
||||
whatsapp_number = request.POST.get('whatsapp_number')
|
||||
logo = request.FILES.get('logo')
|
||||
|
||||
Shop.objects.create(
|
||||
owner=request.user,
|
||||
name=name,
|
||||
description=description,
|
||||
whatsapp_number=whatsapp_number,
|
||||
logo=logo
|
||||
)
|
||||
return redirect('dashboard')
|
||||
|
||||
return render(request, "core/dashboard/shop_form.html", {
|
||||
"title": "Tambah Toko Baru"
|
||||
})
|
||||
|
||||
@login_required
|
||||
def shop_edit(request, pk):
|
||||
"""Edit an existing shop."""
|
||||
shop = get_object_or_404(Shop, pk=pk, owner=request.user)
|
||||
|
||||
if request.method == "POST":
|
||||
shop.name = request.POST.get('name')
|
||||
shop.description = request.POST.get('description')
|
||||
shop.whatsapp_number = request.POST.get('whatsapp_number')
|
||||
if request.FILES.get('logo'):
|
||||
shop.logo = request.FILES.get('logo')
|
||||
|
||||
shop.save()
|
||||
return redirect('dashboard')
|
||||
|
||||
return render(request, "core/dashboard/shop_form.html", {
|
||||
"shop": shop,
|
||||
"title": "Edit Toko"
|
||||
})
|
||||
|
||||
@login_required
|
||||
def product_add(request):
|
||||
"""Add a new product."""
|
||||
shops = Shop.objects.filter(owner=request.user)
|
||||
categories = Category.objects.all()
|
||||
|
||||
if request.method == "POST":
|
||||
shop_id = request.POST.get('shop')
|
||||
category_id = request.POST.get('category')
|
||||
name = request.POST.get('name')
|
||||
description = request.POST.get('description')
|
||||
price = request.POST.get('price')
|
||||
stock = request.POST.get('stock')
|
||||
image = request.FILES.get('image')
|
||||
|
||||
shop = get_object_or_404(Shop, id=shop_id, owner=request.user)
|
||||
category = get_object_or_404(Category, id=category_id)
|
||||
|
||||
Product.objects.create(
|
||||
shop=shop,
|
||||
category=category,
|
||||
name=name,
|
||||
description=description,
|
||||
price=price,
|
||||
stock=stock,
|
||||
image=image
|
||||
)
|
||||
return redirect('dashboard')
|
||||
|
||||
return render(request, "core/dashboard/product_form.html", {
|
||||
"shops": shops,
|
||||
"categories": categories,
|
||||
"title": "Tambah Produk Baru"
|
||||
})
|
||||
|
||||
@login_required
|
||||
def product_edit(request, pk):
|
||||
"""Edit an existing product."""
|
||||
product = get_object_or_404(Product, pk=pk, shop__owner=request.user)
|
||||
shops = Shop.objects.filter(owner=request.user)
|
||||
categories = Category.objects.all()
|
||||
|
||||
if request.method == "POST":
|
||||
product.shop = get_object_or_404(Shop, id=request.POST.get('shop'), owner=request.user)
|
||||
product.category = get_object_or_404(Category, id=request.POST.get('category'))
|
||||
product.name = request.POST.get('name')
|
||||
product.description = request.POST.get('description')
|
||||
product.price = request.POST.get('price')
|
||||
product.stock = request.POST.get('stock')
|
||||
if request.FILES.get('image'):
|
||||
product.image = request.FILES.get('image')
|
||||
|
||||
product.save()
|
||||
return redirect('dashboard')
|
||||
|
||||
return render(request, "core/dashboard/product_form.html", {
|
||||
"product": product,
|
||||
"shops": shops,
|
||||
"categories": categories,
|
||||
"title": "Edit Produk"
|
||||
})
|
||||
|
||||
@login_required
|
||||
def product_delete(request, pk):
|
||||
"""Delete a product."""
|
||||
product = get_object_or_404(Product, pk=pk, shop__owner=request.user)
|
||||
if request.method == "POST":
|
||||
product.delete()
|
||||
return redirect('dashboard')
|
||||
|
||||
@ -1,4 +1,213 @@
|
||||
/* Custom styles for the application */
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
:root {
|
||||
--emerald-primary: #10B981;
|
||||
--emerald-dark: #059669;
|
||||
--forest-deep: #064E3B;
|
||||
--amber-warm: #F59E0B;
|
||||
--glass-bg: rgba(255, 255, 255, 0.8);
|
||||
--soft-shadow: 0 10px 30px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: #f8fafc;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, .navbar-brand {
|
||||
font-family: 'Poppins', sans-serif;
|
||||
}
|
||||
|
||||
.text-emerald {
|
||||
color: var(--emerald-primary);
|
||||
}
|
||||
|
||||
.bg-emerald {
|
||||
background-color: var(--emerald-primary);
|
||||
}
|
||||
|
||||
.btn-emerald {
|
||||
background-color: var(--emerald-primary);
|
||||
border-color: var(--emerald-primary);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-emerald:hover {
|
||||
background-color: var(--emerald-dark);
|
||||
border-color: var(--emerald-dark);
|
||||
color: white;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(16, 185, 129, 0.4);
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: blur(10px);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 1.5rem;
|
||||
color: var(--forest-deep);
|
||||
}
|
||||
|
||||
/* Hero Section */
|
||||
.hero-section {
|
||||
padding: 100px 0;
|
||||
background: linear-gradient(135deg, #ecfdf5 0%, #ffffff 100%);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hero-section::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -10%;
|
||||
right: -5%;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
background: radial-gradient(circle, rgba(16, 185, 129, 0.1) 0%, rgba(255, 255, 255, 0) 70%);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 3.5rem;
|
||||
font-weight: 800;
|
||||
line-height: 1.2;
|
||||
color: var(--forest-deep);
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.category-card {
|
||||
background: white;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: var(--soft-shadow);
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.category-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.category-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: #ecfdf5;
|
||||
color: var(--emerald-primary);
|
||||
border-radius: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 1.5rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.product-card {
|
||||
background: white;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: var(--soft-shadow);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.product-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.product-img-wrapper {
|
||||
height: 220px;
|
||||
background: #f1f5f9;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.product-img-wrapper i {
|
||||
font-size: 4rem;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
color: var(--emerald-primary);
|
||||
font-weight: 700;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.shop-badge {
|
||||
background: #f1f5f9;
|
||||
color: #64748b;
|
||||
padding: 4px 12px;
|
||||
border-radius: 100px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* WhatsApp Button */
|
||||
.btn-whatsapp {
|
||||
background-color: #25D366;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.btn-whatsapp:hover {
|
||||
background-color: #128C7E;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.transform-hover {
|
||||
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.transform-hover:hover {
|
||||
transform: scale(1.02) translateY(-10px) rotate(1deg);
|
||||
box-shadow: 0 25px 50px -12px rgba(16, 185, 129, 0.25) !important;
|
||||
}
|
||||
|
||||
.transition-all {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* Search Box */
|
||||
.search-box {
|
||||
border: 1px solid rgba(0,0,0,0.05);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.search-box:focus-within {
|
||||
border-color: var(--emerald-primary);
|
||||
box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.1) !important;
|
||||
}
|
||||
|
||||
/* Categories Active State */
|
||||
.category-card.active {
|
||||
background: var(--forest-deep);
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.category-card.active .category-icon {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.category-card.active h6 {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.hero-title {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user