theming: migrate cospend to semantic CSS variables, extract SaveFab, refactor measure page
All checks were successful
CI / update (push) Successful in 4m21s
All checks were successful
CI / update (push) Successful in 4m21s
Replace hardcoded Nord colors with semantic CSS variables across all cospend pages and shared components (FormSection, ImageUpload, SplitMethodSelector, UsersList, PaymentModal, BarChart). Remove all dark mode override blocks. Make BarChart font colors theme-reactive via isDark() + MutationObserver. Extract reusable SaveFab component and use it on recipe edit and all cospend edit/add pages. Remove Cancel buttons and back links in favor of browser navigation. Replace raw checkboxes with Toggle component. Move fitness measurement add/edit forms to separate routes with SaveFab. Collapse profile section (sex/height) by default on the measure page. Document theming rules in CLAUDE.md for future reference.
This commit is contained in:
@@ -13,35 +13,17 @@
|
||||
|
||||
<style>
|
||||
.form-section {
|
||||
background: var(--nord6);
|
||||
background: var(--color-surface);
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.form-section h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .form-section {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .form-section h2 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .form-section {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .form-section h2 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
|
||||
<div class="form-section">
|
||||
<h2>{title}</h2>
|
||||
|
||||
|
||||
{#if currentImage}
|
||||
<div class="current-image">
|
||||
<img src={currentImage} alt="Receipt" class="receipt-preview" />
|
||||
@@ -72,7 +72,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
{#if imagePreview}
|
||||
<div class="image-preview">
|
||||
<img src={imagePreview} alt="Receipt preview" />
|
||||
@@ -93,17 +93,17 @@
|
||||
<small>JPEG, PNG, WebP (max 5MB)</small>
|
||||
</div>
|
||||
</label>
|
||||
<input
|
||||
type="file"
|
||||
id="image"
|
||||
accept="image/jpeg,image/jpg,image/png,image/webp"
|
||||
<input
|
||||
type="file"
|
||||
id="image"
|
||||
accept="image/jpeg,image/jpg,image/png,image/webp"
|
||||
onchange={handleImageChange}
|
||||
disabled={uploading}
|
||||
hidden
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
{#if uploading}
|
||||
<div class="upload-status">Uploading image...</div>
|
||||
{/if}
|
||||
@@ -111,114 +111,55 @@
|
||||
|
||||
<style>
|
||||
.form-section {
|
||||
background: var(--nord6);
|
||||
background: var(--color-surface);
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.form-section h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .form-section {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .form-section h2 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .form-section {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .form-section h2 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.image-upload {
|
||||
border: 2px dashed var(--nord4);
|
||||
border: 2px dashed var(--color-border);
|
||||
border-radius: 0.5rem;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
background-color: var(--nord5);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
}
|
||||
|
||||
.image-upload:hover {
|
||||
border-color: var(--blue);
|
||||
background-color: var(--nord4);
|
||||
background-color: var(--color-bg-elevated);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .image-upload {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .image-upload:hover {
|
||||
background-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .image-upload {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .image-upload:hover {
|
||||
background-color: var(--nord3);
|
||||
}
|
||||
|
||||
.upload-label {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.upload-content svg {
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.upload-content p {
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-weight: 500;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.upload-content small {
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .upload-content svg {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .upload-content p {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .upload-content small {
|
||||
color: var(--nord4);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .upload-content svg {
|
||||
color: var(--nord4);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .upload-content p {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .upload-content small {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
.image-preview {
|
||||
text-align: center;
|
||||
}
|
||||
@@ -255,22 +196,13 @@
|
||||
max-height: 200px;
|
||||
object-fit: cover;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
margin-bottom: 0.75rem;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .receipt-preview {
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .receipt-preview {
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
|
||||
.image-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -282,4 +214,4 @@
|
||||
font-size: 0.9rem;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
51
src/lib/components/SaveFab.svelte
Normal file
51
src/lib/components/SaveFab.svelte
Normal file
@@ -0,0 +1,51 @@
|
||||
<script>
|
||||
import Check from '$lib/assets/icons/Check.svelte';
|
||||
|
||||
let { disabled = false, onclick, label = 'Save', type = 'submit' } = $props();
|
||||
</script>
|
||||
|
||||
<button
|
||||
{type}
|
||||
class="fab-save"
|
||||
{onclick}
|
||||
{disabled}
|
||||
aria-label={label}
|
||||
>
|
||||
<Check fill="white" width="2rem" height="2rem" />
|
||||
</button>
|
||||
|
||||
<style>
|
||||
.fab-save {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
padding: 2rem;
|
||||
margin: 2rem;
|
||||
border: none;
|
||||
border-radius: var(--radius-pill);
|
||||
background-color: var(--red);
|
||||
display: grid;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
cursor: pointer;
|
||||
z-index: 100;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.fab-save:hover, .fab-save:focus {
|
||||
background-color: var(--nord11);
|
||||
}
|
||||
|
||||
.fab-save:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
.fab-save {
|
||||
margin: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -20,6 +20,13 @@
|
||||
// Register Chart.js components
|
||||
Chart.register(...registerables);
|
||||
|
||||
function isDark() {
|
||||
const theme = document.documentElement.getAttribute('data-theme');
|
||||
if (theme === 'dark') return true;
|
||||
if (theme === 'light') return false;
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
|
||||
// Nord theme colors for categories
|
||||
const nordColors = [
|
||||
'#5E81AC', // Nord Blue
|
||||
@@ -82,6 +89,12 @@
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
const dark = isDark();
|
||||
const textColor = dark ? '#D8DEE9' : '#2E3440';
|
||||
const tooltipBg = dark ? '#2E3440' : '#ECEFF4';
|
||||
const tooltipText = dark ? '#ECEFF4' : '#2E3440';
|
||||
const tooltipBody = dark ? '#D8DEE9' : '#3B4252';
|
||||
|
||||
// Convert $state proxy to plain arrays to avoid Chart.js property descriptor issues
|
||||
const plainLabels = [...(data.labels || [])];
|
||||
const plainDatasets = (data.datasets || []).map((/** @type {{ label: string, data: number[] }} */ ds) => ({
|
||||
@@ -123,7 +136,7 @@
|
||||
display: false
|
||||
},
|
||||
ticks: {
|
||||
color: '#ffffff',
|
||||
color: textColor,
|
||||
font: {
|
||||
family: 'Inter, system-ui, sans-serif',
|
||||
size: 14,
|
||||
@@ -157,7 +170,7 @@
|
||||
labels: {
|
||||
padding: 20,
|
||||
usePointStyle: true,
|
||||
color: '#ffffff',
|
||||
color: textColor,
|
||||
font: {
|
||||
family: 'Inter, system-ui, sans-serif',
|
||||
size: 14,
|
||||
@@ -194,7 +207,7 @@
|
||||
title: {
|
||||
display: !!title,
|
||||
text: title,
|
||||
color: '#ffffff',
|
||||
color: textColor,
|
||||
font: {
|
||||
family: 'Inter, system-ui, sans-serif',
|
||||
size: 18,
|
||||
@@ -203,9 +216,9 @@
|
||||
padding: 20
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: '#2e3440',
|
||||
titleColor: '#ffffff',
|
||||
bodyColor: '#ffffff',
|
||||
backgroundColor: tooltipBg,
|
||||
titleColor: tooltipText,
|
||||
bodyColor: tooltipBody,
|
||||
borderWidth: 0,
|
||||
cornerRadius: 12,
|
||||
padding: 12,
|
||||
@@ -275,7 +288,7 @@
|
||||
|
||||
ctx.save();
|
||||
ctx.font = 'bold 14px Inter, system-ui, sans-serif';
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fillStyle = isDark() ? '#D8DEE9' : '#2E3440';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'bottom';
|
||||
|
||||
@@ -367,24 +380,12 @@
|
||||
|
||||
<style>
|
||||
.chart-container {
|
||||
background: var(--nord6);
|
||||
border-radius: 0.75rem;
|
||||
background: var(--color-surface);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .chart-container {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .chart-container {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.chart-container {
|
||||
padding: 0.75rem;
|
||||
|
||||
@@ -255,7 +255,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: var(--nord6);
|
||||
background: var(--color-bg-secondary);
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
@@ -263,14 +263,13 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1.5rem;
|
||||
border-bottom: 1px solid var(--nord4);
|
||||
background: var(--nord5);
|
||||
background: var(--color-bg-tertiary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.panel-header h2 {
|
||||
margin: 0;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
@@ -280,13 +279,13 @@
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.close-button:hover {
|
||||
background: var(--nord4);
|
||||
color: var(--nord0);
|
||||
background: var(--color-bg-elevated);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
@@ -303,7 +302,7 @@
|
||||
|
||||
.error {
|
||||
color: var(--red);
|
||||
background-color: var(--nord6);
|
||||
background-color: var(--color-bg-secondary);
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--red);
|
||||
}
|
||||
@@ -318,8 +317,7 @@
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
padding: 1.5rem;
|
||||
background: linear-gradient(135deg, var(--nord5), var(--nord4));
|
||||
border-bottom: 1px solid var(--nord3);
|
||||
background: var(--color-bg-tertiary);
|
||||
}
|
||||
|
||||
.title-with-category {
|
||||
@@ -336,7 +334,7 @@
|
||||
|
||||
.title-section h1 {
|
||||
margin: 0;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
@@ -356,7 +354,7 @@
|
||||
max-height: 100px;
|
||||
object-fit: cover;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.payment-info {
|
||||
@@ -378,43 +376,41 @@
|
||||
|
||||
.label {
|
||||
font-weight: 600;
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.85rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.description {
|
||||
border-top: 1px solid var(--nord4);
|
||||
padding-top: 1.5rem;
|
||||
}
|
||||
|
||||
.description h3 {
|
||||
margin: 0 0 0.75rem 0;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.description p {
|
||||
margin: 0;
|
||||
color: var(--nord2);
|
||||
color: var(--color-text-tertiary);
|
||||
line-height: 1.5;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.splits-section {
|
||||
border-top: 1px solid var(--nord4);
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.splits-section h3 {
|
||||
margin: 0 0 1rem 0;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
@@ -429,14 +425,12 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.75rem;
|
||||
background: var(--nord5);
|
||||
background: var(--color-bg-primary);
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--nord4);
|
||||
}
|
||||
|
||||
.split-item.current-user {
|
||||
background: var(--nord8);
|
||||
border-color: var(--blue);
|
||||
background: var(--color-bg-tertiary);
|
||||
}
|
||||
|
||||
.split-user {
|
||||
@@ -453,7 +447,7 @@
|
||||
|
||||
.username {
|
||||
font-weight: 500;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
@@ -481,8 +475,7 @@
|
||||
|
||||
.panel-actions {
|
||||
padding: 1.5rem;
|
||||
border-top: 1px solid var(--nord4);
|
||||
background: var(--nord5);
|
||||
background: var(--color-bg-tertiary);
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: flex-end;
|
||||
@@ -495,190 +488,19 @@
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
background-color: var(--nord5);
|
||||
color: var(--nord0);
|
||||
border: 1px solid var(--nord4);
|
||||
background-color: var(--color-bg-primary);
|
||||
color: var(--color-text-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: var(--nord4);
|
||||
background-color: var(--color-bg-elevated);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .panel-content {
|
||||
background: var(--nord1);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .panel-header {
|
||||
background: var(--nord2);
|
||||
border-bottom-color: var(--nord3);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .panel-header h2 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .close-button {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .close-button:hover {
|
||||
background: var(--nord3);
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .error {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .payment-header {
|
||||
background: linear-gradient(135deg, var(--nord2), var(--nord3));
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .title-section h1 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .receipt-image img {
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .label {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .value {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .description {
|
||||
border-top-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .description h3 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .description p {
|
||||
color: var(--nord5);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .splits-section {
|
||||
border-top-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .splits-section h3 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .split-item {
|
||||
background: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .split-item.current-user {
|
||||
background: var(--nord3);
|
||||
border-color: var(--blue);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .username {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .panel-actions {
|
||||
background: var(--nord2);
|
||||
border-top-color: var(--nord3);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .btn-secondary {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .btn-secondary:hover {
|
||||
background-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .panel-content {
|
||||
background: var(--nord1);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .panel-header {
|
||||
background: var(--nord2);
|
||||
border-bottom-color: var(--nord3);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .panel-header h2 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .close-button {
|
||||
color: var(--nord4);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .close-button:hover {
|
||||
background: var(--nord3);
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .error {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .payment-header {
|
||||
background: linear-gradient(135deg, var(--nord2), var(--nord3));
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .title-section h1 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .receipt-image img {
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .label {
|
||||
color: var(--nord4);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .value {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .description {
|
||||
border-top-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .description h3 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .description p {
|
||||
color: var(--nord5);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .splits-section {
|
||||
border-top-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .splits-section h3 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .split-item {
|
||||
background: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .split-item.current-user {
|
||||
background: var(--nord3);
|
||||
border-color: var(--blue);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .username {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .panel-actions {
|
||||
background: var(--nord2);
|
||||
border-top-color: var(--nord3);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .btn-secondary {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .btn-secondary:hover {
|
||||
background-color: var(--nord3);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.panel-content {
|
||||
height: 100vh;
|
||||
|
||||
@@ -216,38 +216,20 @@
|
||||
|
||||
<style>
|
||||
.form-section {
|
||||
background: var(--nord6);
|
||||
background: var(--color-surface);
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.form-section h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .form-section {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .form-section h2 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .form-section {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .form-section h2 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
@@ -256,27 +238,18 @@
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
color: var(--nord2);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) label {
|
||||
color: var(--nord5);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) label {
|
||||
color: var(--nord5);
|
||||
}
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1rem;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--nord6);
|
||||
color: var(--nord0);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
select:focus {
|
||||
@@ -285,75 +258,31 @@
|
||||
box-shadow: 0 0 0 2px rgba(94, 129, 172, 0.2);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) select {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) select {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
.proportional-splits, .personal-splits {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.proportional-splits {
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
background-color: var(--nord5);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .proportional-splits {
|
||||
border-color: var(--nord3);
|
||||
background-color: var(--nord2);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .proportional-splits {
|
||||
border-color: var(--nord3);
|
||||
background-color: var(--nord2);
|
||||
}
|
||||
|
||||
.proportional-splits h3, .personal-splits h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .proportional-splits h3,
|
||||
:global(:root:not([data-theme="light"])) .personal-splits h3 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .proportional-splits h3,
|
||||
:global(:root[data-theme="dark"]) .personal-splits h3 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.personal-splits .description {
|
||||
color: var(--nord2);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 1rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .personal-splits .description {
|
||||
color: var(--nord4);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .personal-splits .description {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
.split-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -369,11 +298,11 @@
|
||||
.split-input input {
|
||||
max-width: 120px;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1rem;
|
||||
background-color: var(--nord6);
|
||||
color: var(--nord0);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
color: var(--color-text-primary);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@@ -383,52 +312,19 @@
|
||||
box-shadow: 0 0 0 2px rgba(94, 129, 172, 0.2);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .split-input input {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .split-input input {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
.remainder-info {
|
||||
margin-top: 1rem;
|
||||
padding: 1rem;
|
||||
background-color: var(--nord5);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.remainder-info.error {
|
||||
background-color: var(--nord6);
|
||||
background-color: var(--color-bg-secondary);
|
||||
border-color: var(--red);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .remainder-info {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .remainder-info.error {
|
||||
background-color: var(--accent-dark);
|
||||
border-color: var(--red);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .remainder-info {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .remainder-info.error {
|
||||
background-color: var(--accent-dark);
|
||||
border-color: var(--red);
|
||||
}
|
||||
|
||||
.remainder-info span {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
@@ -443,38 +339,18 @@
|
||||
}
|
||||
|
||||
.split-preview {
|
||||
background-color: var(--nord5);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .split-preview {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .split-preview {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
.split-preview h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .split-preview h3 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .split-preview h3 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.split-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -489,18 +365,9 @@
|
||||
}
|
||||
|
||||
.username {
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .username {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .username {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.amount.positive {
|
||||
color: var(--green);
|
||||
font-weight: 500;
|
||||
@@ -510,4 +377,4 @@
|
||||
color: var(--red);
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
canRemoveUsers?: boolean,
|
||||
newUser?: string
|
||||
}>();
|
||||
|
||||
|
||||
function addUser() {
|
||||
if (predefinedMode) return;
|
||||
|
||||
|
||||
if (newUser.trim() && !users.includes(newUser.trim())) {
|
||||
users = [...users, newUser.trim()];
|
||||
newUser = '';
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
<div class="form-section">
|
||||
<h2>Split Between Users</h2>
|
||||
|
||||
|
||||
{#if predefinedMode}
|
||||
<div class="predefined-users">
|
||||
<p class="predefined-note">Splitting between predefined users:</p>
|
||||
@@ -84,38 +84,20 @@
|
||||
|
||||
<style>
|
||||
.form-section {
|
||||
background: var(--nord6);
|
||||
background: var(--color-surface);
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.form-section h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .form-section {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .form-section h2 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .form-section {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .form-section h2 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.users-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -127,41 +109,21 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
background-color: var(--nord5);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 1rem;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .user-item {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .user-item {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
.user-item.with-profile {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.user-item .username {
|
||||
font-weight: 500;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .user-item .username {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .user-item .username {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.you-badge {
|
||||
background-color: var(--blue);
|
||||
color: white;
|
||||
@@ -172,39 +134,19 @@
|
||||
}
|
||||
|
||||
.predefined-users {
|
||||
background-color: var(--nord5);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .predefined-users {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .predefined-users {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
.predefined-note {
|
||||
margin: 0 0 1rem 0;
|
||||
color: var(--nord2);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.9rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .predefined-note {
|
||||
color: var(--nord4);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .predefined-note {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
.remove-user {
|
||||
background-color: var(--red);
|
||||
color: white;
|
||||
@@ -229,11 +171,11 @@
|
||||
.add-user input {
|
||||
flex: 1;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1rem;
|
||||
background-color: var(--nord6);
|
||||
color: var(--nord0);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
color: var(--color-text-primary);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@@ -243,19 +185,6 @@
|
||||
box-shadow: 0 0 0 2px rgba(94, 129, 172, 0.2);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .add-user input {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .add-user input {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
.add-user button {
|
||||
background-color: var(--blue);
|
||||
color: white;
|
||||
@@ -270,4 +199,4 @@
|
||||
background-color: var(--nord10);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { enhance } from '$app/forms';
|
||||
import { tick } from 'svelte';
|
||||
import type { ActionData, PageData } from './$types';
|
||||
import Check from '$lib/assets/icons/Check.svelte';
|
||||
import SaveFab from '$lib/components/SaveFab.svelte';
|
||||
import Cross from '$lib/assets/icons/Cross.svelte';
|
||||
import SeasonSelect from '$lib/components/recipes/SeasonSelect.svelte';
|
||||
import TranslationApproval from '$lib/components/recipes/TranslationApproval.svelte';
|
||||
@@ -479,34 +479,6 @@
|
||||
font-size: 1.3rem;
|
||||
color: white;
|
||||
}
|
||||
.fab-save {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
padding: 2rem;
|
||||
margin: 2rem;
|
||||
border-radius: var(--radius-pill);
|
||||
background-color: var(--red);
|
||||
display: grid;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
z-index: 100;
|
||||
animation: unset !important;
|
||||
}
|
||||
.fab-save:hover, .fab-save:focus {
|
||||
background-color: var(--nord0) !important;
|
||||
}
|
||||
.fab-save:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
@media screen and (max-width: 500px) {
|
||||
.fab-save {
|
||||
margin: 1rem;
|
||||
}
|
||||
}
|
||||
.submit_buttons {
|
||||
display: flex;
|
||||
margin-inline: auto;
|
||||
@@ -1147,13 +1119,4 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- FAB save button -->
|
||||
<button
|
||||
type="button"
|
||||
class="fab-save action_button"
|
||||
onclick={saveRecipe}
|
||||
disabled={submitting}
|
||||
aria-label="Rezept speichern"
|
||||
>
|
||||
<Check fill="white" width="2rem" height="2rem" />
|
||||
</button>
|
||||
<SaveFab type="button" onclick={saveRecipe} disabled={submitting} label="Rezept speichern" />
|
||||
|
||||
@@ -108,12 +108,13 @@
|
||||
|
||||
.side-panel {
|
||||
position: fixed;
|
||||
top: 4rem;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 400px;
|
||||
height: calc(100vh - 4rem);
|
||||
background: #fbf9f3;
|
||||
border-left: 1px solid #dee2e6;
|
||||
height: 100vh;
|
||||
padding-top: var(--header-h, 3rem);
|
||||
background: var(--color-bg-tertiary);
|
||||
border-left: 1px solid var(--color-border);
|
||||
box-shadow: -2px 0 10px rgba(0, 0, 0, 0.1);
|
||||
z-index: 100;
|
||||
overflow-y: auto;
|
||||
@@ -139,17 +140,6 @@
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .side-panel {
|
||||
background: var(--background-dark);
|
||||
border-left-color: #434C5E;
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .side-panel {
|
||||
background: var(--background-dark);
|
||||
border-left-color: #434C5E;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.layout-container.has-modal .main-content {
|
||||
margin-right: 0;
|
||||
@@ -157,11 +147,12 @@
|
||||
|
||||
.side-panel {
|
||||
position: fixed;
|
||||
top: 4rem;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: calc(100vh - 4rem);
|
||||
height: 100vh;
|
||||
padding-top: var(--header-h, 3rem);
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
@@ -188,8 +179,9 @@
|
||||
min-width: unset;
|
||||
max-width: unset;
|
||||
width: 100%;
|
||||
padding-top: 0;
|
||||
border-left: none;
|
||||
border-top: 1px solid #dee2e6;
|
||||
border-top: 1px solid var(--color-border);
|
||||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
@@ -200,15 +192,4 @@
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) and (prefers-color-scheme: dark) {
|
||||
.side-panel {
|
||||
border-top-color: #434C5E;
|
||||
}
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
:global(:root[data-theme="dark"]) .side-panel {
|
||||
border-top-color: #434C5E;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -310,7 +310,7 @@
|
||||
text-align: center;
|
||||
font-size: 2.5rem;
|
||||
margin-block: 0.5rem 1.5rem;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.loading, .error {
|
||||
@@ -321,26 +321,10 @@
|
||||
|
||||
.error {
|
||||
color: var(--red);
|
||||
background-color: var(--nord6);
|
||||
background-color: var(--color-bg-secondary);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) h1 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .error {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) h1 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .error {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
|
||||
|
||||
.positive {
|
||||
color: var(--green);
|
||||
@@ -379,11 +363,7 @@
|
||||
}
|
||||
|
||||
.recent-activity {
|
||||
background: var(--nord6);
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid var(--nord4);
|
||||
padding: 1.5rem 0;
|
||||
max-width: 800px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
@@ -399,21 +379,21 @@
|
||||
|
||||
.recent-activity h2 {
|
||||
margin: 0;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-weight: 400;
|
||||
font-size: 1rem;
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.clear-filter {
|
||||
background: none;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.25rem 0.75rem;
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
white-space: nowrap;
|
||||
@@ -421,67 +401,17 @@
|
||||
}
|
||||
|
||||
.clear-filter:hover {
|
||||
border-color: var(--blue);
|
||||
color: var(--blue);
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.no-results {
|
||||
text-align: center;
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
font-style: italic;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .recent-activity {
|
||||
background: var(--accent-dark);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .recent-activity h2 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .filter-label {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .clear-filter {
|
||||
border-color: var(--nord3);
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .clear-filter:hover {
|
||||
border-color: var(--blue);
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .no-results {
|
||||
color: var(--nord4);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .recent-activity {
|
||||
background: var(--accent-dark);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .recent-activity h2 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .filter-label {
|
||||
color: var(--nord4);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .clear-filter {
|
||||
border-color: var(--nord3);
|
||||
color: var(--nord4);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .clear-filter:hover {
|
||||
border-color: var(--blue);
|
||||
color: var(--blue);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .no-results {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
.activity-dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -511,11 +441,11 @@
|
||||
}
|
||||
|
||||
.activity-bubble {
|
||||
background: var(--nord5);
|
||||
background: var(--color-bg-secondary);
|
||||
border-radius: 1rem;
|
||||
padding: 1rem;
|
||||
position: relative;
|
||||
border: 1px solid var(--nord4);
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
@@ -524,30 +454,9 @@
|
||||
}
|
||||
|
||||
.activity-message.is-me .activity-bubble {
|
||||
background: var(--nord8);
|
||||
border-color: var(--blue);
|
||||
background: var(--color-bg-tertiary);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .activity-bubble {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .activity-message.is-me .activity-bubble {
|
||||
background: var(--nord2);
|
||||
border-color: var(--blue);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .activity-bubble {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .activity-message.is-me .activity-bubble {
|
||||
background: var(--nord2);
|
||||
border-color: var(--blue);
|
||||
}
|
||||
|
||||
.activity-bubble:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
@@ -564,39 +473,23 @@
|
||||
|
||||
.activity-bubble::before {
|
||||
left: -15px;
|
||||
border-right-color: var(--nord5);
|
||||
border-right-color: var(--color-bg-secondary);
|
||||
}
|
||||
|
||||
.activity-message.is-me .activity-bubble::before {
|
||||
left: auto;
|
||||
right: -15px;
|
||||
border-left-color: var(--nord8);
|
||||
border-left-color: var(--color-bg-tertiary);
|
||||
border-right-color: transparent;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .activity-bubble::before {
|
||||
border-right-color: var(--nord1);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .activity-message.is-me .activity-bubble::before {
|
||||
border-left-color: var(--nord2);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .activity-bubble::before {
|
||||
border-right-color: var(--nord1);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .activity-message.is-me .activity-bubble::before {
|
||||
border-left-color: var(--nord2);
|
||||
}
|
||||
|
||||
|
||||
/* New Settlement Flow Activity Styles */
|
||||
/* Settlement Flow Activity Styles */
|
||||
.settlement-flow-activity {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
background: linear-gradient(135deg, var(--nord6), var(--nord5));
|
||||
background: var(--color-bg-secondary);
|
||||
border: 2px solid var(--green);
|
||||
border-radius: 1rem;
|
||||
padding: 1.5rem;
|
||||
@@ -610,24 +503,6 @@
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .settlement-flow-activity {
|
||||
background: linear-gradient(135deg, var(--nord2), var(--nord1));
|
||||
border-color: var(--green);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .settlement-flow-activity:hover {
|
||||
box-shadow: 0 6px 20px rgba(163, 190, 140, 0.2);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .settlement-flow-activity {
|
||||
background: linear-gradient(135deg, var(--nord2), var(--nord1));
|
||||
border-color: var(--green);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .settlement-flow-activity:hover {
|
||||
box-shadow: 0 6px 20px rgba(163, 190, 140, 0.2);
|
||||
}
|
||||
|
||||
.settlement-activity-content {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -677,19 +552,10 @@
|
||||
|
||||
.settlement-date {
|
||||
font-size: 0.9rem;
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .settlement-date {
|
||||
color: var(--nord4);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .settlement-date {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
.activity-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -715,47 +581,24 @@
|
||||
}
|
||||
|
||||
.category-name {
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-tertiary);
|
||||
font-size: 0.8rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.payment-title {
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.username {
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .category-name {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .payment-title {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .username {
|
||||
color: var(--nord4);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .category-name {
|
||||
color: var(--nord4);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .payment-title {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .username {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
.activity-amount {
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
@@ -775,34 +618,18 @@
|
||||
}
|
||||
|
||||
.payment-date {
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.payment-description {
|
||||
color: var(--nord2);
|
||||
color: var(--color-text-tertiary);
|
||||
font-size: 0.9rem;
|
||||
font-style: italic;
|
||||
margin-top: 0.25rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .payment-date {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .payment-description {
|
||||
color: var(--nord5);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .payment-date {
|
||||
color: var(--nord4);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .payment-description {
|
||||
color: var(--nord5);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.cospend-main {
|
||||
padding: 0.75rem;
|
||||
@@ -814,7 +641,7 @@
|
||||
}
|
||||
|
||||
.recent-activity {
|
||||
padding: 0.75rem;
|
||||
padding: 0.75rem 0;
|
||||
}
|
||||
|
||||
.actions {
|
||||
@@ -918,25 +745,10 @@
|
||||
}
|
||||
|
||||
.chart-section .loading {
|
||||
background: var(--nord6);
|
||||
background: var(--color-bg-secondary);
|
||||
border-radius: 0.75rem;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid var(--nord4);
|
||||
text-align: center;
|
||||
color: var(--nord2);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .chart-section .loading {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
color: var(--nord4);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .chart-section .loading {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
color: var(--nord4);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -289,23 +289,13 @@
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
|
||||
h1 {
|
||||
margin-block: 0 1rem;
|
||||
margin-inline: auto;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) h1 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) h1 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
@@ -328,35 +318,15 @@
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: var(--nord5);
|
||||
color: var(--nord0);
|
||||
border: 1px solid var(--nord4);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
color: var(--color-text-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: var(--nord4);
|
||||
background-color: var(--color-bg-elevated);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .btn-secondary {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .btn-secondary:hover {
|
||||
background-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .btn-secondary {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .btn-secondary:hover {
|
||||
background-color: var(--nord3);
|
||||
}
|
||||
|
||||
.loading, .error {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
@@ -365,63 +335,31 @@
|
||||
|
||||
.error {
|
||||
color: var(--red);
|
||||
background-color: var(--nord6);
|
||||
background-color: var(--color-bg-secondary);
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--red);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .error {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .error {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 4rem 2rem;
|
||||
}
|
||||
|
||||
.empty-content svg {
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.empty-content h2 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
color: var(--nord1);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.empty-content p {
|
||||
margin: 0 0 2rem 0;
|
||||
color: var(--nord2);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .empty-content svg {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .empty-content h2 {
|
||||
color: var(--nord5);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .empty-content p {
|
||||
color: var(--nord4);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .empty-content svg {
|
||||
color: var(--nord4);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .empty-content h2 {
|
||||
color: var(--nord5);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .empty-content p {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
.payments-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
@@ -431,14 +369,14 @@
|
||||
|
||||
.payment-card {
|
||||
display: block;
|
||||
background: var(--nord6);
|
||||
background: var(--color-surface);
|
||||
border-radius: 0.75rem;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.2s;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.payment-card:hover {
|
||||
@@ -446,27 +384,8 @@
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transform: translateY(-1px);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .payment-card {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .payment-card:hover {
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .payment-card {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .payment-card:hover {
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
/* Settlement Card Styles */
|
||||
.settlement-card {
|
||||
background: linear-gradient(135deg, #e8f5e9, #f1f8e9);
|
||||
@@ -494,17 +413,10 @@
|
||||
:global(:root:not([data-theme="light"])) .settlement-card {
|
||||
background: linear-gradient(135deg, #1a2e1a, #1e2b1e);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .settlement-card:hover {
|
||||
box-shadow: 0 6px 20px rgba(163, 190, 140, 0.3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .settlement-card {
|
||||
background: linear-gradient(135deg, #1a2e1a, #1e2b1e);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .settlement-card:hover {
|
||||
box-shadow: 0 6px 20px rgba(163, 190, 140, 0.3);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .settlement-card {
|
||||
background: linear-gradient(135deg, #1a2e1a, #1e2b1e);
|
||||
}
|
||||
|
||||
.settlement-header {
|
||||
display: flex;
|
||||
@@ -531,20 +443,11 @@
|
||||
}
|
||||
|
||||
.settlement-date {
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .settlement-date {
|
||||
color: var(--nord4);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .settlement-date {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
.settlement-flow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -594,25 +497,14 @@
|
||||
}
|
||||
|
||||
.settlement-description {
|
||||
color: var(--nord2);
|
||||
color: var(--color-text-tertiary);
|
||||
margin-top: 1rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--nord4);
|
||||
border-top: 1px solid var(--color-border);
|
||||
font-style: italic;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .settlement-description {
|
||||
color: var(--nord5);
|
||||
border-top-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .settlement-description {
|
||||
color: var(--nord5);
|
||||
border-top-color: var(--nord3);
|
||||
}
|
||||
|
||||
.payment-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -641,29 +533,20 @@
|
||||
|
||||
.payment-title h3 {
|
||||
margin: 0;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .payment-title h3 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .payment-title h3 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.payment-meta {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.payment-meta .category-name {
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
font-style: italic;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
@@ -673,54 +556,20 @@
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .payment-meta {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .payment-meta .category-name {
|
||||
color: var(--nord4);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .payment-meta {
|
||||
color: var(--nord4);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .payment-meta .category-name {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
.receipt-thumb {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
object-fit: cover;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .receipt-thumb {
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .receipt-thumb {
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
|
||||
.payment-description {
|
||||
color: var(--nord2);
|
||||
color: var(--color-text-tertiary);
|
||||
margin-bottom: 1rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .payment-description {
|
||||
color: var(--nord5);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .payment-description {
|
||||
color: var(--nord5);
|
||||
}
|
||||
|
||||
.payment-details {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
@@ -732,58 +581,26 @@
|
||||
}
|
||||
|
||||
.detail-row .label {
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.detail-row .value {
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .detail-row .label {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .detail-row .value {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .detail-row .label {
|
||||
color: var(--nord4);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .detail-row .value {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.splits-summary {
|
||||
border-top: 1px solid var(--nord4);
|
||||
border-top: 1px solid var(--color-border);
|
||||
padding-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.splits-summary h4 {
|
||||
margin: 0 0 0.75rem 0;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .splits-summary {
|
||||
border-top-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .splits-summary h4 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .splits-summary {
|
||||
border-top-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .splits-summary h4 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.splits-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -797,7 +614,7 @@
|
||||
}
|
||||
|
||||
.split-user {
|
||||
color: var(--nord2);
|
||||
color: var(--color-text-tertiary);
|
||||
}
|
||||
|
||||
.split-amount.positive {
|
||||
@@ -810,16 +627,6 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .split-user {
|
||||
color: var(--nord5);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .split-user {
|
||||
color: var(--nord5);
|
||||
}
|
||||
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
import SplitMethodSelector from '$lib/components/cospend/SplitMethodSelector.svelte';
|
||||
import UsersList from '$lib/components/cospend/UsersList.svelte';
|
||||
import ImageUpload from '$lib/components/ImageUpload.svelte';
|
||||
import SaveFab from '$lib/components/SaveFab.svelte';
|
||||
import Toggle from '$lib/components/Toggle.svelte';
|
||||
|
||||
let { data, form } = $props();
|
||||
|
||||
@@ -465,13 +467,9 @@
|
||||
|
||||
<div class="form-group">
|
||||
<label class="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="isRecurring"
|
||||
bind:checked={formData.isRecurring}
|
||||
value="true"
|
||||
/>
|
||||
Make this a recurring payment
|
||||
<Toggle bind:checked={formData.isRecurring} />
|
||||
<span>Make this a recurring payment</span>
|
||||
<input type="hidden" name="isRecurring" value={formData.isRecurring ? 'true' : 'false'} />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -612,14 +610,7 @@
|
||||
<div class="error">{error}</div>
|
||||
{/if}
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="/cospend" class="btn-secondary">
|
||||
Cancel
|
||||
</a>
|
||||
<button type="submit" class="btn-primary" disabled={loading}>
|
||||
{loading ? 'Creating...' : (formData.isRecurring ? 'Create Recurring Payment' : 'Create Payment')}
|
||||
</button>
|
||||
</div>
|
||||
<SaveFab disabled={loading} label="Create payment" />
|
||||
</form>
|
||||
</main>
|
||||
|
||||
@@ -637,32 +628,16 @@
|
||||
|
||||
.header h1 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.header p {
|
||||
margin: 0;
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .header h1 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .header p {
|
||||
color: var(--nord4);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .header h1 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .header p {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
.payment-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -670,38 +645,20 @@
|
||||
}
|
||||
|
||||
.form-section {
|
||||
background: var(--nord6);
|
||||
background: var(--color-surface);
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.form-section h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .form-section {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .form-section h2 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .form-section {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .form-section h2 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
@@ -716,27 +673,18 @@
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
color: var(--nord2);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) label {
|
||||
color: var(--nord5);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) label {
|
||||
color: var(--nord5);
|
||||
}
|
||||
|
||||
input, textarea, select {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1rem;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--nord6);
|
||||
color: var(--nord0);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
input:focus, textarea:focus, select:focus {
|
||||
@@ -745,29 +693,12 @@
|
||||
box-shadow: 0 0 0 2px rgba(94, 129, 172, 0.2);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) input,
|
||||
:global(:root:not([data-theme="light"])) textarea,
|
||||
:global(:root:not([data-theme="light"])) select {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) input,
|
||||
:global(:root[data-theme="dark"]) textarea,
|
||||
:global(:root[data-theme="dark"]) select {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.error {
|
||||
background-color: var(--nord6);
|
||||
background-color: var(--color-bg-secondary);
|
||||
color: var(--red);
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
@@ -775,81 +706,6 @@
|
||||
border: 1px solid var(--red);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .error {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .error {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.btn-primary, .btn-secondary {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--blue);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background-color: var(--nord10);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: var(--nord5);
|
||||
color: var(--nord0);
|
||||
border: 1px solid var(--nord4);
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: var(--nord4);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .btn-secondary {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .btn-secondary:hover {
|
||||
background-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .btn-secondary {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .btn-secondary:hover {
|
||||
background-color: var(--nord3);
|
||||
}
|
||||
|
||||
|
||||
/* Progressive enhancement styles */
|
||||
.no-js-only {
|
||||
display: block;
|
||||
@@ -862,7 +718,7 @@
|
||||
.manual-users textarea {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #ddd;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.5rem;
|
||||
font-family: inherit;
|
||||
font-size: 0.9rem;
|
||||
@@ -872,91 +728,46 @@
|
||||
.manual-users p {
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 0.9rem;
|
||||
color: var(--nord2);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .manual-users p {
|
||||
color: var(--nord4);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .manual-users p {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
/* Recurring payment styles */
|
||||
.checkbox-label {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
gap: 0.75rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.checkbox-label input[type="checkbox"] {
|
||||
width: auto;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.recurring-options {
|
||||
margin-top: 1rem;
|
||||
padding: 1rem;
|
||||
background-color: var(--nord5);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .recurring-options {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .recurring-options {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
.help-text {
|
||||
display: block;
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .help-text {
|
||||
color: var(--nord4);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .help-text {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
.help-text p {
|
||||
margin: 0.5rem 0 0.25rem 0;
|
||||
}
|
||||
|
||||
.help-text code {
|
||||
background-color: var(--nord5);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
padding: 0.125rem 0.25rem;
|
||||
border-radius: 0.25rem;
|
||||
font-family: monospace;
|
||||
font-size: 0.85em;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .help-text code {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .help-text code {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
.help-text ul {
|
||||
margin: 0.5rem 0;
|
||||
padding-left: 1rem;
|
||||
@@ -979,7 +790,7 @@
|
||||
}
|
||||
|
||||
.execution-preview {
|
||||
background-color: var(--nord8);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
border: 1px solid var(--blue);
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
@@ -1000,28 +811,12 @@
|
||||
}
|
||||
|
||||
.frequency-description {
|
||||
color: var(--nord2);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.9rem;
|
||||
margin: 0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .execution-preview {
|
||||
background-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .frequency-description {
|
||||
color: var(--nord4);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .execution-preview {
|
||||
background-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .frequency-description {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
/* Amount-currency styling */
|
||||
.amount-currency {
|
||||
display: flex;
|
||||
@@ -1050,21 +845,21 @@
|
||||
}
|
||||
|
||||
.conversion-preview.loading {
|
||||
background-color: var(--nord8);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
border-color: var(--blue);
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.conversion-preview.error {
|
||||
background-color: var(--nord6);
|
||||
background-color: var(--color-bg-secondary);
|
||||
border-color: var(--red);
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.conversion-preview.success {
|
||||
background-color: var(--nord14);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
border-color: var(--green);
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.conversion-preview small {
|
||||
@@ -1072,31 +867,6 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .conversion-preview.loading {
|
||||
background-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .conversion-preview.error {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .conversion-preview.success {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .conversion-preview.loading {
|
||||
background-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .conversion-preview.error {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .conversion-preview.success {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.add-payment {
|
||||
padding: 1rem;
|
||||
@@ -1106,10 +876,6 @@
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.amount-currency {
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -1119,4 +885,5 @@
|
||||
flex: none;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -4,6 +4,7 @@
|
||||
import { getCategoryOptions } from '$lib/utils/categories';
|
||||
import FormSection from '$lib/components/FormSection.svelte';
|
||||
import ImageUpload from '$lib/components/ImageUpload.svelte';
|
||||
import SaveFab from '$lib/components/SaveFab.svelte';
|
||||
|
||||
/**
|
||||
* @typedef {import('$models/Payment').IPayment & {splits?: import('$models/PaymentSplit').IPaymentSplit[]}} PaymentWithSplits
|
||||
@@ -582,15 +583,9 @@
|
||||
>
|
||||
{deleting ? 'Deleting...' : 'Delete Payment'}
|
||||
</button>
|
||||
<div class="main-actions">
|
||||
<button type="button" class="btn-secondary" onclick={() => goto('/cospend/payments')}>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" class="btn-primary" disabled={saving || deleting}>
|
||||
{saving ? 'Saving...' : 'Save Changes'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SaveFab disabled={saving || deleting} label="Save changes" />
|
||||
</form>
|
||||
{/if}
|
||||
</main>
|
||||
@@ -609,32 +604,16 @@
|
||||
|
||||
.header h1 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.header p {
|
||||
margin: 0;
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .header h1 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .header p {
|
||||
color: var(--nord4);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .header h1 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .header p {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
.loading, .error {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
@@ -643,20 +622,11 @@
|
||||
|
||||
.error {
|
||||
color: var(--red);
|
||||
background-color: var(--nord6);
|
||||
background-color: var(--color-bg-secondary);
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--red);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .error {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .error {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
|
||||
.payment-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -677,27 +647,18 @@
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
color: var(--nord2);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) label {
|
||||
color: var(--nord5);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) label {
|
||||
color: var(--nord5);
|
||||
}
|
||||
|
||||
input, textarea, select {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1rem;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--nord6);
|
||||
color: var(--nord0);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
input:focus, textarea:focus, select:focus {
|
||||
@@ -710,48 +671,20 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) input,
|
||||
:global(:root:not([data-theme="light"])) textarea,
|
||||
:global(:root:not([data-theme="light"])) select {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) input,
|
||||
:global(:root[data-theme="dark"]) textarea,
|
||||
:global(:root[data-theme="dark"]) select {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
.split-method-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.75rem;
|
||||
background-color: var(--nord14);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--green);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .split-method-info {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .split-method-info {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
.split-method-info .label {
|
||||
font-weight: 600;
|
||||
color: var(--nord1);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.split-method-info .value {
|
||||
@@ -759,66 +692,28 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .split-method-info .label {
|
||||
color: var(--nord5);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .split-method-info .label {
|
||||
color: var(--nord5);
|
||||
}
|
||||
|
||||
.personal-amounts-editor {
|
||||
margin-bottom: 1.5rem;
|
||||
padding: 1rem;
|
||||
background-color: var(--nord5);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .personal-amounts-editor {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .personal-amounts-editor {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
.personal-amounts-editor h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .personal-amounts-editor h3 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .personal-amounts-editor h3 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.personal-amounts-editor .description {
|
||||
color: var(--nord2);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 1rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .personal-amounts-editor .description {
|
||||
color: var(--nord4);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .personal-amounts-editor .description {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
.personal-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -835,11 +730,11 @@
|
||||
.personal-input input {
|
||||
max-width: 150px;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1rem;
|
||||
background-color: var(--nord6);
|
||||
color: var(--nord0);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.personal-input input:focus {
|
||||
@@ -848,68 +743,26 @@
|
||||
box-shadow: 0 0 0 2px rgba(94, 129, 172, 0.2);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .personal-input input {
|
||||
background-color: var(--nord1);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .personal-input input {
|
||||
background-color: var(--nord1);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
.remainder-info {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem;
|
||||
background-color: var(--nord14);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--green);
|
||||
}
|
||||
|
||||
.remainder-info.error {
|
||||
background-color: var(--nord6);
|
||||
background-color: var(--color-bg-secondary);
|
||||
border-color: var(--red);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .remainder-info {
|
||||
background-color: var(--nord1);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .remainder-info.error {
|
||||
background-color: var(--accent-dark);
|
||||
border-color: var(--red);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .remainder-info {
|
||||
background-color: var(--nord1);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .remainder-info.error {
|
||||
background-color: var(--accent-dark);
|
||||
border-color: var(--red);
|
||||
}
|
||||
|
||||
.remainder-info span {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .remainder-info span {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .remainder-info span {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: var(--red);
|
||||
font-weight: 600;
|
||||
@@ -927,54 +780,25 @@
|
||||
.splits-display h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .splits-display h3 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .splits-display h3 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.split-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.75rem;
|
||||
background-color: var(--nord5);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .split-item {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .split-item {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
.split-username {
|
||||
font-weight: 500;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .split-username {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .split-username {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.split-amount.positive {
|
||||
color: var(--green);
|
||||
font-weight: 500;
|
||||
@@ -986,21 +810,12 @@
|
||||
}
|
||||
|
||||
.note {
|
||||
color: var(--nord2);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.9rem;
|
||||
font-style: italic;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .note {
|
||||
color: var(--nord4);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .note {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
.js-only {
|
||||
display: none;
|
||||
}
|
||||
@@ -1024,68 +839,12 @@
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.main-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.btn-primary, .btn-secondary, .btn-danger {
|
||||
.btn-danger {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--blue);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background-color: var(--nord10);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: var(--nord5);
|
||||
color: var(--nord0);
|
||||
border: 1px solid var(--nord4);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: var(--nord4);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .btn-secondary {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .btn-secondary:hover {
|
||||
background-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .btn-secondary {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .btn-secondary:hover {
|
||||
background-color: var(--nord3);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: var(--red);
|
||||
color: white;
|
||||
border: none;
|
||||
@@ -1129,21 +888,21 @@
|
||||
}
|
||||
|
||||
.conversion-preview.loading {
|
||||
background-color: var(--nord8);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
border-color: var(--blue);
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.conversion-preview.error {
|
||||
background-color: var(--nord6);
|
||||
background-color: var(--color-bg-secondary);
|
||||
border-color: var(--red);
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.conversion-preview.success {
|
||||
background-color: var(--nord14);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
border-color: var(--green);
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.conversion-preview small {
|
||||
@@ -1155,42 +914,10 @@
|
||||
display: block;
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .conversion-preview.loading {
|
||||
background-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .conversion-preview.error {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .conversion-preview.success {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .help-text {
|
||||
color: var(--nord4);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .conversion-preview.loading {
|
||||
background-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .conversion-preview.error {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .conversion-preview.success {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .help-text {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.edit-payment {
|
||||
padding: 1rem;
|
||||
@@ -1232,4 +959,5 @@
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -4,7 +4,7 @@
|
||||
import ProfilePicture from '$lib/components/cospend/ProfilePicture.svelte';
|
||||
import { getCategoryEmoji, getCategoryName } from '$lib/utils/categories';
|
||||
import EditButton from '$lib/components/EditButton.svelte';
|
||||
|
||||
|
||||
|
||||
import { formatCurrency } from '$lib/utils/formatters';
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
onMount(async () => {
|
||||
// Mark that JavaScript is loaded
|
||||
document.body.classList.add('js-loaded');
|
||||
|
||||
|
||||
// Only refresh if we don't have server data
|
||||
if (!payment) {
|
||||
await loadPayment();
|
||||
@@ -48,7 +48,7 @@
|
||||
if (payment.currency === 'CHF' || !payment.originalAmount) {
|
||||
return formatCurrency(payment.amount, 'CHF', 'de-CH');
|
||||
}
|
||||
|
||||
|
||||
return `${formatCurrency(payment.originalAmount, payment.currency, 'de-CH')} ≈ ${formatCurrency(payment.amount, 'CHF', 'de-CH')}`;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
function getSplitDescription(/** @type {any} */ payment) {
|
||||
if (!payment.splits || payment.splits.length === 0) return 'No splits';
|
||||
|
||||
|
||||
if (payment.splitMethod === 'equal') {
|
||||
return `Split equally among ${payment.splits.length} people`;
|
||||
} else if (payment.splitMethod === 'full') {
|
||||
@@ -182,8 +182,6 @@
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.loading, .error {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
@@ -192,57 +190,27 @@
|
||||
|
||||
.error {
|
||||
color: var(--red);
|
||||
background-color: var(--nord6);
|
||||
background-color: var(--color-bg-secondary);
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--red);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .error {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .error {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
|
||||
.payment-card {
|
||||
background: var(--nord6);
|
||||
background: var(--color-surface);
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .payment-card {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .payment-card {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
|
||||
.payment-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
padding: 2rem;
|
||||
background: linear-gradient(135deg, var(--nord5), var(--nord4));
|
||||
border-bottom: 1px solid var(--nord3);
|
||||
background: var(--color-bg-tertiary);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .payment-header {
|
||||
background: linear-gradient(135deg, var(--nord2), var(--nord3));
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .payment-header {
|
||||
background: linear-gradient(135deg, var(--nord2), var(--nord3));
|
||||
}
|
||||
|
||||
.title-with-category {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -257,7 +225,7 @@
|
||||
|
||||
.title-section h1 {
|
||||
margin: 0;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
|
||||
@@ -267,15 +235,6 @@
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .title-section h1 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .title-section h1 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.receipt-image {
|
||||
flex-shrink: 0;
|
||||
margin-left: 2rem;
|
||||
@@ -286,18 +245,9 @@
|
||||
max-height: 150px;
|
||||
object-fit: cover;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .receipt-image img {
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .receipt-image img {
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
|
||||
.payment-info {
|
||||
padding: 2rem;
|
||||
}
|
||||
@@ -317,100 +267,43 @@
|
||||
|
||||
.label {
|
||||
font-weight: 600;
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.9rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .label {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .value {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .label {
|
||||
color: var(--nord4);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .value {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.description {
|
||||
border-top: 1px solid var(--nord4);
|
||||
padding-top: 1.5rem;
|
||||
}
|
||||
|
||||
.description h3 {
|
||||
margin: 0 0 0.75rem 0;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.description p {
|
||||
margin: 0;
|
||||
color: var(--nord2);
|
||||
color: var(--color-text-tertiary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .description {
|
||||
border-top-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .description h3 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .description p {
|
||||
color: var(--nord5);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .description {
|
||||
border-top-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .description h3 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .description p {
|
||||
color: var(--nord5);
|
||||
}
|
||||
|
||||
.splits-section {
|
||||
border-top: 1px solid var(--nord4);
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.splits-section h3 {
|
||||
margin: 0 0 1rem 0;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .splits-section {
|
||||
border-top-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .splits-section h3 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .splits-section {
|
||||
border-top-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .splits-section h3 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.splits-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -422,36 +315,14 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
background: var(--nord5);
|
||||
background: var(--color-bg-primary);
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--nord4);
|
||||
}
|
||||
|
||||
.split-item.current-user {
|
||||
background: var(--nord8);
|
||||
border-color: var(--blue);
|
||||
background: var(--color-bg-tertiary);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .split-item {
|
||||
background: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .split-item.current-user {
|
||||
background: var(--nord3);
|
||||
border-color: var(--blue);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .split-item {
|
||||
background: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .split-item.current-user {
|
||||
background: var(--nord3);
|
||||
border-color: var(--blue);
|
||||
}
|
||||
|
||||
.split-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -466,7 +337,7 @@
|
||||
|
||||
.username {
|
||||
font-weight: 500;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.you-badge {
|
||||
@@ -478,15 +349,6 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .username {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .username {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.split-amount {
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
@@ -502,7 +364,7 @@
|
||||
|
||||
.exchange-rate-info {
|
||||
margin-top: 0.5rem;
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@@ -510,15 +372,6 @@
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .exchange-rate-info {
|
||||
color: var(--nord4);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .exchange-rate-info {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.payment-view {
|
||||
padding: 1rem;
|
||||
@@ -544,4 +397,4 @@
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { toast } from '$lib/js/toast.svelte';
|
||||
import AddButton from '$lib/components/AddButton.svelte';
|
||||
import { formatCurrency } from '$lib/utils/formatters';
|
||||
import Toggle from '$lib/components/Toggle.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
@@ -102,8 +103,8 @@
|
||||
|
||||
<div class="filters">
|
||||
<label>
|
||||
<input type="checkbox" bind:checked={showActiveOnly} />
|
||||
Show active only
|
||||
<Toggle bind:checked={showActiveOnly} />
|
||||
<span>Show active only</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -143,7 +144,7 @@
|
||||
<span class="label">Category:</span>
|
||||
<span class="value">{getCategoryName(payment.category)}</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="detail-row">
|
||||
<span class="label">Frequency:</span>
|
||||
<span class="value">{getFrequencyDescription(payment)}</span>
|
||||
@@ -241,68 +242,33 @@
|
||||
|
||||
.header h1 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.header p {
|
||||
margin: 0;
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .header h1 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .header p {
|
||||
color: var(--nord4);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .header h1 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .header p {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
.filters {
|
||||
margin-bottom: 1.5rem;
|
||||
padding: 1rem;
|
||||
background: var(--nord6);
|
||||
background: var(--color-surface);
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.filters label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
gap: 0.75rem;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .filters {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .filters label {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .filters {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .filters label {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.loading, .error {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
@@ -311,67 +277,32 @@
|
||||
|
||||
.error {
|
||||
color: var(--red);
|
||||
background-color: var(--nord6);
|
||||
background-color: var(--color-bg-secondary);
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--red);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .error {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .error {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 4rem 2rem;
|
||||
background: var(--nord6);
|
||||
background: var(--color-surface);
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.empty-state h2 {
|
||||
margin-bottom: 1rem;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
color: var(--nord2);
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: 2rem;
|
||||
max-width: 500px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .empty-state {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .empty-state h2 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .empty-state p {
|
||||
color: var(--nord4);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .empty-state {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .empty-state h2 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .empty-state p {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
.payments-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
|
||||
@@ -379,53 +310,24 @@
|
||||
}
|
||||
|
||||
.payment-card {
|
||||
background: var(--nord6);
|
||||
background: var(--color-surface);
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
padding: 1.5rem;
|
||||
transition: all 0.2s;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.payment-card:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
.payment-card.inactive {
|
||||
opacity: 0.7;
|
||||
background: var(--nord5);
|
||||
border-color: var(--nord3);
|
||||
background: var(--color-bg-tertiary);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .payment-card {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .payment-card:hover {
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .payment-card.inactive {
|
||||
background: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .payment-card {
|
||||
background: var(--nord1);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .payment-card:hover {
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .payment-card.inactive {
|
||||
background: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -446,19 +348,10 @@
|
||||
|
||||
.payment-title h3 {
|
||||
margin: 0;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .payment-title h3 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .payment-title h3 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 1rem;
|
||||
@@ -485,20 +378,11 @@
|
||||
}
|
||||
|
||||
.payment-description {
|
||||
color: var(--nord2);
|
||||
color: var(--color-text-tertiary);
|
||||
margin-bottom: 1rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .payment-description {
|
||||
color: var(--nord5);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .payment-description {
|
||||
color: var(--nord5);
|
||||
}
|
||||
|
||||
.payment-details {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
@@ -513,12 +397,12 @@
|
||||
|
||||
.label {
|
||||
font-weight: 500;
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@@ -527,22 +411,6 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .label {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .value {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .label {
|
||||
color: var(--nord4);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .value {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.payer-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -552,37 +420,19 @@
|
||||
.splits-preview {
|
||||
margin-bottom: 1.5rem;
|
||||
padding: 1rem;
|
||||
background-color: var(--nord5);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.splits-preview h4 {
|
||||
margin: 0 0 0.75rem 0;
|
||||
font-size: 0.9rem;
|
||||
color: var(--nord2);
|
||||
color: var(--color-text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .splits-preview {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .splits-preview h4 {
|
||||
color: var(--nord4);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .splits-preview {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .splits-preview h4 {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
.splits-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -598,7 +448,7 @@
|
||||
.split-item .username {
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.split-amount {
|
||||
@@ -614,15 +464,6 @@
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .split-item .username {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .split-item .username {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
@@ -657,35 +498,15 @@
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: var(--nord5);
|
||||
color: var(--nord0);
|
||||
border: 1px solid var(--nord4);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
color: var(--color-text-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: var(--nord4);
|
||||
background-color: var(--color-bg-elevated);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .btn-secondary {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .btn-secondary:hover {
|
||||
background-color: var(--nord3);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .btn-secondary {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord3);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .btn-secondary:hover {
|
||||
background-color: var(--nord3);
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background-color: var(--orange);
|
||||
color: white;
|
||||
@@ -758,4 +579,4 @@
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import ProfilePicture from '$lib/components/cospend/ProfilePicture.svelte';
|
||||
import SplitMethodSelector from '$lib/components/cospend/SplitMethodSelector.svelte';
|
||||
import UsersList from '$lib/components/cospend/UsersList.svelte';
|
||||
import SaveFab from '$lib/components/SaveFab.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
@@ -288,9 +289,6 @@
|
||||
<main class="edit-recurring-payment">
|
||||
<div class="header">
|
||||
<h1>Edit Recurring Payment</h1>
|
||||
<div class="header-actions">
|
||||
<a href="/cospend/recurring" class="back-link">← Back to Recurring Payments</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if loadingPayment}
|
||||
@@ -491,14 +489,7 @@
|
||||
<div class="error">{error}</div>
|
||||
{/if}
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="button" class="btn-secondary" onclick={() => goto('/cospend/recurring')}>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" class="btn-primary" disabled={loading || cronError}>
|
||||
{loading ? 'Saving...' : 'Save Changes'}
|
||||
</button>
|
||||
</div>
|
||||
<SaveFab disabled={loading || cronError} label="Save changes" />
|
||||
</form>
|
||||
{/if}
|
||||
</main>
|
||||
@@ -519,12 +510,7 @@
|
||||
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
color: var(--nord0);
|
||||
}
|
||||
|
||||
.back-link {
|
||||
color: var(--blue);
|
||||
text-decoration: none;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.loading {
|
||||
@@ -540,17 +526,17 @@
|
||||
}
|
||||
|
||||
.form-section {
|
||||
background: var(--nord6);
|
||||
background: var(--color-surface);
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.form-section h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
@@ -568,18 +554,18 @@
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
input, textarea, select {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--nord4);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1rem;
|
||||
box-sizing: border-box;
|
||||
background: var(--nord5);
|
||||
color: var(--nord0);
|
||||
background: var(--color-bg-tertiary);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
input:focus, textarea:focus, select:focus {
|
||||
@@ -594,16 +580,16 @@
|
||||
|
||||
.help-text {
|
||||
margin-top: 0.5rem;
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.help-text code {
|
||||
background-color: var(--nord5);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
padding: 0.125rem 0.25rem;
|
||||
border-radius: 0.25rem;
|
||||
font-family: monospace;
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.help-text ul {
|
||||
@@ -622,7 +608,7 @@
|
||||
}
|
||||
|
||||
.execution-preview {
|
||||
background-color: var(--nord8);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
border: 1px solid var(--blue);
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
@@ -643,17 +629,14 @@
|
||||
}
|
||||
|
||||
.frequency-description {
|
||||
color: var(--nord3);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.9rem;
|
||||
margin: 0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.error {
|
||||
background-color: var(--nord6);
|
||||
background-color: var(--color-bg-secondary);
|
||||
color: var(--red);
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
@@ -661,177 +644,6 @@
|
||||
border: 1px solid var(--red);
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.btn-primary, .btn-secondary {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--blue);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background-color: var(--lightblue);
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: var(--nord5);
|
||||
color: var(--nord0);
|
||||
border: 1px solid var(--nord4);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: var(--nord4);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:global(:root:not([data-theme="light"])) .header h1 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .form-section {
|
||||
background: var(--accent-dark);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .form-section h2 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) label {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) input,
|
||||
:global(:root:not([data-theme="light"])) textarea,
|
||||
:global(:root:not([data-theme="light"])) select {
|
||||
background: var(--nord1);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) input:focus,
|
||||
:global(:root:not([data-theme="light"])) textarea:focus,
|
||||
:global(:root:not([data-theme="light"])) select:focus {
|
||||
box-shadow: 0 0 0 2px rgba(136, 192, 208, 0.2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .help-text {
|
||||
color: var(--nord4);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .help-text code {
|
||||
background-color: var(--nord1);
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .execution-preview {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--blue);
|
||||
}
|
||||
|
||||
|
||||
:global(:root:not([data-theme="light"])) .error {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .btn-secondary {
|
||||
background-color: var(--nord1);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .btn-secondary:hover {
|
||||
background-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .conversion-preview.loading {
|
||||
background-color: var(--nord2);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .conversion-preview.error {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
|
||||
:global(:root:not([data-theme="light"])) .conversion-preview.success {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .header h1 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .form-section {
|
||||
background: var(--accent-dark);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .form-section h2 {
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) label {
|
||||
color: var(--nord4);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) input,
|
||||
:global(:root[data-theme="dark"]) textarea,
|
||||
:global(:root[data-theme="dark"]) select {
|
||||
background: var(--nord1);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) input:focus,
|
||||
:global(:root[data-theme="dark"]) textarea:focus,
|
||||
:global(:root[data-theme="dark"]) select:focus {
|
||||
box-shadow: 0 0 0 2px rgba(136, 192, 208, 0.2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .help-text {
|
||||
color: var(--nord4);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .help-text code {
|
||||
background-color: var(--nord1);
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .execution-preview {
|
||||
background-color: var(--nord2);
|
||||
border-color: var(--blue);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .error {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .btn-secondary {
|
||||
background-color: var(--nord1);
|
||||
color: var(--font-default-dark);
|
||||
border-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .btn-secondary:hover {
|
||||
background-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .conversion-preview.loading {
|
||||
background-color: var(--nord2);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .conversion-preview.error {
|
||||
background-color: var(--accent-dark);
|
||||
}
|
||||
:global(:root[data-theme="dark"]) .conversion-preview.success {
|
||||
background-color: var(--nord2);
|
||||
color: var(--font-default-dark);
|
||||
}
|
||||
|
||||
/* Amount-currency styling */
|
||||
.amount-currency {
|
||||
display: flex;
|
||||
@@ -860,21 +672,21 @@
|
||||
}
|
||||
|
||||
.conversion-preview.loading {
|
||||
background-color: var(--nord8);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
border-color: var(--blue);
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.conversion-preview.error {
|
||||
background-color: var(--nord6);
|
||||
background-color: var(--color-bg-secondary);
|
||||
border-color: var(--red);
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.conversion-preview.success {
|
||||
background-color: var(--nord14);
|
||||
background-color: var(--color-bg-tertiary);
|
||||
border-color: var(--green);
|
||||
color: var(--nord0);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.conversion-preview small {
|
||||
@@ -891,10 +703,6 @@
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.amount-currency {
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -904,4 +712,5 @@
|
||||
flex: none;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,22 +1,22 @@
|
||||
<script>
|
||||
import { page } from '$app/stores';
|
||||
import { Pencil, Trash2 } from 'lucide-svelte';
|
||||
import { Pencil, Trash2, ChevronDown } from 'lucide-svelte';
|
||||
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
|
||||
import { toast } from '$lib/js/toast.svelte';
|
||||
|
||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
||||
const measureSlug = $derived(lang === 'en' ? 'measure' : 'messen');
|
||||
import { getWorkout } from '$lib/js/workout.svelte';
|
||||
import AddActionButton from '$lib/components/AddActionButton.svelte';
|
||||
import AddButton from '$lib/components/AddButton.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
const workout = getWorkout();
|
||||
|
||||
let latest = $state(data.latest ? { ...data.latest } : {});
|
||||
let measurements = $state(data.measurements?.measurements ? [...data.measurements.measurements] : []);
|
||||
let showForm = $state(false);
|
||||
let saving = $state(false);
|
||||
|
||||
// Profile fields (sex, height) — stored in FitnessGoal
|
||||
let showProfile = $state(false);
|
||||
let profileSex = $state(data.profile?.sex ?? 'male');
|
||||
let profileHeight = $state(data.profile?.heightCm != null ? String(data.profile.heightCm) : '');
|
||||
let profileSaving = $state(false);
|
||||
@@ -48,27 +48,6 @@
|
||||
profileSaving = false;
|
||||
}
|
||||
}
|
||||
/** @type {string | null} */
|
||||
let editingId = $state(null);
|
||||
|
||||
// Form fields
|
||||
let formDate = $state('');
|
||||
let formWeight = $state('');
|
||||
let formBodyFat = $state('');
|
||||
let formCalories = $state('');
|
||||
let formNeck = $state('');
|
||||
let formShoulders = $state('');
|
||||
let formChest = $state('');
|
||||
let formBicepsL = $state('');
|
||||
let formBicepsR = $state('');
|
||||
let formForearmsL = $state('');
|
||||
let formForearmsR = $state('');
|
||||
let formWaist = $state('');
|
||||
let formHips = $state('');
|
||||
let formThighsL = $state('');
|
||||
let formThighsR = $state('');
|
||||
let formCalvesL = $state('');
|
||||
let formCalvesR = $state('');
|
||||
|
||||
const bodyPartFields = $derived([
|
||||
{ label: t('neck', lang), key: 'neck', value: latest.measurements?.neck },
|
||||
@@ -86,153 +65,6 @@
|
||||
{ label: t('r_calf', lang), key: 'calvesRight', value: latest.measurements?.calves?.right }
|
||||
]);
|
||||
|
||||
function resetForm() {
|
||||
formDate = new Date().toISOString().slice(0, 10);
|
||||
formWeight = '';
|
||||
formBodyFat = '';
|
||||
formCalories = '';
|
||||
formNeck = '';
|
||||
formShoulders = '';
|
||||
formChest = '';
|
||||
formBicepsL = '';
|
||||
formBicepsR = '';
|
||||
formForearmsL = '';
|
||||
formForearmsR = '';
|
||||
formWaist = '';
|
||||
formHips = '';
|
||||
formThighsL = '';
|
||||
formThighsR = '';
|
||||
formCalvesL = '';
|
||||
formCalvesR = '';
|
||||
editingId = null;
|
||||
}
|
||||
|
||||
/** @param {any} m */
|
||||
function populateForm(m) {
|
||||
formDate = new Date(m.date).toISOString().slice(0, 10);
|
||||
formWeight = m.weight != null ? String(m.weight) : '';
|
||||
formBodyFat = m.bodyFatPercent != null ? String(m.bodyFatPercent) : '';
|
||||
formCalories = m.caloricIntake != null ? String(m.caloricIntake) : '';
|
||||
const bp = m.measurements ?? {};
|
||||
formNeck = bp.neck != null ? String(bp.neck) : '';
|
||||
formShoulders = bp.shoulders != null ? String(bp.shoulders) : '';
|
||||
formChest = bp.chest != null ? String(bp.chest) : '';
|
||||
const bl = bp.biceps?.left ?? bp.leftBicep;
|
||||
const br = bp.biceps?.right ?? bp.rightBicep;
|
||||
formBicepsL = bl != null ? String(bl) : '';
|
||||
formBicepsR = br != null ? String(br) : '';
|
||||
const fl = bp.forearms?.left ?? bp.leftForearm;
|
||||
const fr = bp.forearms?.right ?? bp.rightForearm;
|
||||
formForearmsL = fl != null ? String(fl) : '';
|
||||
formForearmsR = fr != null ? String(fr) : '';
|
||||
formWaist = bp.waist != null ? String(bp.waist) : '';
|
||||
formHips = bp.hips != null ? String(bp.hips) : '';
|
||||
const tl = bp.thighs?.left ?? bp.leftThigh;
|
||||
const tr = bp.thighs?.right ?? bp.rightThigh;
|
||||
formThighsL = tl != null ? String(tl) : '';
|
||||
formThighsR = tr != null ? String(tr) : '';
|
||||
const cl = bp.calves?.left ?? bp.leftCalf;
|
||||
const cr = bp.calves?.right ?? bp.rightCalf;
|
||||
formCalvesL = cl != null ? String(cl) : '';
|
||||
formCalvesR = cr != null ? String(cr) : '';
|
||||
}
|
||||
|
||||
/** @param {any} m */
|
||||
function startEdit(m) {
|
||||
populateForm(m);
|
||||
editingId = m._id;
|
||||
showForm = true;
|
||||
}
|
||||
|
||||
function startAdd() {
|
||||
resetForm();
|
||||
editingId = null;
|
||||
showForm = true;
|
||||
}
|
||||
|
||||
function buildBody() {
|
||||
/** @type {any} */
|
||||
const body = { date: formDate };
|
||||
if (formWeight) body.weight = Number(formWeight);
|
||||
else body.weight = null;
|
||||
if (formBodyFat) body.bodyFatPercent = Number(formBodyFat);
|
||||
else body.bodyFatPercent = null;
|
||||
if (formCalories) body.caloricIntake = Number(formCalories);
|
||||
else body.caloricIntake = null;
|
||||
|
||||
/** @type {any} */
|
||||
const m = {};
|
||||
if (formNeck) m.neck = Number(formNeck);
|
||||
if (formShoulders) m.shoulders = Number(formShoulders);
|
||||
if (formChest) m.chest = Number(formChest);
|
||||
if (formBicepsL || formBicepsR) m.biceps = {};
|
||||
if (formBicepsL) m.biceps.left = Number(formBicepsL);
|
||||
if (formBicepsR) m.biceps.right = Number(formBicepsR);
|
||||
if (formForearmsL || formForearmsR) m.forearms = {};
|
||||
if (formForearmsL) m.forearms.left = Number(formForearmsL);
|
||||
if (formForearmsR) m.forearms.right = Number(formForearmsR);
|
||||
if (formWaist) m.waist = Number(formWaist);
|
||||
if (formHips) m.hips = Number(formHips);
|
||||
if (formThighsL || formThighsR) m.thighs = {};
|
||||
if (formThighsL) m.thighs.left = Number(formThighsL);
|
||||
if (formThighsR) m.thighs.right = Number(formThighsR);
|
||||
if (formCalvesL || formCalvesR) m.calves = {};
|
||||
if (formCalvesL) m.calves.left = Number(formCalvesL);
|
||||
if (formCalvesR) m.calves.right = Number(formCalvesR);
|
||||
|
||||
body.measurements = Object.keys(m).length > 0 ? m : null;
|
||||
return body;
|
||||
}
|
||||
|
||||
async function refreshLatest() {
|
||||
try {
|
||||
const latestRes = await fetch('/api/fitness/measurements/latest');
|
||||
if (latestRes.ok) latest = await latestRes.json();
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function saveMeasurement() {
|
||||
saving = true;
|
||||
const body = buildBody();
|
||||
|
||||
try {
|
||||
if (editingId) {
|
||||
const res = await fetch(`/api/fitness/measurements/${editingId}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
if (res.ok) {
|
||||
const d = await res.json();
|
||||
measurements = measurements.map((m) => m._id === editingId ? d.measurement : m);
|
||||
await refreshLatest();
|
||||
showForm = false;
|
||||
resetForm();
|
||||
} else {
|
||||
const err = await res.json().catch(() => null);
|
||||
toast.error(err?.error ?? 'Failed to save measurement');
|
||||
}
|
||||
} else {
|
||||
const res = await fetch('/api/fitness/measurements', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
if (res.ok) {
|
||||
const d = await res.json();
|
||||
measurements = [d.measurement, ...measurements];
|
||||
await refreshLatest();
|
||||
showForm = false;
|
||||
resetForm();
|
||||
} else {
|
||||
const err = await res.json().catch(() => null);
|
||||
toast.error(err?.error ?? 'Failed to save measurement');
|
||||
}
|
||||
}
|
||||
} catch { toast.error('Failed to save measurement'); }
|
||||
saving = false;
|
||||
}
|
||||
|
||||
/** @param {string} id */
|
||||
async function deleteMeasurement(id) {
|
||||
if (!confirm(t('delete_measurement_confirm', lang))) return;
|
||||
@@ -240,11 +72,10 @@
|
||||
const res = await fetch(`/api/fitness/measurements/${id}`, { method: 'DELETE' });
|
||||
if (res.ok) {
|
||||
measurements = measurements.filter((m) => m._id !== id);
|
||||
await refreshLatest();
|
||||
if (editingId === id) {
|
||||
showForm = false;
|
||||
resetForm();
|
||||
}
|
||||
try {
|
||||
const latestRes = await fetch('/api/fitness/measurements/latest');
|
||||
if (latestRes.ok) latest = await latestRes.json();
|
||||
} catch {}
|
||||
} else {
|
||||
const err = await res.json().catch(() => null);
|
||||
toast.error(err?.error ?? 'Failed to delete measurement');
|
||||
@@ -275,88 +106,32 @@
|
||||
<h1>{t('measure_title', lang)}</h1>
|
||||
|
||||
<section class="profile-section">
|
||||
<h2>{t('profile', lang)}</h2>
|
||||
<div class="profile-row">
|
||||
<div class="form-group">
|
||||
<label for="p-sex">{t('sex', lang)}</label>
|
||||
<select id="p-sex" bind:value={profileSex}>
|
||||
<option value="male">{t('male', lang)}</option>
|
||||
<option value="female">{t('female', lang)}</option>
|
||||
</select>
|
||||
<button class="profile-toggle" onclick={() => showProfile = !showProfile}>
|
||||
<h2>{t('profile', lang)}</h2>
|
||||
<ChevronDown size={16} class={showProfile ? 'chevron open' : 'chevron'} />
|
||||
</button>
|
||||
{#if showProfile}
|
||||
<div class="profile-row">
|
||||
<div class="form-group">
|
||||
<label for="p-sex">{t('sex', lang)}</label>
|
||||
<select id="p-sex" bind:value={profileSex}>
|
||||
<option value="male">{t('male', lang)}</option>
|
||||
<option value="female">{t('female', lang)}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="p-height">{t('height', lang)}</label>
|
||||
<input id="p-height" type="number" min="100" max="250" placeholder="175" bind:value={profileHeight} />
|
||||
</div>
|
||||
{#if profileDirty}
|
||||
<button class="profile-save-btn" onclick={saveProfile} disabled={profileSaving}>
|
||||
{profileSaving ? t('saving', lang) : t('save', lang)}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="p-height">{t('height', lang)}</label>
|
||||
<input id="p-height" type="number" min="100" max="250" placeholder="175" bind:value={profileHeight} />
|
||||
</div>
|
||||
{#if profileDirty}
|
||||
<button class="profile-save-btn" onclick={saveProfile} disabled={profileSaving}>
|
||||
{profileSaving ? t('saving', lang) : t('save', lang)}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
{#if showForm}
|
||||
<form class="measure-form" onsubmit={(e) => { e.preventDefault(); saveMeasurement(); }}>
|
||||
<div class="form-header">
|
||||
<h2>{editingId ? t('edit_measurement', lang) : t('new_measurement', lang)}</h2>
|
||||
<button type="button" class="cancel-form-btn" onclick={() => { showForm = false; resetForm(); }}>{t('cancel', lang)}</button>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="m-date">{t('date', lang)}</label>
|
||||
<input id="m-date" type="date" bind:value={formDate} />
|
||||
</div>
|
||||
|
||||
<h3>{t('general', lang)}</h3>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="m-weight">{t('weight_kg', lang)}</label>
|
||||
<input id="m-weight" type="number" step="0.1" bind:value={formWeight} placeholder="—" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="m-bf">{t('body_fat_pct', lang)}</label>
|
||||
<input id="m-bf" type="number" step="0.1" bind:value={formBodyFat} placeholder="—" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="m-cal">{t('calories_kcal', lang)}</label>
|
||||
<input id="m-cal" type="number" bind:value={formCalories} placeholder="—" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>{t('body_parts_cm', lang)}</h3>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label for="m-neck">{t('neck', lang)}</label><input id="m-neck" type="number" step="0.1" bind:value={formNeck} placeholder="—" /></div>
|
||||
<div class="form-group"><label for="m-shoulders">{t('shoulders', lang)}</label><input id="m-shoulders" type="number" step="0.1" bind:value={formShoulders} placeholder="—" /></div>
|
||||
<div class="form-group"><label for="m-chest">{t('chest', lang)}</label><input id="m-chest" type="number" step="0.1" bind:value={formChest} placeholder="—" /></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label for="m-bl">{t('l_bicep', lang)}</label><input id="m-bl" type="number" step="0.1" bind:value={formBicepsL} placeholder="—" /></div>
|
||||
<div class="form-group"><label for="m-br">{t('r_bicep', lang)}</label><input id="m-br" type="number" step="0.1" bind:value={formBicepsR} placeholder="—" /></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label for="m-fl">{t('l_forearm', lang)}</label><input id="m-fl" type="number" step="0.1" bind:value={formForearmsL} placeholder="—" /></div>
|
||||
<div class="form-group"><label for="m-fr">{t('r_forearm', lang)}</label><input id="m-fr" type="number" step="0.1" bind:value={formForearmsR} placeholder="—" /></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label for="m-waist">{t('waist', lang)}</label><input id="m-waist" type="number" step="0.1" bind:value={formWaist} placeholder="—" /></div>
|
||||
<div class="form-group"><label for="m-hips">{t('hips', lang)}</label><input id="m-hips" type="number" step="0.1" bind:value={formHips} placeholder="—" /></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label for="m-tl">{t('l_thigh', lang)}</label><input id="m-tl" type="number" step="0.1" bind:value={formThighsL} placeholder="—" /></div>
|
||||
<div class="form-group"><label for="m-tr">{t('r_thigh', lang)}</label><input id="m-tr" type="number" step="0.1" bind:value={formThighsR} placeholder="—" /></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label for="m-cl">{t('l_calf', lang)}</label><input id="m-cl" type="number" step="0.1" bind:value={formCalvesL} placeholder="—" /></div>
|
||||
<div class="form-group"><label for="m-cr">{t('r_calf', lang)}</label><input id="m-cr" type="number" step="0.1" bind:value={formCalvesR} placeholder="—" /></div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="save-btn" disabled={saving}>
|
||||
{saving ? t('saving', lang) : editingId ? t('update_measurement', lang) : t('save_measurement', lang)}
|
||||
</button>
|
||||
</form>
|
||||
{/if}
|
||||
|
||||
<section class="latest-section">
|
||||
<h2>{t('latest', lang)}</h2>
|
||||
<div class="stat-grid">
|
||||
@@ -394,16 +169,16 @@
|
||||
<h2>{t('history', lang)}</h2>
|
||||
<div class="history-list">
|
||||
{#each measurements as m (m._id)}
|
||||
<div class="history-item" class:editing={editingId === m._id}>
|
||||
<div class="history-item">
|
||||
<div class="history-main">
|
||||
<div class="history-info">
|
||||
<span class="history-date">{formatDate(m.date)}</span>
|
||||
<span class="history-summary">{summaryParts(m)}</span>
|
||||
</div>
|
||||
<div class="history-actions">
|
||||
<button class="icon-btn edit" onclick={() => startEdit(m)} aria-label="Edit measurement">
|
||||
<a class="icon-btn edit" href="/fitness/{measureSlug}/edit/{m._id}" aria-label="Edit measurement">
|
||||
<Pencil size={14} />
|
||||
</button>
|
||||
</a>
|
||||
<button class="icon-btn delete" onclick={() => deleteMeasurement(m._id)} aria-label="Delete measurement">
|
||||
<Trash2 size={14} />
|
||||
</button>
|
||||
@@ -417,7 +192,7 @@
|
||||
</div>
|
||||
|
||||
{#if !workout.active}
|
||||
<AddActionButton onclick={startAdd} ariaLabel="Add measurement" />
|
||||
<AddButton href="/fitness/{measureSlug}/add" />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
@@ -434,12 +209,6 @@
|
||||
margin: 0 0 0.5rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
h3 {
|
||||
margin: 0.75rem 0 0.25rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* Profile */
|
||||
.profile-section {
|
||||
background: var(--color-surface);
|
||||
@@ -447,6 +216,27 @@
|
||||
box-shadow: var(--shadow-sm);
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
.profile-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
}
|
||||
.profile-toggle h2 {
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.profile-toggle :global(.chevron) {
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.profile-toggle :global(.chevron.open) {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.profile-section h2 {
|
||||
margin: 0 0 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
@@ -455,14 +245,7 @@
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
align-items: flex-end;
|
||||
}
|
||||
.profile-row select {
|
||||
padding: 0.4rem 0.5rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 6px;
|
||||
background: var(--color-bg-elevated);
|
||||
color: inherit;
|
||||
font-size: 0.85rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
.profile-save-btn {
|
||||
padding: 0.4rem 0.75rem;
|
||||
@@ -482,43 +265,6 @@
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Form */
|
||||
.measure-form {
|
||||
background: var(--color-surface);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
padding: 1rem;
|
||||
}
|
||||
.form-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.form-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.cancel-form-btn {
|
||||
background: none;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 6px;
|
||||
color: var(--color-text-secondary);
|
||||
padding: 0.3rem 0.75rem;
|
||||
font-weight: 700;
|
||||
font-size: 0.75rem;
|
||||
cursor: pointer;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
.cancel-form-btn:hover {
|
||||
border-color: var(--color-text-primary);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.form-group {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
@@ -533,7 +279,7 @@
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.form-group input {
|
||||
.form-group input, .form-group select {
|
||||
padding: 0.4rem 0.5rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 6px;
|
||||
@@ -545,22 +291,6 @@
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
.save-btn {
|
||||
width: 100%;
|
||||
margin-top: 0.75rem;
|
||||
padding: 0.7rem;
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 700;
|
||||
font-size: 0.85rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.save-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Latest */
|
||||
.stat-grid {
|
||||
@@ -624,9 +354,6 @@
|
||||
box-shadow: var(--shadow-sm);
|
||||
padding: 0.6rem 0.75rem;
|
||||
}
|
||||
.history-item.editing {
|
||||
border: 1px solid var(--color-primary);
|
||||
}
|
||||
.history-main {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -682,8 +409,5 @@
|
||||
.stat-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.form-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
200
src/routes/fitness/[measure=fitnessMeasure]/add/+page.svelte
Normal file
200
src/routes/fitness/[measure=fitnessMeasure]/add/+page.svelte
Normal file
@@ -0,0 +1,200 @@
|
||||
<script>
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
|
||||
import { toast } from '$lib/js/toast.svelte';
|
||||
import SaveFab from '$lib/components/SaveFab.svelte';
|
||||
|
||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
||||
const measureSlug = $derived(lang === 'en' ? 'measure' : 'messen');
|
||||
|
||||
let saving = $state(false);
|
||||
|
||||
let formDate = $state(new Date().toISOString().slice(0, 10));
|
||||
let formWeight = $state('');
|
||||
let formBodyFat = $state('');
|
||||
let formCalories = $state('');
|
||||
let formNeck = $state('');
|
||||
let formShoulders = $state('');
|
||||
let formChest = $state('');
|
||||
let formBicepsL = $state('');
|
||||
let formBicepsR = $state('');
|
||||
let formForearmsL = $state('');
|
||||
let formForearmsR = $state('');
|
||||
let formWaist = $state('');
|
||||
let formHips = $state('');
|
||||
let formThighsL = $state('');
|
||||
let formThighsR = $state('');
|
||||
let formCalvesL = $state('');
|
||||
let formCalvesR = $state('');
|
||||
|
||||
function buildBody() {
|
||||
/** @type {any} */
|
||||
const body = { date: formDate };
|
||||
if (formWeight) body.weight = Number(formWeight);
|
||||
else body.weight = null;
|
||||
if (formBodyFat) body.bodyFatPercent = Number(formBodyFat);
|
||||
else body.bodyFatPercent = null;
|
||||
if (formCalories) body.caloricIntake = Number(formCalories);
|
||||
else body.caloricIntake = null;
|
||||
|
||||
/** @type {any} */
|
||||
const m = {};
|
||||
if (formNeck) m.neck = Number(formNeck);
|
||||
if (formShoulders) m.shoulders = Number(formShoulders);
|
||||
if (formChest) m.chest = Number(formChest);
|
||||
if (formBicepsL || formBicepsR) m.biceps = {};
|
||||
if (formBicepsL) m.biceps.left = Number(formBicepsL);
|
||||
if (formBicepsR) m.biceps.right = Number(formBicepsR);
|
||||
if (formForearmsL || formForearmsR) m.forearms = {};
|
||||
if (formForearmsL) m.forearms.left = Number(formForearmsL);
|
||||
if (formForearmsR) m.forearms.right = Number(formForearmsR);
|
||||
if (formWaist) m.waist = Number(formWaist);
|
||||
if (formHips) m.hips = Number(formHips);
|
||||
if (formThighsL || formThighsR) m.thighs = {};
|
||||
if (formThighsL) m.thighs.left = Number(formThighsL);
|
||||
if (formThighsR) m.thighs.right = Number(formThighsR);
|
||||
if (formCalvesL || formCalvesR) m.calves = {};
|
||||
if (formCalvesL) m.calves.left = Number(formCalvesL);
|
||||
if (formCalvesR) m.calves.right = Number(formCalvesR);
|
||||
|
||||
body.measurements = Object.keys(m).length > 0 ? m : null;
|
||||
return body;
|
||||
}
|
||||
|
||||
async function saveMeasurement() {
|
||||
saving = true;
|
||||
try {
|
||||
const res = await fetch('/api/fitness/measurements', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(buildBody())
|
||||
});
|
||||
if (res.ok) {
|
||||
await goto(`/fitness/${measureSlug}`);
|
||||
} else {
|
||||
const err = await res.json().catch(() => null);
|
||||
toast.error(err?.error ?? 'Failed to save measurement');
|
||||
}
|
||||
} catch { toast.error('Failed to save measurement'); }
|
||||
saving = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head><title>{t('new_measurement', lang)} - Bocken</title></svelte:head>
|
||||
|
||||
<div class="measure-add">
|
||||
<h1>{t('new_measurement', lang)}</h1>
|
||||
|
||||
<form onsubmit={(e) => { e.preventDefault(); saveMeasurement(); }}>
|
||||
<div class="form-group">
|
||||
<label for="m-date">{t('date', lang)}</label>
|
||||
<input id="m-date" type="date" bind:value={formDate} />
|
||||
</div>
|
||||
|
||||
<h3>{t('general', lang)}</h3>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="m-weight">{t('weight_kg', lang)}</label>
|
||||
<input id="m-weight" type="number" step="0.1" bind:value={formWeight} placeholder="--" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="m-bf">{t('body_fat_pct', lang)}</label>
|
||||
<input id="m-bf" type="number" step="0.1" bind:value={formBodyFat} placeholder="--" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="m-cal">{t('calories_kcal', lang)}</label>
|
||||
<input id="m-cal" type="number" bind:value={formCalories} placeholder="--" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>{t('body_parts_cm', lang)}</h3>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label for="m-neck">{t('neck', lang)}</label><input id="m-neck" type="number" step="0.1" bind:value={formNeck} placeholder="--" /></div>
|
||||
<div class="form-group"><label for="m-shoulders">{t('shoulders', lang)}</label><input id="m-shoulders" type="number" step="0.1" bind:value={formShoulders} placeholder="--" /></div>
|
||||
<div class="form-group"><label for="m-chest">{t('chest', lang)}</label><input id="m-chest" type="number" step="0.1" bind:value={formChest} placeholder="--" /></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label for="m-bl">{t('l_bicep', lang)}</label><input id="m-bl" type="number" step="0.1" bind:value={formBicepsL} placeholder="--" /></div>
|
||||
<div class="form-group"><label for="m-br">{t('r_bicep', lang)}</label><input id="m-br" type="number" step="0.1" bind:value={formBicepsR} placeholder="--" /></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label for="m-fl">{t('l_forearm', lang)}</label><input id="m-fl" type="number" step="0.1" bind:value={formForearmsL} placeholder="--" /></div>
|
||||
<div class="form-group"><label for="m-fr">{t('r_forearm', lang)}</label><input id="m-fr" type="number" step="0.1" bind:value={formForearmsR} placeholder="--" /></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label for="m-waist">{t('waist', lang)}</label><input id="m-waist" type="number" step="0.1" bind:value={formWaist} placeholder="--" /></div>
|
||||
<div class="form-group"><label for="m-hips">{t('hips', lang)}</label><input id="m-hips" type="number" step="0.1" bind:value={formHips} placeholder="--" /></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label for="m-tl">{t('l_thigh', lang)}</label><input id="m-tl" type="number" step="0.1" bind:value={formThighsL} placeholder="--" /></div>
|
||||
<div class="form-group"><label for="m-tr">{t('r_thigh', lang)}</label><input id="m-tr" type="number" step="0.1" bind:value={formThighsR} placeholder="--" /></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label for="m-cl">{t('l_calf', lang)}</label><input id="m-cl" type="number" step="0.1" bind:value={formCalvesL} placeholder="--" /></div>
|
||||
<div class="form-group"><label for="m-cr">{t('r_calf', lang)}</label><input id="m-cr" type="number" step="0.1" bind:value={formCalvesR} placeholder="--" /></div>
|
||||
</div>
|
||||
|
||||
<SaveFab disabled={saving} label={t('save_measurement', lang)} />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.measure-add {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
h3 {
|
||||
margin: 0.75rem 0 0.25rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
form {
|
||||
background: var(--color-surface);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
padding: 1rem;
|
||||
}
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.form-group {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
.form-group label {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.form-group input {
|
||||
padding: 0.4rem 0.5rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 6px;
|
||||
background: var(--color-bg-elevated);
|
||||
color: inherit;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.form-group input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.form-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ fetch, params }) => {
|
||||
const res = await fetch(`/api/fitness/measurements/${params.id}`);
|
||||
if (!res.ok) return { measurement: null };
|
||||
return { measurement: await res.json() };
|
||||
};
|
||||
@@ -0,0 +1,256 @@
|
||||
<script>
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
|
||||
import { toast } from '$lib/js/toast.svelte';
|
||||
import { Trash2 } from 'lucide-svelte';
|
||||
import SaveFab from '$lib/components/SaveFab.svelte';
|
||||
|
||||
const lang = $derived(detectFitnessLang($page.url.pathname));
|
||||
const measureSlug = $derived(lang === 'en' ? 'measure' : 'messen');
|
||||
|
||||
let { data } = $props();
|
||||
const m = data.measurement?.measurement;
|
||||
|
||||
let saving = $state(false);
|
||||
let deleting = $state(false);
|
||||
|
||||
// Populate form from loaded measurement
|
||||
let formDate = $state(m ? new Date(m.date).toISOString().slice(0, 10) : '');
|
||||
let formWeight = $state(m?.weight != null ? String(m.weight) : '');
|
||||
let formBodyFat = $state(m?.bodyFatPercent != null ? String(m.bodyFatPercent) : '');
|
||||
let formCalories = $state(m?.caloricIntake != null ? String(m.caloricIntake) : '');
|
||||
|
||||
const bp = m?.measurements ?? {};
|
||||
let formNeck = $state(bp.neck != null ? String(bp.neck) : '');
|
||||
let formShoulders = $state(bp.shoulders != null ? String(bp.shoulders) : '');
|
||||
let formChest = $state(bp.chest != null ? String(bp.chest) : '');
|
||||
let formBicepsL = $state((bp.biceps?.left ?? bp.leftBicep) != null ? String(bp.biceps?.left ?? bp.leftBicep) : '');
|
||||
let formBicepsR = $state((bp.biceps?.right ?? bp.rightBicep) != null ? String(bp.biceps?.right ?? bp.rightBicep) : '');
|
||||
let formForearmsL = $state((bp.forearms?.left ?? bp.leftForearm) != null ? String(bp.forearms?.left ?? bp.leftForearm) : '');
|
||||
let formForearmsR = $state((bp.forearms?.right ?? bp.rightForearm) != null ? String(bp.forearms?.right ?? bp.rightForearm) : '');
|
||||
let formWaist = $state(bp.waist != null ? String(bp.waist) : '');
|
||||
let formHips = $state(bp.hips != null ? String(bp.hips) : '');
|
||||
let formThighsL = $state((bp.thighs?.left ?? bp.leftThigh) != null ? String(bp.thighs?.left ?? bp.leftThigh) : '');
|
||||
let formThighsR = $state((bp.thighs?.right ?? bp.rightThigh) != null ? String(bp.thighs?.right ?? bp.rightThigh) : '');
|
||||
let formCalvesL = $state((bp.calves?.left ?? bp.leftCalf) != null ? String(bp.calves?.left ?? bp.leftCalf) : '');
|
||||
let formCalvesR = $state((bp.calves?.right ?? bp.rightCalf) != null ? String(bp.calves?.right ?? bp.rightCalf) : '');
|
||||
|
||||
function buildBody() {
|
||||
/** @type {any} */
|
||||
const body = { date: formDate };
|
||||
if (formWeight) body.weight = Number(formWeight);
|
||||
else body.weight = null;
|
||||
if (formBodyFat) body.bodyFatPercent = Number(formBodyFat);
|
||||
else body.bodyFatPercent = null;
|
||||
if (formCalories) body.caloricIntake = Number(formCalories);
|
||||
else body.caloricIntake = null;
|
||||
|
||||
/** @type {any} */
|
||||
const ms = {};
|
||||
if (formNeck) ms.neck = Number(formNeck);
|
||||
if (formShoulders) ms.shoulders = Number(formShoulders);
|
||||
if (formChest) ms.chest = Number(formChest);
|
||||
if (formBicepsL || formBicepsR) ms.biceps = {};
|
||||
if (formBicepsL) ms.biceps.left = Number(formBicepsL);
|
||||
if (formBicepsR) ms.biceps.right = Number(formBicepsR);
|
||||
if (formForearmsL || formForearmsR) ms.forearms = {};
|
||||
if (formForearmsL) ms.forearms.left = Number(formForearmsL);
|
||||
if (formForearmsR) ms.forearms.right = Number(formForearmsR);
|
||||
if (formWaist) ms.waist = Number(formWaist);
|
||||
if (formHips) ms.hips = Number(formHips);
|
||||
if (formThighsL || formThighsR) ms.thighs = {};
|
||||
if (formThighsL) ms.thighs.left = Number(formThighsL);
|
||||
if (formThighsR) ms.thighs.right = Number(formThighsR);
|
||||
if (formCalvesL || formCalvesR) ms.calves = {};
|
||||
if (formCalvesL) ms.calves.left = Number(formCalvesL);
|
||||
if (formCalvesR) ms.calves.right = Number(formCalvesR);
|
||||
|
||||
body.measurements = Object.keys(ms).length > 0 ? ms : null;
|
||||
return body;
|
||||
}
|
||||
|
||||
async function saveMeasurement() {
|
||||
saving = true;
|
||||
try {
|
||||
const res = await fetch(`/api/fitness/measurements/${m._id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(buildBody())
|
||||
});
|
||||
if (res.ok) {
|
||||
await goto(`/fitness/${measureSlug}`);
|
||||
} else {
|
||||
const err = await res.json().catch(() => null);
|
||||
toast.error(err?.error ?? 'Failed to save measurement');
|
||||
}
|
||||
} catch { toast.error('Failed to save measurement'); }
|
||||
saving = false;
|
||||
}
|
||||
|
||||
async function deleteMeasurement() {
|
||||
if (!confirm(t('delete_measurement_confirm', lang))) return;
|
||||
deleting = true;
|
||||
try {
|
||||
const res = await fetch(`/api/fitness/measurements/${m._id}`, { method: 'DELETE' });
|
||||
if (res.ok) {
|
||||
await goto(`/fitness/${measureSlug}`);
|
||||
} else {
|
||||
const err = await res.json().catch(() => null);
|
||||
toast.error(err?.error ?? 'Failed to delete measurement');
|
||||
}
|
||||
} catch { toast.error('Failed to delete measurement'); }
|
||||
deleting = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head><title>{t('edit_measurement', lang)} - Bocken</title></svelte:head>
|
||||
|
||||
<div class="measure-edit">
|
||||
<h1>{t('edit_measurement', lang)}</h1>
|
||||
|
||||
{#if !m}
|
||||
<p>Measurement not found.</p>
|
||||
{:else}
|
||||
<form onsubmit={(e) => { e.preventDefault(); saveMeasurement(); }}>
|
||||
<div class="form-group">
|
||||
<label for="m-date">{t('date', lang)}</label>
|
||||
<input id="m-date" type="date" bind:value={formDate} />
|
||||
</div>
|
||||
|
||||
<h3>{t('general', lang)}</h3>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="m-weight">{t('weight_kg', lang)}</label>
|
||||
<input id="m-weight" type="number" step="0.1" bind:value={formWeight} placeholder="--" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="m-bf">{t('body_fat_pct', lang)}</label>
|
||||
<input id="m-bf" type="number" step="0.1" bind:value={formBodyFat} placeholder="--" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="m-cal">{t('calories_kcal', lang)}</label>
|
||||
<input id="m-cal" type="number" bind:value={formCalories} placeholder="--" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>{t('body_parts_cm', lang)}</h3>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label for="m-neck">{t('neck', lang)}</label><input id="m-neck" type="number" step="0.1" bind:value={formNeck} placeholder="--" /></div>
|
||||
<div class="form-group"><label for="m-shoulders">{t('shoulders', lang)}</label><input id="m-shoulders" type="number" step="0.1" bind:value={formShoulders} placeholder="--" /></div>
|
||||
<div class="form-group"><label for="m-chest">{t('chest', lang)}</label><input id="m-chest" type="number" step="0.1" bind:value={formChest} placeholder="--" /></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label for="m-bl">{t('l_bicep', lang)}</label><input id="m-bl" type="number" step="0.1" bind:value={formBicepsL} placeholder="--" /></div>
|
||||
<div class="form-group"><label for="m-br">{t('r_bicep', lang)}</label><input id="m-br" type="number" step="0.1" bind:value={formBicepsR} placeholder="--" /></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label for="m-fl">{t('l_forearm', lang)}</label><input id="m-fl" type="number" step="0.1" bind:value={formForearmsL} placeholder="--" /></div>
|
||||
<div class="form-group"><label for="m-fr">{t('r_forearm', lang)}</label><input id="m-fr" type="number" step="0.1" bind:value={formForearmsR} placeholder="--" /></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label for="m-waist">{t('waist', lang)}</label><input id="m-waist" type="number" step="0.1" bind:value={formWaist} placeholder="--" /></div>
|
||||
<div class="form-group"><label for="m-hips">{t('hips', lang)}</label><input id="m-hips" type="number" step="0.1" bind:value={formHips} placeholder="--" /></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label for="m-tl">{t('l_thigh', lang)}</label><input id="m-tl" type="number" step="0.1" bind:value={formThighsL} placeholder="--" /></div>
|
||||
<div class="form-group"><label for="m-tr">{t('r_thigh', lang)}</label><input id="m-tr" type="number" step="0.1" bind:value={formThighsR} placeholder="--" /></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group"><label for="m-cl">{t('l_calf', lang)}</label><input id="m-cl" type="number" step="0.1" bind:value={formCalvesL} placeholder="--" /></div>
|
||||
<div class="form-group"><label for="m-cr">{t('r_calf', lang)}</label><input id="m-cr" type="number" step="0.1" bind:value={formCalvesR} placeholder="--" /></div>
|
||||
</div>
|
||||
|
||||
<div class="delete-actions">
|
||||
<button type="button" class="btn-danger" onclick={deleteMeasurement} disabled={deleting || saving}>
|
||||
<Trash2 size={14} />
|
||||
{deleting ? t('saving', lang) : t('delete_', lang)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<SaveFab disabled={saving || deleting} label={t('update_measurement', lang)} />
|
||||
</form>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.measure-edit {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
h3 {
|
||||
margin: 0.75rem 0 0.25rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
form {
|
||||
background: var(--color-surface);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
padding: 1rem;
|
||||
}
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.form-group {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
.form-group label {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.form-group input {
|
||||
padding: 0.4rem 0.5rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 6px;
|
||||
background: var(--color-bg-elevated);
|
||||
color: inherit;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.form-group input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
.delete-actions {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.btn-danger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background: var(--red);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn-danger:hover:not(:disabled) {
|
||||
background: var(--nord11);
|
||||
}
|
||||
.btn-danger:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.form-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user