diff --git a/.gitignore b/.gitignore index fffbc02..c712afb 100644 --- a/.gitignore +++ b/.gitignore @@ -52,7 +52,7 @@ media/ staticfiles/ staticfiles/ static_root/ -/static/ +# /static/ # Testing .coverage diff --git a/src/api/base/urls.py b/src/api/base/urls.py index 97b69b9..91307df 100644 --- a/src/api/base/urls.py +++ b/src/api/base/urls.py @@ -1,5 +1,9 @@ from django.urls import path -from .views import home +from src.api.base import views -urlpatterns = [path("", home, name="home")] +urlpatterns = [ + path("", views.dashboard, name="dashboard"), + path("login/", views.login_view, name="login"), + path("logout/", views.logout_view, name="logout"), +] diff --git a/src/api/base/views.py b/src/api/base/views.py index 95b1bcb..792d066 100644 --- a/src/api/base/views.py +++ b/src/api/base/views.py @@ -1,5 +1,105 @@ -from django.shortcuts import render +from datetime import timedelta + +from django.contrib import messages +from django.contrib.auth import authenticate +from django.contrib.auth import login as auth_login +from django.contrib.auth import logout as auth_logout +from django.contrib.auth.decorators import login_required, user_passes_test +from django.db.models import Sum +from django.shortcuts import redirect, render +from django.utils import timezone + +from src.models.clients import Client +from src.models.finance import Expense, Invoice +from src.models.projects import Project, Task +from src.models.services import Service -def home(request): +def is_superuser(user): + return user.is_superuser + + +def login_view(request): + if request.user.is_authenticated: + return redirect("dashboard") + + if request.method == "POST": + username = request.POST.get("username") + password = request.POST.get("password") + user = authenticate(request, username=username, password=password) + + if user is not None and user.is_superuser: + auth_login(request, user) + return redirect("dashboard") + else: + messages.error( + request, + "Invalid credentials or insufficient permissions. Superuser access required.", + ) return render(request, "base/login.html") + + +def logout_view(request): + auth_logout(request) + messages.success(request, "You have been logged out successfully") + return redirect("login") + + +@login_required(login_url="login") +@user_passes_test(is_superuser, login_url="login") +def dashboard(request): + # Date ranges + today = timezone.now().date() + week_ago = today - timedelta(days=7) + month_ago = today - timedelta(days=30) + + # Core metrics + total_clients = Client.objects.count() + active_projects = Project.objects.filter(status="In_PROGRESS").count() + pending_tasks = Task.objects.filter(status="TODO").count() + + # Financial metrics + total_income = ( + Invoice.objects.filter(status="PAID").aggregate(Sum("amount"))["amount__sum"] + or 0 + ) + pending_income = ( + Invoice.objects.filter(status="SENT").aggregate(Sum("amount"))["amount__sum"] + or 0 + ) + monthly_expenses = ( + Expense.objects.filter(date__gte=month_ago).aggregate(Sum("amount"))[ + "amount__sum" + ] + or 0 + ) + overdue_invoices = Invoice.objects.filter(status="OVERDUE").count() + + # Recent Activity + recent_projects = Project.objects.select_related("client").order_by("-updated_at")[ + :5 + ] + recent_tasks = Task.objects.select_related("project").order_by("-updated_at")[:5] + upcoming_deadlines = Project.objects.filter( + deadline__gte=today, deadline__lte=today + timedelta(days=7) + ).order_by("deadline")[:5] + + # Services expiring soon + expiring_services = Service.objects.filter( + expiry_date__gte=today, expiry_date__lte=today + timedelta(days=30) + ).order_by("expiry_date")[:5] + + context = { + "total_clients": total_clients, + "active_projects": active_projects, + "pending_tasks": pending_tasks, + "total_income": total_income, + "pending_income": pending_income, + "monthly_expenses": monthly_expenses, + "overdue_invoices": overdue_invoices, + "recent_projects": recent_projects, + "recent_tasks": recent_tasks, + "upcoming_deadlines": upcoming_deadlines, + "expiring_services": expiring_services, + } + return render(request, "base/dashboard.html", context) diff --git a/static/css/tailwind.css b/static/css/tailwind.css index dd6bb95..c60b6dd 100644 --- a/static/css/tailwind.css +++ b/static/css/tailwind.css @@ -7,10 +7,44 @@ 'Noto Color Emoji'; --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; + --color-indigo-300: oklch(78.5% 0.115 274.713); + --color-indigo-400: oklch(67.3% 0.182 276.935); + --color-indigo-500: oklch(58.5% 0.233 277.117); + --color-gray-100: oklch(96.7% 0.003 264.542); + --color-gray-400: oklch(70.7% 0.022 261.325); + --color-gray-500: oklch(55.1% 0.027 264.364); --color-black: #000; + --color-white: #fff; --spacing: 0.25rem; + --breakpoint-xl: 80rem; --container-sm: 24rem; + --container-md: 28rem; + --container-7xl: 80rem; + --text-xs: 0.75rem; + --text-xs--line-height: calc(1 / 0.75); + --text-sm: 0.875rem; + --text-sm--line-height: calc(1.25 / 0.875); + --text-base: 1rem; + --text-base--line-height: calc(1.5 / 1); + --text-xl: 1.25rem; + --text-xl--line-height: calc(1.75 / 1.25); + --text-2xl: 1.5rem; + --text-2xl--line-height: calc(2 / 1.5); + --text-3xl: 1.875rem; + --text-3xl--line-height: calc(2.25 / 1.875); + --font-weight-normal: 400; + --font-weight-medium: 500; + --font-weight-semibold: 600; + --font-weight-bold: 700; + --tracking-tight: -0.025em; + --tracking-wide: 0.025em; + --radius-sm: 0.25rem; --radius-md: 0.375rem; + --radius-lg: 0.5rem; + --ease-out: cubic-bezier(0, 0, 0.2, 1); + --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); + --default-transition-duration: 150ms; + --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); --default-font-family: var(--font-sans); --default-mono-font-family: var(--font-mono); } @@ -164,6 +198,314 @@ } } @layer utilities { + .menu { + @layer daisyui.l1.l2.l3 { + display: flex; + width: fit-content; + flex-direction: column; + flex-wrap: wrap; + padding: calc(0.25rem * 2); + --menu-active-fg: var(--color-neutral-content); + --menu-active-bg: var(--color-neutral); + font-size: 0.875rem; + :where(li ul) { + position: relative; + margin-inline-start: calc(0.25rem * 4); + padding-inline-start: calc(0.25rem * 2); + white-space: nowrap; + &:before { + position: absolute; + inset-inline-start: calc(0.25rem * 0); + top: calc(0.25rem * 3); + bottom: calc(0.25rem * 3); + background-color: var(--color-base-content); + opacity: 10%; + width: var(--border); + content: ""; + } + } + :where(li > .menu-dropdown:not(.menu-dropdown-show)) { + display: none; + } + :where(li:not(.menu-title) > *:not(ul, details, .menu-title, .btn)), :where(li:not(.menu-title) > details > summary:not(.menu-title)) { + display: grid; + grid-auto-flow: column; + align-content: flex-start; + align-items: center; + gap: calc(0.25rem * 2); + border-radius: var(--radius-field); + padding-inline: calc(0.25rem * 3); + padding-block: calc(0.25rem * 1.5); + text-align: start; + transition-property: color, background-color, box-shadow; + transition-duration: 0.2s; + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + grid-auto-columns: minmax(auto, max-content) auto max-content; + text-wrap: balance; + user-select: none; + } + :where(li > details > summary) { + --tw-outline-style: none; + outline-style: none; + @media (forced-colors: active) { + outline: 2px solid transparent; + outline-offset: 2px; + } + &::-webkit-details-marker { + display: none; + } + } + :where(li > details > summary), :where(li > .menu-dropdown-toggle) { + &:after { + justify-self: flex-end; + display: block; + height: 0.375rem; + width: 0.375rem; + rotate: -135deg; + translate: 0 -1px; + transition-property: rotate, translate; + transition-duration: 0.2s; + content: ""; + transform-origin: 50% 50%; + box-shadow: 2px 2px inset; + pointer-events: none; + } + } + details { + overflow: hidden; + interpolate-size: allow-keywords; + } + details::details-content { + block-size: 0; + @media (prefers-reduced-motion: no-preference) { + transition-behavior: allow-discrete; + transition-property: block-size, content-visibility; + transition-duration: 0.2s; + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + } + } + details[open]::details-content { + block-size: auto; + } + :where(li > details[open] > summary):after, :where(li > .menu-dropdown-toggle.menu-dropdown-show):after { + rotate: 45deg; + translate: 0 1px; + } + :where( li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title), li:not(.menu-title, .disabled) > details > summary:not(.menu-title) ):not(.menu-active, :active, .btn) { + &.menu-focus, &:focus-visible { + cursor: pointer; + background-color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-base-content) 10%, transparent); + } + color: var(--color-base-content); + --tw-outline-style: none; + outline-style: none; + @media (forced-colors: active) { + outline: 2px solid transparent; + outline-offset: 2px; + } + } + } + :where( li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title):not(.menu-active, :active, .btn):hover, li:not(.menu-title, .disabled) > details > summary:not(.menu-title):not(.menu-active, :active, .btn):hover ) { + cursor: pointer; + background-color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-base-content) 10%, transparent); + } + --tw-outline-style: none; + outline-style: none; + @media (forced-colors: active) { + outline: 2px solid transparent; + outline-offset: 2px; + } + box-shadow: 0 1px oklch(0% 0 0 / 0.01) inset, 0 -1px oklch(100% 0 0 / 0.01) inset; + } + :where(li:empty) { + background-color: var(--color-base-content); + opacity: 10%; + margin: 0.5rem 1rem; + height: 1px; + } + :where(li) { + position: relative; + display: flex; + flex-shrink: 0; + flex-direction: column; + flex-wrap: wrap; + align-items: stretch; + .badge { + justify-self: flex-end; + } + & > *:not(ul, .menu-title, details, .btn):active, & > *:not(ul, .menu-title, details, .btn).menu-active, & > details > summary:active { + --tw-outline-style: none; + outline-style: none; + @media (forced-colors: active) { + outline: 2px solid transparent; + outline-offset: 2px; + } + color: var(--menu-active-fg); + background-color: var(--menu-active-bg); + background-size: auto, calc(var(--noise) * 100%); + background-image: none, var(--fx-noise); + &:not(&:active) { + box-shadow: 0 2px calc(var(--depth) * 3px) -2px var(--menu-active-bg); + } + } + &.menu-disabled { + pointer-events: none; + color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, var(--color-base-content) 20%, transparent); + } + } + } + .dropdown:focus-within { + .menu-dropdown-toggle:after { + rotate: 45deg; + translate: 0 1px; + } + } + .dropdown-content { + margin-top: calc(0.25rem * 2); + padding: calc(0.25rem * 2); + &:before { + display: none; + } + } + } + } + .collapse-arrow { + @layer daisyui.l1.l2 { + > .collapse-title:after { + position: absolute; + display: block; + height: 0.5rem; + width: 0.5rem; + transform: translateY(-100%) rotate(45deg); + @media (prefers-reduced-motion: no-preference) { + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 0.2s; + } + top: 50%; + inset-inline-end: 1.4rem; + content: ""; + transform-origin: 75% 75%; + box-shadow: 2px 2px; + pointer-events: none; + } + } + } + .collapse-plus { + @layer daisyui.l1.l2 { + > .collapse-title:after { + position: absolute; + display: block; + height: 0.5rem; + width: 0.5rem; + @media (prefers-reduced-motion: no-preference) { + transition-property: all; + transition-duration: 300ms; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + } + top: 0.9rem; + inset-inline-end: 1.4rem; + --tw-content: "+"; + content: var(--tw-content); + pointer-events: none; + } + } + } + .dropdown { + @layer daisyui.l1.l2.l3 { + position: relative; + display: inline-block; + position-area: var(--anchor-v, bottom) var(--anchor-h, span-right); + & > *:not(:has(~ [class*="dropdown-content"])):focus { + --tw-outline-style: none; + outline-style: none; + @media (forced-colors: active) { + outline: 2px solid transparent; + outline-offset: 2px; + } + } + .dropdown-content { + position: absolute; + } + &.dropdown-close .dropdown-content, &:not(details, .dropdown-open, .dropdown-hover:hover, :focus-within) .dropdown-content, &.dropdown-hover:not(:hover) [tabindex]:first-child:focus:not(:focus-visible) ~ .dropdown-content { + display: none; + transform-origin: top; + opacity: 0%; + scale: 95%; + } + &[popover], .dropdown-content { + z-index: 999; + @media (prefers-reduced-motion: no-preference) { + animation: dropdown 0.2s; + transition-property: opacity, scale, display; + transition-behavior: allow-discrete; + transition-duration: 0.2s; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + } + } + @starting-style { + &[popover], .dropdown-content { + scale: 95%; + opacity: 0; + } + } + &:not(.dropdown-close) { + &.dropdown-open, &:not(.dropdown-hover):focus, &:focus-within { + > [tabindex]:first-child { + pointer-events: none; + } + .dropdown-content { + opacity: 100%; + scale: 100%; + } + } + &.dropdown-hover:hover { + .dropdown-content { + opacity: 100%; + scale: 100%; + } + } + } + &:is(details) { + summary { + &::-webkit-details-marker { + display: none; + } + } + } + &:where([popover]) { + background: #0000; + } + &[popover] { + position: fixed; + color: inherit; + @supports not (position-area: bottom) { + margin: auto; + &.dropdown-close, &.dropdown-open:not(:popover-open) { + display: none; + transform-origin: top; + opacity: 0%; + scale: 95%; + } + &::backdrop { + background-color: color-mix(in oklab, #000 30%, #0000); + } + } + &.dropdown-close, &:not(.dropdown-open, :popover-open) { + display: none; + transform-origin: top; + opacity: 0%; + scale: 95%; + } + } + } + } .btn { :where(&) { @layer daisyui.l1.l2.l3 { @@ -441,6 +783,40 @@ } } } + .collapse-content { + @layer daisyui.l1.l2.l3 { + grid-column-start: 1; + grid-row-start: 1; + } + @layer daisyui.l1.l2.l3 { + content-visibility: hidden; + grid-column-start: 1; + grid-row-start: 2; + min-height: 0; + padding-left: 1rem; + padding-right: 1rem; + cursor: unset; + @supports not (content-visibility: hidden) { + visibility: hidden; + } + @media (prefers-reduced-motion: no-preference) { + transition: content-visibility 0.2s allow-discrete, visibility 0.2s allow-discrete, min-height 0.2s ease-out allow-discrete, padding 0.1s ease-out 20ms, background-color 0.2s ease-out; + } + } + } + .collapse-open { + @layer daisyui.l1.l2 { + grid-template-rows: max-content 1fr; + > .collapse-content { + content-visibility: visible; + min-height: fit-content; + padding-bottom: 1rem; + @supports not (content-visibility: visible) { + visibility: visible; + } + } + } + } .collapse { visibility: collapse; } @@ -503,6 +879,27 @@ } } } + .toast { + @layer daisyui.l1.l2.l3 { + position: fixed; + inset-inline-start: auto; + inset-inline-end: calc(0.25rem * 4); + top: auto; + bottom: calc(0.25rem * 4); + display: flex; + flex-direction: column; + gap: calc(0.25rem * 2); + background-color: transparent; + translate: var(--toast-x, 0) var(--toast-y, 0); + width: max-content; + max-width: calc(100vw - 2rem); + & > * { + @media (prefers-reduced-motion: no-preference) { + animation: toast 0.25s ease-out; + } + } + } + } .input { @layer daisyui.l1.l2.l3 { cursor: text; @@ -626,6 +1023,75 @@ } } } + .table { + @layer daisyui.l1.l2.l3 { + font-size: 0.875rem; + position: relative; + width: 100%; + border-collapse: separate; + --tw-border-spacing-x: calc(0.25rem * 0); + --tw-border-spacing-y: calc(0.25rem * 0); + border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y); + border-radius: var(--radius-box); + text-align: left; + &:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *) { + text-align: right; + } + tr.row-hover { + &, &:nth-child(even) { + &:hover { + @media (hover: hover) { + background-color: var(--color-base-200); + } + } + } + } + :where(th, td) { + padding-inline: calc(0.25rem * 4); + padding-block: calc(0.25rem * 3); + vertical-align: middle; + } + :where(thead, tfoot) { + white-space: nowrap; + color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, var(--color-base-content) 60%, transparent); + } + font-size: 0.875rem; + font-weight: 600; + } + :where(tfoot tr:first-child :is(td, th)) { + border-top: var(--border) solid var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + border-top: var(--border) solid color-mix(in oklch, var(--color-base-content) 5%, #0000); + } + } + :where(.table-pin-rows thead tr) { + position: sticky; + top: calc(0.25rem * 0); + z-index: 1; + background-color: var(--color-base-100); + } + :where(.table-pin-rows tfoot tr) { + position: sticky; + bottom: calc(0.25rem * 0); + z-index: 1; + background-color: var(--color-base-100); + } + :where(.table-pin-cols tr th) { + position: sticky; + right: calc(0.25rem * 0); + left: calc(0.25rem * 0); + background-color: var(--color-base-100); + } + :where(thead tr :is(td, th), tbody tr:not(:last-child) :is(td, th)) { + border-bottom: var(--border) solid var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + border-bottom: var(--border) solid color-mix(in oklch, var(--color-base-content) 5%, #0000); + } + } + } + } .steps { @layer daisyui.l1.l2.l3 { display: inline-grid; @@ -906,6 +1372,73 @@ } } } + .collapse-title { + @layer daisyui.l1.l2.l3 { + grid-column-start: 1; + grid-row-start: 1; + } + @layer daisyui.l1.l2.l3 { + position: relative; + width: 100%; + padding: 1rem; + padding-inline-end: 3rem; + min-height: 1lh; + transition: background-color 0.2s ease-out; + } + } + .sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; + border-width: 0; + } + .menu-horizontal { + @layer daisyui.l1.l2 { + display: inline-flex; + flex-direction: row; + & > li:not(.menu-title) > details { + & > ul { + position: absolute; + margin-inline-start: calc(0.25rem * 0); + margin-top: calc(0.25rem * 4); + transform-origin: top; + border-radius: var(--radius-box); + background-color: var(--color-base-100); + padding-block: calc(0.25rem * 2); + padding-inline-end: calc(0.25rem * 2); + opacity: 0%; + scale: 95%; + box-shadow: 0 1px 3px 0 oklch(0% 0 0/0.1), 0 1px 2px -1px oklch(0% 0 0/0.1); + @media (prefers-reduced-motion: no-preference) { + @starting-style { + scale: 95%; + opacity: 0; + } + animation: menu 0.2s; + transition-property: opacity, scale, display; + transition-behavior: allow-discrete; + transition-duration: 0.2s; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + } + } + &[open] > ul { + opacity: 100%; + scale: 100%; + } + } + & > li > details > ul { + &:before { + --tw-content: none; + content: var(--tw-content); + } + } + } + } .avatar { @layer daisyui.l1.l2.l3 { position: relative; @@ -923,6 +1456,238 @@ } } } + .checkbox { + @layer daisyui.l1.l2.l3 { + border: var(--border) solid var(--input-color, var(--color-base-content)); + @supports (color: color-mix(in lab, red, red)) { + border: var(--border) solid var(--input-color, color-mix(in oklab, var(--color-base-content) 20%, #0000)); + } + position: relative; + display: inline-block; + flex-shrink: 0; + cursor: pointer; + appearance: none; + border-radius: var(--radius-selector); + padding: calc(0.25rem * 1); + vertical-align: middle; + color: var(--color-base-content); + box-shadow: 0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 0 #0000 inset, 0 0 #0000; + transition: background-color 0.2s, box-shadow 0.2s; + --size: calc(var(--size-selector, 0.25rem) * 6); + width: var(--size); + height: var(--size); + background-size: auto, calc(var(--noise) * 100%); + background-image: none, var(--fx-noise); + &:before { + --tw-content: ""; + content: var(--tw-content); + display: block; + width: 100%; + height: 100%; + rotate: 45deg; + background-color: currentcolor; + opacity: 0%; + transition: clip-path 0.3s, opacity 0.1s, rotate 0.3s, translate 0.3s; + transition-delay: 0.1s; + clip-path: polygon(20% 100%, 20% 80%, 50% 80%, 50% 80%, 70% 80%, 70% 100%); + box-shadow: 0px 3px 0 0px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset; + font-size: 1rem; + line-height: 0.75; + } + &:focus-visible { + outline: 2px solid var(--input-color, currentColor); + outline-offset: 2px; + } + &:checked, &[aria-checked="true"] { + background-color: var(--input-color, #0000); + box-shadow: 0 0 #0000 inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1)); + &:before { + clip-path: polygon(20% 100%, 20% 80%, 50% 80%, 50% 0%, 70% 0%, 70% 100%); + opacity: 100%; + } + @media (forced-colors: active) { + &:before { + rotate: 0deg; + background-color: transparent; + --tw-content: "✔︎"; + clip-path: none; + } + } + @media print { + &:before { + rotate: 0deg; + background-color: transparent; + --tw-content: "✔︎"; + clip-path: none; + } + } + } + &:indeterminate { + background-color: var( --input-color, var(--color-base-content) ); + @supports (color: color-mix(in lab, red, red)) { + background-color: var( --input-color, color-mix(in oklab, var(--color-base-content) 20%, #0000) ); + } + &:before { + rotate: 0deg; + opacity: 100%; + translate: 0 -35%; + clip-path: polygon(20% 100%, 20% 80%, 50% 80%, 50% 80%, 80% 80%, 80% 100%); + } + } + } + &:disabled { + @layer daisyui.l1.l2 { + cursor: not-allowed; + opacity: 20%; + } + } + } + .radio { + @layer daisyui.l1.l2.l3 { + position: relative; + display: inline-block; + flex-shrink: 0; + cursor: pointer; + appearance: none; + border-radius: calc(infinity * 1px); + padding: calc(0.25rem * 1); + vertical-align: middle; + border: var(--border) solid var(--input-color, currentColor); + @supports (color: color-mix(in lab, red, red)) { + border: var(--border) solid var(--input-color, color-mix(in srgb, currentColor 20%, #0000)); + } + box-shadow: 0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset; + --size: calc(var(--size-selector, 0.25rem) * 6); + width: var(--size); + height: var(--size); + color: var(--input-color, currentColor); + &:before { + display: block; + width: 100%; + height: 100%; + border-radius: calc(infinity * 1px); + --tw-content: ""; + content: var(--tw-content); + background-size: auto, calc(var(--noise) * 100%); + background-image: none, var(--fx-noise); + } + &:focus-visible { + outline: 2px solid currentColor; + } + &:checked, &[aria-checked="true"] { + border-color: currentcolor; + background-color: var(--color-base-100); + @media (prefers-reduced-motion: no-preference) { + animation: radio 0.2s ease-out; + } + &:before { + background-color: currentcolor; + box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1)); + } + @media (forced-colors: active) { + &:before { + outline-style: var(--tw-outline-style); + outline-width: 1px; + outline-offset: calc(1px * -1); + } + } + @media print { + &:before { + outline: 0.25rem solid; + outline-offset: -1rem; + } + } + } + } + &:disabled { + @layer daisyui.l1.l2 { + cursor: not-allowed; + opacity: 20%; + } + } + } + .rating { + @layer daisyui.l1.l2.l3 { + position: relative; + display: inline-flex; + vertical-align: middle; + & input { + border: none; + appearance: none; + } + :where(*) { + height: calc(0.25rem * 6); + width: calc(0.25rem * 6); + border-radius: 0; + background-color: var(--color-base-content); + opacity: 20%; + @media (prefers-reduced-motion: no-preference) { + animation: rating 0.25s ease-out; + } + &:is(input) { + cursor: pointer; + } + } + & .rating-hidden { + width: calc(0.25rem * 2); + background-color: transparent; + } + input[type="radio"]:checked { + background-image: none; + } + * { + &:checked, &[aria-checked="true"], &[aria-current="true"], &:has(~ *:checked, ~ *[aria-checked="true"], ~ *[aria-current="true"]) { + opacity: 100%; + } + &:focus-visible { + scale: 1.1; + @media (prefers-reduced-motion: no-preference) { + transition: scale 0.2s ease-out; + } + } + } + & *:active:focus { + animation: none; + scale: 1.1; + } + } + @layer daisyui.l1.l2 { + &.rating-xs :where(*:not(.rating-hidden)) { + width: calc(0.25rem * 4); + height: calc(0.25rem * 4); + } + &.rating-sm :where(*:not(.rating-hidden)) { + width: calc(0.25rem * 5); + height: calc(0.25rem * 5); + } + &.rating-md :where(*:not(.rating-hidden)) { + width: calc(0.25rem * 6); + height: calc(0.25rem * 6); + } + &.rating-lg :where(*:not(.rating-hidden)) { + width: calc(0.25rem * 7); + height: calc(0.25rem * 7); + } + &.rating-xl :where(*:not(.rating-hidden)) { + width: calc(0.25rem * 8); + height: calc(0.25rem * 8); + } + } + } + .navbar { + @layer daisyui.l1.l2.l3 { + display: flex; + width: 100%; + align-items: center; + padding: 0.5rem; + min-height: 4rem; + } + :where(&) { + @layer daisyui.l1.l2 { + position: relative; + } + } + } .card { @layer daisyui.l1.l2.l3 { position: relative; @@ -972,6 +1737,15 @@ } } } + .stats { + @layer daisyui.l1.l2.l3 { + position: relative; + display: inline-grid; + grid-auto-flow: column; + overflow-x: auto; + border-radius: var(--radius-box); + } + } .progress { @layer daisyui.l1.l2.l3 { position: relative; @@ -1022,9 +1796,18 @@ } } } + .fixed { + position: fixed; + } .static { position: static; } + .start-0 { + inset-inline-start: calc(var(--spacing) * 0); + } + .top-0 { + top: calc(var(--spacing) * 0); + } .hero-content { @layer daisyui.l1.l2.l3 { isolation: isolate; @@ -1036,6 +1819,99 @@ padding: calc(0.25rem * 4); } } + .textarea { + @layer daisyui.l1.l2.l3 { + border: var(--border) solid #0000; + min-height: calc(0.25rem * 20); + flex-shrink: 1; + appearance: none; + border-radius: var(--radius-field); + background-color: var(--color-base-100); + padding-block: calc(0.25rem * 2); + vertical-align: middle; + width: clamp(3rem, 20rem, 100%); + padding-inline-start: 0.75rem; + padding-inline-end: 0.75rem; + font-size: max(var(--font-size, 0.875rem), 0.875rem); + touch-action: manipulation; + border-color: var(--input-color); + box-shadow: 0 1px var(--input-color) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset; + @supports (color: color-mix(in lab, red, red)) { + box-shadow: 0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset; + } + --input-color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + --input-color: color-mix(in oklab, var(--color-base-content) 20%, #0000); + } + textarea { + appearance: none; + background-color: transparent; + border: none; + &:focus, &:focus-within { + --tw-outline-style: none; + outline-style: none; + @media (forced-colors: active) { + outline: 2px solid transparent; + outline-offset: 2px; + } + } + } + &:focus, &:focus-within { + --input-color: var(--color-base-content); + box-shadow: 0 1px var(--input-color); + @supports (color: color-mix(in lab, red, red)) { + box-shadow: 0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000); + } + outline: 2px solid var(--input-color); + outline-offset: 2px; + isolation: isolate; + } + @media (pointer: coarse) { + @supports (-webkit-touch-callout: none) { + &:focus, &:focus-within { + --font-size: 1rem; + } + } + } + &:has(> textarea[disabled]), &:is(:disabled, [disabled]) { + cursor: not-allowed; + border-color: var(--color-base-200); + background-color: var(--color-base-200); + color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, var(--color-base-content) 40%, transparent); + } + &::placeholder { + color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, var(--color-base-content) 20%, transparent); + } + } + box-shadow: none; + } + &:has(> textarea[disabled]) > textarea[disabled] { + cursor: not-allowed; + } + } + } + .z-1 { + z-index: 1; + } + .z-20 { + z-index: 20; + } + .z-50 { + z-index: 50; + } + .stat-figure { + @layer daisyui.l1.l2.l3 { + grid-column-start: 2; + grid-row: span 3 / span 3; + grid-row-start: 1; + place-self: center; + justify-self: flex-end; + } + } .hero { @layer daisyui.l1.l2.l3 { display: grid; @@ -1049,6 +1925,36 @@ } } } + .stat-value { + @layer daisyui.l1.l2.l3 { + grid-column-start: 1; + white-space: nowrap; + font-size: 2rem; + font-weight: 800; + } + } + .stat-desc { + @layer daisyui.l1.l2.l3 { + grid-column-start: 1; + white-space: nowrap; + color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, var(--color-base-content) 60%, transparent); + } + font-size: 0.75rem; + } + } + .stat-title { + @layer daisyui.l1.l2.l3 { + grid-column-start: 1; + white-space: nowrap; + color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, var(--color-base-content) 60%, transparent); + } + font-size: 0.75rem; + } + } .container { width: 100%; @media (width >= 40rem) { @@ -1067,6 +1973,51 @@ max-width: 96rem; } } + .filter { + @layer daisyui.l1.l2.l3 { + display: flex; + flex-wrap: wrap; + input[type="radio"] { + width: auto; + } + input { + overflow: hidden; + opacity: 100%; + scale: 1; + transition: margin 0.1s, opacity 0.3s, padding 0.3s, border-width 0.1s; + &:not(:last-child) { + margin-inline-end: calc(0.25rem * 1); + } + &.filter-reset { + aspect-ratio: 1 / 1; + &::after { + --tw-content: "×"; + content: var(--tw-content); + } + } + } + &:not(:has(input:checked:not(.filter-reset))) { + .filter-reset, input[type="reset"] { + scale: 0; + border-width: 0; + margin-inline: calc(0.25rem * 0); + width: calc(0.25rem * 0); + padding-inline: calc(0.25rem * 0); + opacity: 0%; + } + } + &:has(input:checked:not(.filter-reset)) { + input:not(:checked, .filter-reset, input[type="reset"]) { + scale: 0; + border-width: 0; + margin-inline: calc(0.25rem * 0); + width: calc(0.25rem * 0); + padding-inline: calc(0.25rem * 0); + opacity: 0%; + } + } + } + } .mx-auto { margin-inline: auto; } @@ -1109,12 +2060,30 @@ } } } + .me-2 { + margin-inline-end: calc(var(--spacing) * 2); + } + .me-2\.5 { + margin-inline-end: calc(var(--spacing) * 2.5); + } + .mt-3 { + margin-top: calc(var(--spacing) * 3); + } .mt-4 { margin-top: calc(var(--spacing) * 4); } .mb-2 { margin-bottom: calc(var(--spacing) * 2); } + .mb-4 { + margin-bottom: calc(var(--spacing) * 4); + } + .mb-8 { + margin-bottom: calc(var(--spacing) * 8); + } + .box-border { + box-sizing: border-box; + } .status { @layer daisyui.l1.l2.l3 { display: inline-block; @@ -1140,6 +2109,61 @@ } } } + .badge { + @layer daisyui.l1.l2.l3 { + display: inline-flex; + align-items: center; + justify-content: center; + gap: calc(0.25rem * 2); + border-radius: var(--radius-selector); + vertical-align: middle; + color: var(--badge-fg); + border: var(--border) solid var(--badge-color, var(--color-base-200)); + font-size: 0.875rem; + width: fit-content; + background-size: auto, calc(var(--noise) * 100%); + background-image: none, var(--fx-noise); + background-color: var(--badge-bg); + --badge-bg: var(--badge-color, var(--color-base-100)); + --badge-fg: var(--color-base-content); + --size: calc(var(--size-selector, 0.25rem) * 6); + height: var(--size); + padding-inline: calc(var(--size) / 2 - var(--border)); + } + } + .stat { + @layer daisyui.l1.l2.l3 { + display: inline-grid; + width: 100%; + column-gap: calc(0.25rem * 4); + padding-inline: calc(0.25rem * 6); + padding-block: calc(0.25rem * 4); + grid-template-columns: repeat(1, 1fr); + &:not(:last-child) { + border-inline-end: var(--border) dashed currentColor; + @supports (color: color-mix(in lab, red, red)) { + border-inline-end: var(--border) dashed color-mix(in oklab, currentColor 10%, #0000); + } + border-block-end: none; + } + } + } + .navbar-end { + @layer daisyui.l1.l2.l3 { + display: inline-flex; + align-items: center; + width: 50%; + justify-content: flex-end; + } + } + .navbar-start { + @layer daisyui.l1.l2.l3 { + display: inline-flex; + align-items: center; + width: 50%; + justify-content: flex-start; + } + } .card-body { @layer daisyui.l1.l2.l3 { display: flex; @@ -1153,6 +2177,45 @@ } } } + .navbar-center { + @layer daisyui.l1.l2.l3 { + display: inline-flex; + align-items: center; + flex-shrink: 0; + } + } + .alert { + border-width: var(--border); + border-color: var(--alert-border-color, var(--color-base-200)); + @layer daisyui.l1.l2.l3 { + border-style: solid; + --alert-border-color: var(--color-base-200); + display: grid; + align-items: center; + gap: calc(0.25rem * 4); + border-radius: var(--radius-box); + padding-inline: calc(0.25rem * 4); + padding-block: calc(0.25rem * 3); + color: var(--color-base-content); + background-color: var(--alert-color, var(--color-base-200)); + justify-content: start; + justify-items: start; + grid-auto-flow: column; + grid-template-columns: auto; + text-align: start; + font-size: 0.875rem; + line-height: 1.25rem; + background-size: auto, calc(var(--noise) * 100%); + background-image: none, var(--fx-noise); + box-shadow: 0 3px 0 -2px oklch(100% 0 0 / calc(var(--depth) * 0.08)) inset, 0 1px #000, 0 4px 3px -2px oklch(0% 0 0 / calc(var(--depth) * 0.08)); + @supports (color: color-mix(in lab, red, red)) { + box-shadow: 0 3px 0 -2px oklch(100% 0 0 / calc(var(--depth) * 0.08)) inset, 0 1px color-mix( in oklab, color-mix(in oklab, #000 20%, var(--alert-color, var(--color-base-200))) calc(var(--depth) * 20%), #0000 ), 0 4px 3px -2px oklch(0% 0 0 / calc(var(--depth) * 0.08)); + } + &:has(:nth-child(2)) { + grid-template-columns: auto minmax(auto, 1fr); + } + } + } .fieldset { @layer daisyui.l1.l2.l3 { display: grid; @@ -1166,21 +2229,151 @@ .block { display: block; } + .flex { + display: flex; + } + .grid { + display: grid; + } + .hidden { + display: none; + } + .inline { + display: inline; + } + .inline-block { + display: inline-block; + } + .inline-flex { + display: inline-flex; + } + .table { + display: table; + } + .btn-square { + @layer daisyui.l1.l2 { + padding-inline: calc(0.25rem * 0); + width: var(--size); + height: var(--size); + } + } + .size-10 { + width: calc(var(--spacing) * 10); + height: calc(var(--spacing) * 10); + } + .size-\[1\.2em\] { + width: 1.2em; + height: 1.2em; + } + .h-3 { + height: calc(var(--spacing) * 3); + } + .h-3\.5 { + height: calc(var(--spacing) * 3.5); + } + .h-4 { + height: calc(var(--spacing) * 4); + } + .h-5 { + height: calc(var(--spacing) * 5); + } + .h-6 { + height: calc(var(--spacing) * 6); + } + .h-7 { + height: calc(var(--spacing) * 7); + } + .h-8 { + height: calc(var(--spacing) * 8); + } + .h-10 { + height: calc(var(--spacing) * 10); + } .h-32 { height: calc(var(--spacing) * 32); } .min-h-screen { min-height: 100vh; } + .w-3 { + width: calc(var(--spacing) * 3); + } + .w-3\.5 { + width: calc(var(--spacing) * 3.5); + } + .w-4 { + width: calc(var(--spacing) * 4); + } + .w-5 { + width: calc(var(--spacing) * 5); + } + .w-6 { + width: calc(var(--spacing) * 6); + } + .w-7 { + width: calc(var(--spacing) * 7); + } + .w-8 { + width: calc(var(--spacing) * 8); + } + .w-10 { + width: calc(var(--spacing) * 10); + } + .w-40 { + width: calc(var(--spacing) * 40); + } + .w-52 { + width: calc(var(--spacing) * 52); + } .w-full { width: 100%; } + .max-w-7xl { + max-width: var(--container-7xl); + } + .max-w-\[60\%\] { + max-width: 60%; + } + .max-w-md { + max-width: var(--container-md); + } + .max-w-screen-xl { + max-width: var(--breakpoint-xl); + } .max-w-sm { max-width: var(--container-sm); } + .flex-shrink { + flex-shrink: 1; + } .shrink-0 { flex-shrink: 0; } + .flex-grow { + flex-grow: 1; + } + .border-collapse { + border-collapse: collapse; + } + .transform { + transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,); + } + .skeleton { + @layer daisyui.l1.l2.l3 { + border-radius: var(--radius-box); + background-color: var(--color-base-300); + @media (prefers-reduced-motion: reduce) { + transition-duration: 15s; + } + will-change: background-position; + background-image: linear-gradient( 105deg, #0000 0% 40%, var(--color-base-100) 50%, #0000 60% 100% ); + background-size: 200% auto; + background-position-x: -50%; + @media (prefers-reduced-motion: no-preference) { + animation: skeleton 1.8s ease-in-out infinite; + } + } + } .link { @layer daisyui.l1.l2.l3 { cursor: pointer; @@ -1199,37 +2392,590 @@ } } } + .resize { + resize: both; + } + .stats-vertical { + @layer daisyui.l1.l2 { + grid-auto-flow: row; + overflow-y: auto; + .stat:not(:last-child) { + border-inline-end: none; + border-block-end: var(--border) dashed currentColor; + @supports (color: color-mix(in lab, red, red)) { + border-block-end: var(--border) dashed color-mix(in oklab, currentColor 10%, #0000); + } + } + } + } + .grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); + } .flex-col { flex-direction: column; } + .flex-wrap { + flex-wrap: wrap; + } + .items-center { + align-items: center; + } + .justify-between { + justify-content: space-between; + } + .justify-center { + justify-content: center; + } + .gap-6 { + gap: calc(var(--spacing) * 6); + } + .gap-8 { + gap: calc(var(--spacing) * 8); + } + .space-y-3 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse))); + } + } + .space-y-4 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse))); + } + } + .space-y-8 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse))); + } + } + .space-x-3 { + :where(& > :not(:last-child)) { + --tw-space-x-reverse: 0; + margin-inline-start: calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse)); + margin-inline-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse))); + } + } + .self-center { + align-self: center; + } + .truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .overflow-x-auto { + overflow-x: auto; + } + .menu-sm { + @layer daisyui.l1.l2 { + :where(li:not(.menu-title) > *:not(ul, details, .menu-title)), :where(li:not(.menu-title) > details > summary:not(.menu-title)) { + border-radius: var(--radius-field); + padding-inline: calc(0.25rem * 2.5); + padding-block: calc(0.25rem * 1); + font-size: 0.75rem; + } + .menu-title { + padding-inline: calc(0.25rem * 3); + padding-block: calc(0.25rem * 2); + } + } + } + .rounded { + border-radius: 0.25rem; + } + .rounded-box { + border-radius: var(--radius-box); + } + .rounded-box { + border-radius: var(--radius-box); + } + .rounded-full { + border-radius: calc(infinity * 1px); + } + .rounded-lg { + border-radius: var(--radius-lg); + } .rounded-md { border-radius: var(--radius-md); } + .rounded-sm { + border-radius: var(--radius-sm); + } + .border { + border-style: var(--tw-border-style); + border-width: 1px; + } + .border-b { + border-bottom-style: var(--tw-border-style); + border-bottom-width: 1px; + } + .badge-ghost { + @layer daisyui.l1.l2 { + border-color: var(--color-base-200); + background-color: var(--color-base-200); + color: var(--color-base-content); + background-image: none; + } + } + .border-transparent { + border-color: transparent; + } .bg-base-100 { background-color: var(--color-base-100); } .bg-base-200 { background-color: var(--color-base-200); } + .stroke-current { + stroke: currentcolor; + } + .p-2 { + padding: calc(var(--spacing) * 2); + } + .p-3 { + padding: calc(var(--spacing) * 3); + } + .p-4 { + padding: calc(var(--spacing) * 4); + } + .p-6 { + padding: calc(var(--spacing) * 6); + } + .menu-title { + @layer daisyui.l1.l2.l3 { + padding-inline: calc(0.25rem * 3); + padding-block: calc(0.25rem * 2); + color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, var(--color-base-content) 40%, transparent); + } + font-size: 0.875rem; + font-weight: 600; + } + } + .px-1 { + padding-inline: calc(var(--spacing) * 1); + } + .px-3 { + padding-inline: calc(var(--spacing) * 3); + } .px-4 { padding-inline: calc(var(--spacing) * 4); } .py-2 { padding-block: calc(var(--spacing) * 2); } + .py-4 { + padding-block: calc(var(--spacing) * 4); + } + .pt-20 { + padding-top: calc(var(--spacing) * 20); + } + .pb-2 { + padding-bottom: calc(var(--spacing) * 2); + } + .pb-10 { + padding-bottom: calc(var(--spacing) * 10); + } + .pb-20 { + padding-bottom: calc(var(--spacing) * 20); + } + .pb-40 { + padding-bottom: calc(var(--spacing) * 40); + } + .pb-50 { + padding-bottom: calc(var(--spacing) * 50); + } + .pb-100 { + padding-bottom: calc(var(--spacing) * 100); + } .text-center { text-align: center; } + .text-2xl { + font-size: var(--text-2xl); + line-height: var(--tw-leading, var(--text-2xl--line-height)); + } + .text-3xl { + font-size: var(--text-3xl); + line-height: var(--tw-leading, var(--text-3xl--line-height)); + } + .text-sm { + font-size: var(--text-sm); + line-height: var(--tw-leading, var(--text-sm--line-height)); + } + .text-xl { + font-size: var(--text-xl); + line-height: var(--tw-leading, var(--text-xl--line-height)); + } + .text-xs { + font-size: var(--text-xs); + line-height: var(--tw-leading, var(--text-xs--line-height)); + } + .badge-sm { + @layer daisyui.l1.l2 { + --size: calc(var(--size-selector, 0.25rem) * 5); + font-size: 0.75rem; + } + } + .badge-xs { + @layer daisyui.l1.l2 { + --size: calc(var(--size-selector, 0.25rem) * 4); + font-size: 0.625rem; + } + } + .leading-5 { + --tw-leading: calc(var(--spacing) * 5); + line-height: calc(var(--spacing) * 5); + } + .font-bold { + --tw-font-weight: var(--font-weight-bold); + font-weight: var(--font-weight-bold); + } + .font-medium { + --tw-font-weight: var(--font-weight-medium); + font-weight: var(--font-weight-medium); + } + .font-normal { + --tw-font-weight: var(--font-weight-normal); + font-weight: var(--font-weight-normal); + } + .font-semibold { + --tw-font-weight: var(--font-weight-semibold); + font-weight: var(--font-weight-semibold); + } + .tracking-wide { + --tw-tracking: var(--tracking-wide); + letter-spacing: var(--tracking-wide); + } + .text-balance { + text-wrap: balance; + } + .text-wrap { + text-wrap: wrap; + } + .whitespace-nowrap { + white-space: nowrap; + } + .text-error { + color: var(--color-error); + } + .text-info { + color: var(--color-info); + } + .text-success { + color: var(--color-success); + } + .text-warning { + color: var(--color-warning); + } + .text-white { + color: var(--color-white); + } + .uppercase { + text-transform: uppercase; + } + .underline { + text-decoration-line: underline; + } + .opacity-60 { + opacity: 60%; + } + .shadow { + --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } .shadow-2xl { --tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } + .shadow-md { + --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .shadow-sm { + --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .shadow-xs { + --tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .outline { + outline-style: var(--tw-outline-style); + outline-width: 1px; + } + .btn-ghost { + @layer daisyui.l1 { + &:not(.btn-active, :hover, :active:focus, :focus-visible, input:checked:not(.filter .btn)) { + --btn-shadow: ""; + --btn-bg: #0000; + --btn-border: #0000; + --btn-noise: none; + &:not(:disabled, [disabled], .btn-disabled) { + outline-color: currentcolor; + --btn-fg: var(--btn-color, currentColor); + } + } + @media (hover: none) { + &:not(.btn-active, :active, :focus-visible, input:checked:not(.filter .btn)):hover { + outline-color: currentcolor; + --btn-shadow: ""; + --btn-bg: #0000; + --btn-fg: var(--btn-color, currentColor); + --btn-border: #0000; + --btn-noise: none; + } + } + } + } + .filter { + filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); + } + .transition { + transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + .transition-colors { + transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + .duration-200 { + --tw-duration: 200ms; + transition-duration: 200ms; + } + .ease-in-out { + --tw-ease: var(--ease-in-out); + transition-timing-function: var(--ease-in-out); + } + .ease-out { + --tw-ease: var(--ease-out); + transition-timing-function: var(--ease-out); + } + .badge-error { + @layer daisyui.l1.l2 { + --badge-color: var(--color-error); + --badge-fg: var(--color-error-content); + } + } + .badge-info { + @layer daisyui.l1.l2 { + --badge-color: var(--color-info); + --badge-fg: var(--color-info-content); + } + } + .badge-success { + @layer daisyui.l1.l2 { + --badge-color: var(--color-success); + --badge-fg: var(--color-success-content); + } + } + .badge-warning { + @layer daisyui.l1.l2 { + --badge-color: var(--color-warning); + --badge-fg: var(--color-warning-content); + } + } .btn-neutral { @layer daisyui.l1.l2.l3 { --btn-color: var(--color-neutral); --btn-fg: var(--color-neutral-content); } } + .last\:border-0 { + &:last-child { + border-style: var(--tw-border-style); + border-width: 0px; + } + } + .hover\:no-underline { + &:hover { + @media (hover: hover) { + text-decoration-line: none; + } + } + } + .focus\:ring-2 { + &:focus { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + } + .focus\:ring-4 { + &:focus { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + } + .focus\:outline-none { + &:focus { + --tw-outline-style: none; + outline-style: none; + } + } + .sm\:px-6 { + @media (width >= 40rem) { + padding-inline: calc(var(--spacing) * 6); + } + } + .md\:order-1 { + @media (width >= 48rem) { + order: 1; + } + } + .md\:order-2 { + @media (width >= 48rem) { + order: 2; + } + } + .md\:mt-0 { + @media (width >= 48rem) { + margin-top: calc(var(--spacing) * 0); + } + } + .md\:block { + @media (width >= 48rem) { + display: block; + } + } + .md\:flex { + @media (width >= 48rem) { + display: flex; + } + } + .md\:hidden { + @media (width >= 48rem) { + display: none; + } + } + .md\:w-auto { + @media (width >= 48rem) { + width: auto; + } + } + .md\:grid-cols-2 { + @media (width >= 48rem) { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + } + .md\:flex-row { + @media (width >= 48rem) { + flex-direction: row; + } + } + .md\:space-x-0 { + @media (width >= 48rem) { + :where(& > :not(:last-child)) { + --tw-space-x-reverse: 0; + margin-inline-start: calc(calc(var(--spacing) * 0) * var(--tw-space-x-reverse)); + margin-inline-end: calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-x-reverse))); + } + } + } + .md\:space-x-8 { + @media (width >= 48rem) { + :where(& > :not(:last-child)) { + --tw-space-x-reverse: 0; + margin-inline-start: calc(calc(var(--spacing) * 8) * var(--tw-space-x-reverse)); + margin-inline-end: calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-x-reverse))); + } + } + } + .md\:border-0 { + @media (width >= 48rem) { + border-style: var(--tw-border-style); + border-width: 0px; + } + } + .md\:bg-transparent { + @media (width >= 48rem) { + background-color: transparent; + } + } + .md\:p-0 { + @media (width >= 48rem) { + padding: calc(var(--spacing) * 0); + } + } + .md\:hover\:bg-transparent { + @media (width >= 48rem) { + &:hover { + @media (hover: hover) { + background-color: transparent; + } + } + } + } + .lg\:col-span-2 { + @media (width >= 64rem) { + grid-column: span 2 / span 2; + } + } + .lg\:flex { + @media (width >= 64rem) { + display: flex; + } + } + .lg\:hidden { + @media (width >= 64rem) { + display: none; + } + } + .lg\:stats-horizontal { + @media (width >= 64rem) { + @layer daisyui.l1.l2 { + grid-auto-flow: column; + overflow-x: auto; + .stat:not(:last-child) { + border-inline-end: var(--border) dashed currentColor; + @supports (color: color-mix(in lab, red, red)) { + border-inline-end: var(--border) dashed color-mix(in oklab, currentColor 10%, #0000); + } + border-block-end: none; + } + } + } + } + .lg\:grid-cols-3 { + @media (width >= 64rem) { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + } + .lg\:grid-cols-4 { + @media (width >= 64rem) { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + } + .lg\:px-8 { + @media (width >= 64rem) { + padding-inline: calc(var(--spacing) * 8); + } + } + .rtl\:space-x-reverse { + &:where(:dir(rtl), [dir="rtl"], [dir="rtl"] *) { + :where(& > :not(:last-child)) { + --tw-space-x-reverse: 1; + } + } + } + .md\:dark\:hover\:bg-transparent { + @media (width >= 48rem) { + @media (prefers-color-scheme: dark) { + &:hover { + @media (hover: hover) { + background-color: transparent; + } + } + } + } + } } @layer base { :where(:root),:root:has(input.theme-controller[value=light]:checked),[data-theme=light] { @@ -1477,6 +3223,53 @@ background-position-x: -115%; } } +@property --tw-rotate-x { + syntax: "*"; + inherits: false; +} +@property --tw-rotate-y { + syntax: "*"; + inherits: false; +} +@property --tw-rotate-z { + syntax: "*"; + inherits: false; +} +@property --tw-skew-x { + syntax: "*"; + inherits: false; +} +@property --tw-skew-y { + syntax: "*"; + inherits: false; +} +@property --tw-space-y-reverse { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-space-x-reverse { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-border-style { + syntax: "*"; + inherits: false; + initial-value: solid; +} +@property --tw-leading { + syntax: "*"; + inherits: false; +} +@property --tw-font-weight { + syntax: "*"; + inherits: false; +} +@property --tw-tracking { + syntax: "*"; + inherits: false; +} @property --tw-shadow { syntax: "*"; inherits: false; @@ -1542,9 +3335,86 @@ inherits: false; initial-value: 0 0 #0000; } +@property --tw-outline-style { + syntax: "*"; + inherits: false; + initial-value: solid; +} +@property --tw-blur { + syntax: "*"; + inherits: false; +} +@property --tw-brightness { + syntax: "*"; + inherits: false; +} +@property --tw-contrast { + syntax: "*"; + inherits: false; +} +@property --tw-grayscale { + syntax: "*"; + inherits: false; +} +@property --tw-hue-rotate { + syntax: "*"; + inherits: false; +} +@property --tw-invert { + syntax: "*"; + inherits: false; +} +@property --tw-opacity { + syntax: "*"; + inherits: false; +} +@property --tw-saturate { + syntax: "*"; + inherits: false; +} +@property --tw-sepia { + syntax: "*"; + inherits: false; +} +@property --tw-drop-shadow { + syntax: "*"; + inherits: false; +} +@property --tw-drop-shadow-color { + syntax: "*"; + inherits: false; +} +@property --tw-drop-shadow-alpha { + syntax: ""; + inherits: false; + initial-value: 100%; +} +@property --tw-drop-shadow-size { + syntax: "*"; + inherits: false; +} +@property --tw-duration { + syntax: "*"; + inherits: false; +} +@property --tw-ease { + syntax: "*"; + inherits: false; +} @layer properties { @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) { *, ::before, ::after, ::backdrop { + --tw-rotate-x: initial; + --tw-rotate-y: initial; + --tw-rotate-z: initial; + --tw-skew-x: initial; + --tw-skew-y: initial; + --tw-space-y-reverse: 0; + --tw-space-x-reverse: 0; + --tw-border-style: solid; + --tw-leading: initial; + --tw-font-weight: initial; + --tw-tracking: initial; --tw-shadow: 0 0 #0000; --tw-shadow-color: initial; --tw-shadow-alpha: 100%; @@ -1559,6 +3429,22 @@ --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; + --tw-outline-style: solid; + --tw-blur: initial; + --tw-brightness: initial; + --tw-contrast: initial; + --tw-grayscale: initial; + --tw-hue-rotate: initial; + --tw-invert: initial; + --tw-opacity: initial; + --tw-saturate: initial; + --tw-sepia: initial; + --tw-drop-shadow: initial; + --tw-drop-shadow-color: initial; + --tw-drop-shadow-alpha: 100%; + --tw-drop-shadow-size: initial; + --tw-duration: initial; + --tw-ease: initial; } } } diff --git a/templates/base/dashboard.html b/templates/base/dashboard.html new file mode 100644 index 0000000..0610921 --- /dev/null +++ b/templates/base/dashboard.html @@ -0,0 +1,187 @@ +{% extends "shared/base.html" %} +{% load static %} +{% block content %} +{% include "shared/navbar.html" %} + +
+
+

Dashboard

+

Welcome back! {{ request.user.username }}

+ + +
+
+
+ + + + +
+
Total Clients
+
{{ total_clients }}
+
Lifetime total
+
+ +
+
+ + + + +
+
Active Projects
+
{{ active_projects }}
+
In progress
+
+ +
+
+ + + +
+
Pending Tasks
+
{{ pending_tasks }}
+
Needs attention
+
+ +
+
+ + + + +
+
Revenue
+
Rs.{{ total_income|floatformat:0 }}
+
Total paid invoices
+
+
+ +
+ +
+ +
+

Recent Projects

+
+ + + + + + + + + + + {% for project in recent_projects %} + + + + + + + {% empty %} + + + + {% endfor %} + +
ProjectClientStatusUpdated
{{ project.name }}{{ project.client.name }} + + {{ project.get_status_display }} + + {{ project.updated_at|date:"M d" }}
No recent projects
+
+
+ + +
+

Recent Tasks

+
    + {% for task in recent_tasks %} +
  • +
    +
    +
    +
    +

    {{ task.title }}

    +

    {{ task.project.name }}

    +
    +
    + {{ task.updated_at|timesince }} ago +
  • + {% empty %} +
  • No recent tasks
  • + {% endfor %} +
+
+
+ + +
+ +
+

Financial Overview

+
+
+ Pending Income + Rs.{{ pending_income|floatformat:2 }} +
+
+ Monthly Expenses + Rs.{{ monthly_expenses|floatformat:2 }} +
+
+ Overdue Invoices + {{ overdue_invoices }} +
+
+
+ + +
+

Upcoming Deadlines

+
    + {% for project in upcoming_deadlines %} +
  • + {{ project.name }} + {{ project.deadline|date:"M d" }} +
  • + {% empty %} +
  • No upcoming deadlines
  • + {% endfor %} +
+
+ + +
+

Expiring Services

+
    + {% for service in expiring_services %} +
  • + {{ service.name }} + {{ service.expiry_date|date:"M d" }} +
  • + {% empty %} +
  • No expiring services
  • + {% endfor %} +
+
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/base/login.html b/templates/base/login.html index 1e3e6f3..e296cd8 100644 --- a/templates/base/login.html +++ b/templates/base/login.html @@ -3,23 +3,35 @@ {% block content %}
-
+
DevSuite -

- comprehensive management system designed for developers and agencies to streamline client management, project tracking, finance, and productivity. + +

+ comprehensive management system designed for developers and agencies + to streamline client management, project tracking, finance, and productivity.

-
- - - - - -
+ {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} +
+ {% csrf_token %} +
+ + + + + +
+
-{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/shared/navbar.html b/templates/shared/navbar.html new file mode 100644 index 0000000..c502456 --- /dev/null +++ b/templates/shared/navbar.html @@ -0,0 +1,98 @@ +{% load static %} + + \ No newline at end of file