Revert to version 65bfca7
This commit is contained in:
parent
6f1ed9a2fa
commit
be406aca40
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -180,5 +180,3 @@ if EMAIL_USE_SSL:
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
MEDIA_URL = "/media/"
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
||||
|
||||
@ -27,4 +27,3 @@ urlpatterns = [
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets")
|
||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,19 +1,3 @@
|
||||
from django.contrib import admin
|
||||
from .models import Category, Item, Outfit, DailySchedule
|
||||
|
||||
@admin.register(Category)
|
||||
class CategoryAdmin(admin.ModelAdmin):
|
||||
list_display = ('name',)
|
||||
|
||||
@admin.register(Item)
|
||||
class ItemAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'item_type', 'category')
|
||||
list_filter = ('item_type', 'category')
|
||||
|
||||
@admin.register(Outfit)
|
||||
class OutfitAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'created_at')
|
||||
|
||||
@admin.register(DailySchedule)
|
||||
class DailyScheduleAdmin(admin.ModelAdmin):
|
||||
list_display = ('date', 'outfit')
|
||||
# Register your models here.
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
from django import forms
|
||||
from .models import Item, Category
|
||||
|
||||
class ItemForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Item
|
||||
fields = ['name', 'category', 'image', 'item_type', 'tags']
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'class': 'form-control bg-dark text-white border-secondary', 'placeholder': 'Item Name'}),
|
||||
'category': forms.Select(attrs={'class': 'form-select bg-dark text-white border-secondary'}),
|
||||
'item_type': forms.Select(attrs={'class': 'form-select bg-dark text-white border-secondary'}),
|
||||
'tags': forms.TextInput(attrs={'class': 'form-control bg-dark text-white border-secondary', 'placeholder': 'Tags (comma-separated)'}),
|
||||
'image': forms.FileInput(attrs={'class': 'form-control bg-dark text-white border-secondary'}),
|
||||
}
|
||||
@ -1,53 +0,0 @@
|
||||
# Generated by Django 5.2.7 on 2026-02-04 02:59
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
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)),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Categories',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Item',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('image', models.ImageField(blank=True, null=True, upload_to='items/')),
|
||||
('item_type', models.CharField(choices=[('wardrobe', 'Wardrobe'), ('accessory', 'Accessory')], default='wardrobe', max_length=20)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.category')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Outfit',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(blank=True, max_length=255)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('items', models.ManyToManyField(related_name='outfits', to='core.item')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DailySchedule',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date', models.DateField(unique=True)),
|
||||
('outfit', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.outfit')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@ -1,27 +0,0 @@
|
||||
# Generated by Django 5.2.7 on 2026-02-04 03:08
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Folder',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='outfit',
|
||||
name='folder',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='outfits', to='core.folder'),
|
||||
),
|
||||
]
|
||||
@ -1,50 +0,0 @@
|
||||
# Generated by Django 5.2.7 on 2026-02-04 03:34
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0002_folder_outfit_folder'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='dailyschedule',
|
||||
name='user',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='schedules', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='folder',
|
||||
name='user',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='folders', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='item',
|
||||
name='tags',
|
||||
field=models.CharField(blank=True, help_text='Comma-separated tags', max_length=255),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='item',
|
||||
name='user',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='items', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='outfit',
|
||||
name='user',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='outfits', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='dailyschedule',
|
||||
name='date',
|
||||
field=models.DateField(),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='dailyschedule',
|
||||
unique_together={('user', 'date')},
|
||||
),
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,58 +1,3 @@
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
class Category(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
# e.g., 'Pants', 'T-shirts', 'Beanies', 'Rings'
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = "Categories"
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Item(models.Model):
|
||||
ITEM_TYPES = (
|
||||
('wardrobe', 'Wardrobe'),
|
||||
('accessory', 'Accessory'),
|
||||
)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='items', null=True, blank=True)
|
||||
name = models.CharField(max_length=255)
|
||||
image = models.ImageField(upload_to='items/', blank=True, null=True)
|
||||
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
item_type = models.CharField(max_length=20, choices=ITEM_TYPES, default='wardrobe')
|
||||
tags = models.CharField(max_length=255, blank=True, help_text="Comma-separated tags")
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Folder(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='folders', null=True, blank=True)
|
||||
name = models.CharField(max_length=100)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Outfit(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='outfits', null=True, blank=True)
|
||||
name = models.CharField(max_length=255, blank=True)
|
||||
items = models.ManyToManyField(Item, related_name='outfits')
|
||||
folder = models.ForeignKey(Folder, on_delete=models.SET_NULL, null=True, blank=True, related_name='outfits')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name or f"Outfit {self.id}"
|
||||
|
||||
class DailySchedule(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='schedules', null=True, blank=True)
|
||||
date = models.DateField()
|
||||
outfit = models.ForeignKey(Outfit, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('user', 'date')
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.date} - {self.outfit}"
|
||||
# Create your models here.
|
||||
|
||||
@ -1,257 +1,25 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}New Style{% endblock %}</title>
|
||||
|
||||
<!-- Google 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@300;400;500;600;700&family=Montserrat:wght@700;800&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Bootstrap 5 -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--bg-color: #050505;
|
||||
--surface: #121212;
|
||||
--surface-accent: #1a1a1a;
|
||||
--border: #252525;
|
||||
--text: #ffffff;
|
||||
--text-muted: #888888;
|
||||
--accent: #00ff88; /* Vibrant green accent */
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text);
|
||||
font-family: 'Inter', sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.main-app {
|
||||
max-width: 450px; /* Mobile focused width */
|
||||
margin: 0 auto;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 15px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Top Bar */
|
||||
.header-bar {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
align-items: center;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.header-center {
|
||||
text-align: center;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 800;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.avatar-circle {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
border-radius: 50%;
|
||||
background: var(--surface-accent);
|
||||
border: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
}
|
||||
.avatar-circle:hover {
|
||||
color: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
/* Category Nav */
|
||||
.category-nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px 0;
|
||||
border-top: 1px solid var(--border);
|
||||
border-bottom: 1px solid var(--border);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.category-nav a {
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.category-nav a:hover, .category-nav a.active {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.nav-divider {
|
||||
width: 1px;
|
||||
height: 15px;
|
||||
background-color: var(--border);
|
||||
}
|
||||
|
||||
/* Forms & Buttons */
|
||||
.btn-custom {
|
||||
background: var(--surface-accent);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text);
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
font-size: 0.8rem;
|
||||
transition: all 0.2s;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-custom:hover {
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.btn-accent {
|
||||
background: var(--accent);
|
||||
color: #000;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
font-weight: 700;
|
||||
font-size: 0.8rem;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.btn-accent:hover {
|
||||
opacity: 0.9;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.line-separator {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: var(--border);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Modal Customization */
|
||||
.modal-content {
|
||||
background-color: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text);
|
||||
}
|
||||
.modal-header {
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.modal-footer {
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
.form-control, .form-select {
|
||||
background-color: var(--bg-color);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text);
|
||||
}
|
||||
.form-control:focus, .form-select:focus {
|
||||
background-color: var(--bg-color);
|
||||
border-color: var(--accent);
|
||||
color: var(--text);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.empty-state i {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 15px;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
{% block extra_css %}{% endblock %}
|
||||
<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 %}
|
||||
{% load static %}
|
||||
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="main-app">
|
||||
<header class="header-bar">
|
||||
<div class="header-left">
|
||||
{% if user.is_authenticated %}
|
||||
<span class="season-icon">
|
||||
<i class="bi bi-person-fill text-muted"></i>
|
||||
<span style="font-size: 0.7rem; margin-left: 5px;">{{ user.username }}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="{% url 'home' %}" style="text-decoration: none; color: inherit;">NEW STYLE</a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
{% if user.is_authenticated %}
|
||||
<form action="{% url 'logout' %}" method="post" style="display:inline;">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="avatar-circle" style="background:none; border:none; cursor:pointer;">
|
||||
<i class="bi bi-box-arrow-right"></i>
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<a href="{% url 'login' %}" class="avatar-circle">
|
||||
<i class="bi bi-person"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
<nav class="category-nav">
|
||||
<a href="{% url 'wardrobe' %}" class="{% if request.resolver_match.url_name == 'wardrobe' %}active{% endif %}">Wardrobe</a>
|
||||
<div class="nav-divider"></div>
|
||||
<a href="{% url 'accessories' %}" class="{% if request.resolver_match.url_name == 'accessories' %}active{% endif %}">Accessories</a>
|
||||
<div class="nav-divider"></div>
|
||||
<a href="{% url 'new_fit' %}" class="{% if request.resolver_match.url_name == 'new_fit' %}active{% endif %}">New Fit</a>
|
||||
<div class="nav-divider"></div>
|
||||
<a href="{% url 'outfits' %}" class="{% if request.resolver_match.url_name == 'outfits' %}active{% endif %}">Outfits</a>
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
{% block extra_js %}{% endblock %}
|
||||
{% block content %}{% endblock %}
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@ -1,146 +1,145 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}Weekly Planner - New Style{% endblock %}
|
||||
{% 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 %}
|
||||
|
||||
{% block content %}
|
||||
<div class="planner-grid">
|
||||
<!-- Row 1: Mon, Tue, Wed -->
|
||||
<div class="planner-row planner-row-3">
|
||||
{% for day in weekly_days|slice:":3" %}
|
||||
<div class="day-box" data-bs-toggle="modal" data-bs-target="#scheduleModal" data-date="{{ day.date|date:'Y-m-d' }}" data-day="{{ day.day_name }}">
|
||||
<div class="outfit-display">
|
||||
{% if day.schedule.outfit %}
|
||||
<img src="{{ day.schedule.outfit.items.first.image.url }}" style="width: 100%; height: 100%; object-fit: cover;">
|
||||
<div style="position: absolute; bottom: 35%; left: 0; width: 100%; background: rgba(0,0,0,0.5); font-size: 0.5rem; padding: 2px;">{{ day.schedule.outfit.name }}</div>
|
||||
{% else %}
|
||||
<span>NO FIT</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="day-footer">
|
||||
<p class="day-label">{{ day.day_name|slice:":3" }}</p>
|
||||
<p class="date-label">{{ day.date|date:"d M" }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<!-- Row 2: Thu, Fri -->
|
||||
<div class="planner-row planner-row-2">
|
||||
{% for day in weekly_days|slice:"3:5" %}
|
||||
<div class="day-box" data-bs-toggle="modal" data-bs-target="#scheduleModal" data-date="{{ day.date|date:'Y-m-d' }}" data-day="{{ day.day_name }}">
|
||||
<div class="outfit-display">
|
||||
{% if day.schedule.outfit %}
|
||||
<img src="{{ day.schedule.outfit.items.first.image.url }}" style="width: 100%; height: 100%; object-fit: cover;">
|
||||
<div style="position: absolute; bottom: 35%; left: 0; width: 100%; background: rgba(0,0,0,0.5); font-size: 0.5rem; padding: 2px;">{{ day.schedule.outfit.name }}</div>
|
||||
{% else %}
|
||||
<span>NO FIT</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="day-footer">
|
||||
<p class="day-label">{{ day.day_name|slice:":3" }}</p>
|
||||
<p class="date-label">{{ day.date|date:"d M" }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Row 3: Sat, Sun -->
|
||||
<div class="planner-row planner-row-2">
|
||||
{% for day in weekly_days|slice:"5:7" %}
|
||||
<div class="day-box" data-bs-toggle="modal" data-bs-target="#scheduleModal" data-date="{{ day.date|date:'Y-m-d' }}" data-day="{{ day.day_name }}">
|
||||
<div class="outfit-display">
|
||||
{% if day.schedule.outfit %}
|
||||
<img src="{{ day.schedule.outfit.items.first.image.url }}" style="width: 100%; height: 100%; object-fit: cover;">
|
||||
<div style="position: absolute; bottom: 35%; left: 0; width: 100%; background: rgba(0,0,0,0.5); font-size: 0.5rem; padding: 2px;">{{ day.schedule.outfit.name }}</div>
|
||||
{% else %}
|
||||
<span>NO FIT</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="day-footer">
|
||||
<p class="day-label">{{ day.day_name|slice:":3" }}</p>
|
||||
<p class="date-label">{{ day.date|date:"d M" }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="line-separator"></div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<i class="bi bi-clock-history text-muted"></i>
|
||||
<span class="small fw-bold">{{ total_items }} ITEMS LOADED</span>
|
||||
</div>
|
||||
<div class="d-flex gap-3">
|
||||
<i class="bi bi-brightness-high {% if current_season == 'Summer' %}text-accent{% else %}text-muted{% endif %}"></i>
|
||||
<i class="bi bi-flower1 {% if current_season == 'Spring' %}text-accent{% else %}text-muted{% endif %}"></i>
|
||||
<i class="bi bi-tree {% if current_season == 'Autumn' %}text-accent{% else %}text-muted{% endif %}"></i>
|
||||
<i class="bi bi-snow {% if current_season == 'Winter' %}text-accent{% else %}text-muted{% endif %}"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Schedule Modal -->
|
||||
<div class="modal fade" id="scheduleModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header border-0">
|
||||
<h5 class="modal-title font-montserrat" id="modalDayTitle">SCHEDULE FIT</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form action="{% url 'schedule_outfit' %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="date" id="modalDateInput">
|
||||
<p class="text-muted small mb-3">Select an outfit for <span id="modalDayName" class="text-white fw-bold"></span></p>
|
||||
|
||||
<div class="list-group bg-transparent">
|
||||
<label class="list-group-item bg-transparent border-secondary text-white d-flex align-items-center gap-3 py-3">
|
||||
<input class="form-check-input bg-dark border-secondary" type="radio" name="outfit_id" value="" checked>
|
||||
<span class="small">CLEAR SCHEDULE</span>
|
||||
</label>
|
||||
{% for outfit in user.outfits.all %}
|
||||
<label class="list-group-item bg-transparent border-secondary text-white d-flex align-items-center gap-3 py-3">
|
||||
<input class="form-check-input bg-dark border-secondary" type="radio" name="outfit_id" value="{{ outfit.id }}">
|
||||
<div class="d-flex flex-column">
|
||||
<span class="fw-bold small">{{ outfit.name|upper }}</span>
|
||||
<span class="text-muted" style="font-size: 0.6rem;">{{ outfit.items.count }} ITEMS</span>
|
||||
</div>
|
||||
</label>
|
||||
{% empty %}
|
||||
<div class="py-4 text-center text-muted">
|
||||
<p class="small mb-2">No outfits saved yet.</p>
|
||||
<a href="{% url 'new_fit' %}" class="btn-custom py-1 px-3">Build One</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-0">
|
||||
<button type="button" class="btn-custom" data-bs-dismiss="modal">CANCEL</button>
|
||||
<button type="submit" class="btn-accent">SCHEDULE</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
const scheduleModal = document.getElementById('scheduleModal');
|
||||
if (scheduleModal) {
|
||||
scheduleModal.addEventListener('show.bs.modal', function (event) {
|
||||
const button = event.relatedTarget;
|
||||
const date = button.getAttribute('data-date');
|
||||
const dayName = button.getAttribute('data-day');
|
||||
|
||||
const modalDateInput = scheduleModal.querySelector('#modalDateInput');
|
||||
const modalDayName = scheduleModal.querySelector('#modalDayName');
|
||||
|
||||
modalDateInput.value = date;
|
||||
modalDayName.textContent = dayName;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
<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 %}
|
||||
@ -1,263 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}New Fit - New Style{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.builder-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
|
||||
.preview-canvas {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
height: 400px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.preview-canvas .placeholder-text {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.preview-canvas img {
|
||||
max-height: 80%;
|
||||
max-width: 80%;
|
||||
object-fit: contain;
|
||||
position: absolute;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.category-tabs {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 10px;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.category-tabs::-webkit-scrollbar { display: none; }
|
||||
|
||||
.tab-item {
|
||||
white-space: nowrap;
|
||||
padding: 5px 15px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid var(--border);
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.tab-item.active {
|
||||
background: var(--accent);
|
||||
color: #000;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.items-selector {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.selector-card {
|
||||
aspect-ratio: 1/1;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
.selector-card.selected {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 10px rgba(0, 255, 136, 0.2);
|
||||
}
|
||||
.selector-card img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.selector-card .selected-badge {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
background: var(--accent);
|
||||
color: #000;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.6rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
.selector-card.selected .selected-badge {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.save-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
background: var(--bg-color);
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
z-index: 1000;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="builder-container">
|
||||
<div class="preview-canvas" id="previewCanvas">
|
||||
<div class="placeholder-text">Outfit Preview</div>
|
||||
<!-- Images will be injected here via JS -->
|
||||
</div>
|
||||
|
||||
<div class="category-tabs">
|
||||
<div class="tab-item active" data-category="all">ALL</div>
|
||||
{% for cat in categories %}
|
||||
<div class="tab-item" data-category="{{ cat.id }}">{{ cat.name|upper }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="items-selector" id="itemsSelector">
|
||||
{% for item in items %}
|
||||
<div class="selector-card" data-id="{{ item.id }}" data-category="{{ item.category.id }}" data-image="{{ item.image.url }}">
|
||||
<img src="{{ item.image.url }}" alt="{{ item.name }}">
|
||||
<div class="selected-badge"><i class="bi bi-check"></i></div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="col-12 text-center text-muted py-5">
|
||||
<p>No items in your wardrobe yet.</p>
|
||||
<a href="{% url 'wardrobe' %}" class="btn-custom">Add Items</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="save-bar">
|
||||
<input type="text" id="outfitName" class="form-control" placeholder="Outfit Name (e.g. Summer Night)">
|
||||
<button class="btn-accent" id="saveOutfitBtn">SAVE FIT</button>
|
||||
</div>
|
||||
|
||||
<!-- Hidden Form for Saving -->
|
||||
<form id="saveOutfitForm" action="{% url 'save_outfit' %}" method="POST" style="display: none;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="name" id="hiddenName">
|
||||
<div id="hiddenItems"></div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
const selectedItems = new Set();
|
||||
const previewCanvas = document.getElementById('previewCanvas');
|
||||
const itemsSelector = document.getElementById('itemsSelector');
|
||||
const categoryTabs = document.querySelectorAll('.tab-item');
|
||||
const saveOutfitBtn = document.getElementById('saveOutfitBtn');
|
||||
const saveOutfitForm = document.getElementById('saveOutfitForm');
|
||||
|
||||
// Category Filtering
|
||||
categoryTabs.forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
categoryTabs.forEach(t => t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
|
||||
const category = tab.dataset.category;
|
||||
const cards = itemsSelector.querySelectorAll('.selector-card');
|
||||
|
||||
cards.forEach(card => {
|
||||
if (category === 'all' || card.dataset.category === category) {
|
||||
card.style.display = 'block';
|
||||
} else {
|
||||
card.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Item Selection
|
||||
itemsSelector.querySelectorAll('.selector-card').forEach(card => {
|
||||
card.addEventListener('click', () => {
|
||||
const id = card.dataset.id;
|
||||
const imageUrl = card.dataset.image;
|
||||
|
||||
if (selectedItems.has(id)) {
|
||||
selectedItems.delete(id);
|
||||
card.classList.remove('selected');
|
||||
removePreview(id);
|
||||
} else {
|
||||
selectedItems.add(id);
|
||||
card.classList.add('selected');
|
||||
addPreview(id, imageUrl);
|
||||
}
|
||||
|
||||
updatePlaceholder();
|
||||
});
|
||||
});
|
||||
|
||||
function addPreview(id, url) {
|
||||
const img = document.createElement('img');
|
||||
img.src = url;
|
||||
img.dataset.id = id;
|
||||
img.style.zIndex = selectedItems.size;
|
||||
// Basic stack logic - could be improved
|
||||
previewCanvas.appendChild(img);
|
||||
}
|
||||
|
||||
function removePreview(id) {
|
||||
const img = previewCanvas.querySelector(`img[data-id="${id}"]`);
|
||||
if (img) img.remove();
|
||||
}
|
||||
|
||||
function updatePlaceholder() {
|
||||
const placeholder = previewCanvas.querySelector('.placeholder-text');
|
||||
if (selectedItems.size > 0) {
|
||||
placeholder.style.display = 'none';
|
||||
} else {
|
||||
placeholder.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// Save Logic
|
||||
saveOutfitBtn.addEventListener('click', () => {
|
||||
if (selectedItems.size === 0) {
|
||||
alert('Please select at least one item.');
|
||||
return;
|
||||
}
|
||||
|
||||
const name = document.getElementById('outfitName').value || 'Untitled Outfit';
|
||||
document.getElementById('hiddenName').value = name;
|
||||
|
||||
const hiddenItemsDiv = document.getElementById('hiddenItems');
|
||||
hiddenItemsDiv.innerHTML = '';
|
||||
selectedItems.forEach(id => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'items';
|
||||
input.value = id;
|
||||
hiddenItemsDiv.appendChild(input);
|
||||
});
|
||||
|
||||
saveOutfitForm.submit();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -1,158 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}My Outfits - New Style{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.folder-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.folder-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s;
|
||||
text-decoration: none;
|
||||
color: var(--text);
|
||||
}
|
||||
.folder-card:hover {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.folder-card i {
|
||||
font-size: 2rem;
|
||||
color: var(--accent);
|
||||
}
|
||||
.folder-card .folder-name {
|
||||
font-weight: 700;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.folder-card .item-count {
|
||||
font-size: 0.6rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.outfit-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 15px;
|
||||
}
|
||||
.outfit-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.outfit-preview {
|
||||
aspect-ratio: 1/1.2;
|
||||
background: #1a1a1a;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.outfit-preview img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.outfit-info {
|
||||
padding: 10px;
|
||||
background: rgba(0,0,0,0.8);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.outfit-name {
|
||||
font-weight: 700;
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="font-montserrat m-0" style="font-size: 1.2rem;">COLLECTIONS</h2>
|
||||
<button class="btn-custom py-1" data-bs-toggle="modal" data-bs-target="#folderModal">
|
||||
<i class="bi bi-folder-plus"></i> NEW
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="folder-grid">
|
||||
{% for folder in folders %}
|
||||
<div class="folder-card">
|
||||
<i class="bi bi-folder2-open"></i>
|
||||
<span class="folder-name">{{ folder.name }}</span>
|
||||
<span class="item-count">{{ folder.outfits.count }} OUTFITS</span>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="col-12 text-center text-muted py-3 small">No folders created yet.</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="line-separator"></div>
|
||||
|
||||
<h2 class="font-montserrat mb-4" style="font-size: 1.2rem;">UNCATEGORIZED FITS</h2>
|
||||
|
||||
<div class="outfit-grid">
|
||||
{% for outfit in outfits %}
|
||||
<div class="outfit-card">
|
||||
<div class="outfit-preview">
|
||||
{% if outfit.items.exists %}
|
||||
<img src="{{ outfit.items.first.image.url }}" alt="{{ outfit.name }}">
|
||||
{% else %}
|
||||
<i class="bi bi-images text-muted"></i>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="outfit-info">
|
||||
<p class="outfit-name">{{ outfit.name|default:"UNTITLED" }}</p>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="text-muted" style="font-size: 0.6rem;">{{ outfit.items.count }} ITEMS</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="col-12 text-center text-muted py-5">
|
||||
<p class="small">No outfits found.</p>
|
||||
<a href="{% url 'new_fit' %}" class="btn-accent">CREATE NEW FIT</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Folder Modal -->
|
||||
<div class="modal fade" id="folderModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header border-0">
|
||||
<h5 class="modal-title font-montserrat">NEW COLLECTION</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form action="{% url 'create_folder' %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-muted small">Collection Name</label>
|
||||
<input type="text" name="name" class="form-control" placeholder="e.g. Winter Essentials" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-0">
|
||||
<button type="button" class="btn-custom" data-bs-dismiss="modal">CANCEL</button>
|
||||
<button type="submit" class="btn-accent">CREATE</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -1,164 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ title }} - New Style{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.wardrobe-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 15px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
.item-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.item-image {
|
||||
aspect-ratio: 1/1.2;
|
||||
overflow: hidden;
|
||||
}
|
||||
.item-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.item-overlay {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.item-card:hover .item-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
.item-details {
|
||||
padding: 10px;
|
||||
background: rgba(0,0,0,0.8);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.item-name {
|
||||
font-weight: 700;
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.item-category {
|
||||
font-size: 0.6rem;
|
||||
color: var(--accent);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.tag-badge {
|
||||
font-size: 0.5rem;
|
||||
background: var(--surface-accent);
|
||||
border: 1px solid var(--border);
|
||||
padding: 1px 5px;
|
||||
border-radius: 10px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="font-montserrat m-0" style="font-size: 1.2rem;">{{ title|upper }}</h2>
|
||||
<button class="btn-accent py-1 px-3" data-bs-toggle="modal" data-bs-target="#addItemModal">
|
||||
<i class="bi bi-plus-lg"></i> ADD
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="wardrobe-grid">
|
||||
{% for item in items %}
|
||||
<div class="item-card">
|
||||
<div class="item-image">
|
||||
{% if item.image %}
|
||||
<img src="{{ item.image.url }}" alt="{{ item.name }}">
|
||||
{% else %}
|
||||
<div class="w-100 h-100 d-flex align-items-center justify-content-center bg-dark text-muted">
|
||||
<i class="bi bi-image"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="item-overlay">
|
||||
<form action="{% url 'delete_item' item.pk %}" method="POST" onsubmit="return confirm('Delete this item?');">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-sm btn-danger p-1" style="border-radius: 50%; width: 25px; height: 25px; font-size: 0.7rem;">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="item-details">
|
||||
<p class="item-name">{{ item.name }}</p>
|
||||
<div class="d-flex justify-content-between align-items-end">
|
||||
<span class="item-category">{{ item.category.name|default:"General" }}</span>
|
||||
{% if item.tags %}
|
||||
<div class="d-flex gap-1 overflow-hidden">
|
||||
{% for tag in item.tags.split|slice:":2" %}
|
||||
<span class="tag-badge">{{ tag }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="col-12 empty-state">
|
||||
<i class="bi bi-clouds"></i>
|
||||
<p>Your {{ title|lower }} is empty.</p>
|
||||
<button class="btn-custom mt-2" data-bs-toggle="modal" data-bs-target="#addItemModal">ADD FIRST ITEM</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Add Item Modal -->
|
||||
<div class="modal fade" id="addItemModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header border-0">
|
||||
<h5 class="modal-title font-montserrat">NEW ITEM</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form action="{% url 'add_item' %}" method="POST" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-muted small">Name</label>
|
||||
{{ form.name }}
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<label class="form-label text-muted small">Category</label>
|
||||
{{ form.category }}
|
||||
</div>
|
||||
<div class="col">
|
||||
<label class="form-label text-muted small">Type</label>
|
||||
{{ form.item_type }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-muted small">Tags (comma-separated)</label>
|
||||
{{ form.tags }}
|
||||
</div>
|
||||
<div class="mb-0">
|
||||
<label class="form-label text-muted small">Photo</label>
|
||||
{{ form.image }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-0">
|
||||
<button type="button" class="btn-custom" data-bs-dismiss="modal">CANCEL</button>
|
||||
<button type="submit" class="btn-accent">UPLOAD</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -1,29 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Login - New Style{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex flex-column justify-content-center align-items-center" style="min-height: 60vh;">
|
||||
<h2 class="font-montserrat mb-4">LOGIN</h2>
|
||||
<div class="card bg-dark border-secondary w-100" style="max-width: 350px;">
|
||||
<div class="card-body p-4">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-muted small">Username</label>
|
||||
<input type="text" name="username" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="form-label text-muted small">Password</label>
|
||||
<input type="password" name="password" class="form-control" required>
|
||||
</div>
|
||||
<button type="submit" class="btn-accent w-100 mb-3">ENTER</button>
|
||||
</form>
|
||||
<div class="text-center">
|
||||
<p class="small text-muted mb-0">Don't have an account?</p>
|
||||
<a href="{% url 'signup' %}" class="text-accent text-decoration-none small">SIGN UP</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -1,41 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Sign Up - New Style{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex flex-column justify-content-center align-items-center" style="min-height: 60vh;">
|
||||
<h2 class="font-montserrat mb-4">SIGN UP</h2>
|
||||
<div class="card bg-dark border-secondary w-100" style="max-width: 350px;">
|
||||
<div class="card-body p-4">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% for field in form %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-muted small">{{ field.label }}</label>
|
||||
{{ field }}
|
||||
{% if field.help_text %}
|
||||
<div class="form-text text-muted" style="font-size: 0.6rem;">{{ field.help_text }}</div>
|
||||
{% endif %}
|
||||
{% for error in field.errors %}
|
||||
<div class="text-danger small">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<button type="submit" class="btn-accent w-100 mb-3">CREATE ACCOUNT</button>
|
||||
</form>
|
||||
<div class="text-center">
|
||||
<p class="small text-muted mb-0">Already have an account?</p>
|
||||
<a href="{% url 'login' %}" class="text-accent text-decoration-none small">LOG IN</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.card input {
|
||||
background-color: var(--bg-color) !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
color: var(--text) !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
24
core/urls.py
24
core/urls.py
@ -1,21 +1,7 @@
|
||||
from django.urls import path, include
|
||||
from django.contrib.auth import views as auth_views
|
||||
from . import views
|
||||
from django.urls import path
|
||||
|
||||
from .views import home
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.home, name='home'),
|
||||
path('new-fit/', views.new_fit, name='new_fit'),
|
||||
path('outfits/', views.outfits, name='outfits'),
|
||||
path('wardrobe/', views.wardrobe, name='wardrobe'),
|
||||
path('accessories/', views.accessories, name='accessories'),
|
||||
path('add-item/', views.add_item, name='add_item'),
|
||||
path('delete-item/<int:pk>/', views.delete_item, name='delete_item'),
|
||||
path('save-outfit/', views.save_outfit, name='save_outfit'),
|
||||
path('schedule-outfit/', views.schedule_outfit, name='schedule_outfit'),
|
||||
path('create-folder/', views.create_folder, name='create_folder'),
|
||||
|
||||
# Auth
|
||||
path('accounts/login/', auth_views.LoginView.as_view(template_name='registration/login.html'), name='login'),
|
||||
path('accounts/logout/', auth_views.LogoutView.as_view(next_page='login'), name='logout'),
|
||||
path('accounts/signup/', views.signup, name='signup'),
|
||||
]
|
||||
path("", home, name="home"),
|
||||
]
|
||||
|
||||
172
core/views.py
172
core/views.py
@ -1,167 +1,25 @@
|
||||
import os
|
||||
from datetime import date, timedelta
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.forms import UserCreationForm
|
||||
from django.contrib.auth import login
|
||||
from django.db.models import Q
|
||||
from .models import Item, Outfit, DailySchedule, Category, Folder
|
||||
from .forms import ItemForm
|
||||
import platform
|
||||
|
||||
def signup(request):
|
||||
if request.method == 'POST':
|
||||
form = UserCreationForm(request.POST)
|
||||
if form.is_valid():
|
||||
user = form.save()
|
||||
login(request, user)
|
||||
return redirect('home')
|
||||
else:
|
||||
form = UserCreationForm()
|
||||
return render(request, 'registration/signup.html', {'form': form})
|
||||
from django import get_version as django_version
|
||||
from django.shortcuts import render
|
||||
from django.utils import timezone
|
||||
|
||||
def get_weekly_schedule(user):
|
||||
today = date.today()
|
||||
start_of_week = today - timedelta(days=today.weekday())
|
||||
days = []
|
||||
for i in range(7):
|
||||
day_date = start_of_week + timedelta(days=i)
|
||||
schedule, _ = DailySchedule.objects.get_or_create(user=user, date=day_date)
|
||||
days.append({
|
||||
'date': day_date,
|
||||
'day_name': day_date.strftime('%A'),
|
||||
'schedule': schedule
|
||||
})
|
||||
return days
|
||||
|
||||
@login_required
|
||||
def home(request):
|
||||
"""Render the landing screen with the weekly planner grid."""
|
||||
weekly_days = get_weekly_schedule(request.user)
|
||||
total_items = Item.objects.filter(user=request.user).count()
|
||||
|
||||
month = date.today().month
|
||||
if month in [12, 1, 2]:
|
||||
season = "Winter"
|
||||
elif month in [3, 4, 5]:
|
||||
season = "Spring"
|
||||
elif month in [6, 7, 8]:
|
||||
season = "Summer"
|
||||
else:
|
||||
season = "Autumn"
|
||||
"""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()
|
||||
|
||||
context = {
|
||||
"weekly_days": weekly_days,
|
||||
"total_items": total_items,
|
||||
"current_season": season,
|
||||
"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", ""),
|
||||
}
|
||||
return render(request, "core/index.html", context)
|
||||
|
||||
@login_required
|
||||
def new_fit(request):
|
||||
"""View to create a new outfit on a model."""
|
||||
items = Item.objects.filter(user=request.user)
|
||||
categories = Category.objects.all()
|
||||
context = {
|
||||
"title": "New Fit",
|
||||
"items": items,
|
||||
"categories": categories,
|
||||
"total_items": items.count(),
|
||||
}
|
||||
return render(request, "core/new_fit.html", context)
|
||||
|
||||
@login_required
|
||||
def outfits(request):
|
||||
"""View all saved outfits and folders."""
|
||||
outfits = Outfit.objects.filter(user=request.user, folder__isnull=True)
|
||||
folders = Folder.objects.filter(user=request.user)
|
||||
context = {
|
||||
"outfits": outfits,
|
||||
"folders": folders,
|
||||
"title": "Outfits",
|
||||
"total_items": Item.objects.filter(user=request.user).count(),
|
||||
}
|
||||
return render(request, "core/outfits.html", context)
|
||||
|
||||
@login_required
|
||||
def wardrobe(request):
|
||||
"""Render the wardrobe items."""
|
||||
items = Item.objects.filter(user=request.user, item_type='wardrobe')
|
||||
categories = Category.objects.all()
|
||||
form = ItemForm(initial={'item_type': 'wardrobe'})
|
||||
context = {
|
||||
"items": items,
|
||||
"categories": categories,
|
||||
"title": "Wardrobe",
|
||||
"total_items": Item.objects.filter(user=request.user).count(),
|
||||
"form": form,
|
||||
}
|
||||
return render(request, "core/wardrobe.html", context)
|
||||
|
||||
@login_required
|
||||
def accessories(request):
|
||||
"""Render the accessories items."""
|
||||
items = Item.objects.filter(user=request.user, item_type='accessory')
|
||||
categories = Category.objects.all()
|
||||
form = ItemForm(initial={'item_type': 'accessory'})
|
||||
context = {
|
||||
"items": items,
|
||||
"categories": categories,
|
||||
"title": "Accessories",
|
||||
"total_items": Item.objects.filter(user=request.user).count(),
|
||||
"form": form,
|
||||
}
|
||||
return render(request, "core/wardrobe.html", context)
|
||||
|
||||
@login_required
|
||||
def add_item(request):
|
||||
"""Handle item creation."""
|
||||
if request.method == 'POST':
|
||||
form = ItemForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
item = form.save(commit=False)
|
||||
item.user = request.user
|
||||
item.save()
|
||||
if item.item_type == 'accessory':
|
||||
return redirect('accessories')
|
||||
return redirect('wardrobe')
|
||||
return redirect('wardrobe')
|
||||
|
||||
@login_required
|
||||
def delete_item(request, pk):
|
||||
item = get_object_or_404(Item, pk=pk, user=request.user)
|
||||
item.delete()
|
||||
return redirect(request.META.get('HTTP_REFERER', 'wardrobe'))
|
||||
|
||||
@login_required
|
||||
def save_outfit(request):
|
||||
if request.method == 'POST':
|
||||
name = request.POST.get('name', 'Untitled Outfit')
|
||||
item_ids = request.POST.getlist('items')
|
||||
outfit = Outfit.objects.create(user=request.user, name=name)
|
||||
if item_ids:
|
||||
outfit.items.add(*item_ids)
|
||||
return redirect('outfits')
|
||||
return redirect('new_fit')
|
||||
|
||||
@login_required
|
||||
def schedule_outfit(request):
|
||||
if request.method == 'POST':
|
||||
day_date = request.POST.get('date')
|
||||
outfit_id = request.POST.get('outfit_id')
|
||||
schedule = get_object_or_404(DailySchedule, user=request.user, date=day_date)
|
||||
if outfit_id:
|
||||
outfit = get_object_or_404(Outfit, pk=outfit_id, user=request.user)
|
||||
schedule.outfit = outfit
|
||||
else:
|
||||
schedule.outfit = None
|
||||
schedule.save()
|
||||
return redirect('home')
|
||||
|
||||
@login_required
|
||||
def create_folder(request):
|
||||
if request.method == 'POST':
|
||||
name = request.POST.get('name')
|
||||
if name:
|
||||
Folder.objects.create(user=request.user, name=name)
|
||||
return redirect('outfits')
|
||||
Loading…
x
Reference in New Issue
Block a user