Initial commit
This commit is contained in:
commit
983cee0320
322 changed files with 57174 additions and 0 deletions
105
backup/first -fina app/app/templates/subscriptions/create.html
Normal file
105
backup/first -fina app/app/templates/subscriptions/create.html
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ _('subscription.add') }} - FINA{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="form-container">
|
||||
<div class="glass-card form-card">
|
||||
<h1>➕ {{ _('subscription.add') }}</h1>
|
||||
|
||||
<form method="POST" class="form">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="name">{{ _('subscription.name') }}</label>
|
||||
<input type="text" id="name" name="name" required placeholder="Netflix, Spotify, etc.">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="amount">{{ _('expense.amount') }}</label>
|
||||
<input type="number" id="amount" name="amount" step="0.01" min="0" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="frequency">{{ _('subscription.frequency') }}</label>
|
||||
<select id="frequency" name="frequency" required onchange="toggleCustomInterval()">
|
||||
<option value="weekly">{{ _('subscription.freq_weekly') }}</option>
|
||||
<option value="biweekly">{{ _('subscription.freq_biweekly') }}</option>
|
||||
<option value="monthly" selected>{{ _('subscription.freq_monthly') }}</option>
|
||||
<option value="quarterly">{{ _('subscription.freq_quarterly') }}</option>
|
||||
<option value="yearly">{{ _('subscription.freq_yearly') }}</option>
|
||||
<option value="custom">{{ _('subscription.freq_custom') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="custom-interval-group" style="display: none;">
|
||||
<label for="custom_interval_days">{{ _('subscription.custom_interval') }}</label>
|
||||
<input type="number" id="custom_interval_days" name="custom_interval_days" min="1" placeholder="e.g., 45 days">
|
||||
<small style="color: var(--text-secondary);">{{ _('subscription.custom_interval_desc') }}</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="category_id">{{ _('category.name') }}</label>
|
||||
<select id="category_id" name="category_id" required>
|
||||
<option value="">{{ _('common.select') }}</option>
|
||||
{% for category in categories %}
|
||||
<option value="{{ category.id }}">{{ category.icon }} {{ category.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="start_date">{{ _('subscription.start_date') }}</label>
|
||||
<input type="date" id="start_date" name="start_date" value="{{ today }}" required>
|
||||
<small style="color: var(--text-secondary);">{{ _('subscription.start_date_desc') }}</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="end_date">{{ _('subscription.end_date') }} ({{ _('common.optional') }})</label>
|
||||
<input type="date" id="end_date" name="end_date">
|
||||
<small style="color: var(--text-secondary);">{{ _('subscription.end_date_desc') }}</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="total_occurrences">{{ _('subscription.total_occurrences') }} ({{ _('common.optional') }})</label>
|
||||
<input type="number" id="total_occurrences" name="total_occurrences" min="1" placeholder="e.g., 12">
|
||||
<small style="color: var(--text-secondary);">{{ _('subscription.total_occurrences_desc') }}</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
|
||||
<input type="checkbox" id="auto_create_expense" name="auto_create_expense" style="width: auto;">
|
||||
<span>{{ _('subscription.auto_create') }}</span>
|
||||
</label>
|
||||
<small style="color: var(--text-secondary);">{{ _('subscription.auto_create_desc') }}</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="notes">{{ _('subscription.notes') }}</label>
|
||||
<textarea id="notes" name="notes" rows="3" placeholder="{{ _('subscription.notes_placeholder') }}"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="{{ url_for('subscriptions.index') }}" class="btn btn-secondary">{{ _('common.cancel') }}</a>
|
||||
<button type="submit" class="btn btn-primary">{{ _('common.save') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function toggleCustomInterval() {
|
||||
const frequency = document.getElementById('frequency').value;
|
||||
const customGroup = document.getElementById('custom-interval-group');
|
||||
const customInput = document.getElementById('custom_interval_days');
|
||||
|
||||
if (frequency === 'custom') {
|
||||
customGroup.style.display = 'block';
|
||||
customInput.required = true;
|
||||
} else {
|
||||
customGroup.style.display = 'none';
|
||||
customInput.required = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
101
backup/first -fina app/app/templates/subscriptions/edit.html
Normal file
101
backup/first -fina app/app/templates/subscriptions/edit.html
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ _('common.edit') }} {{ subscription.name }} - FINA{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="form-container">
|
||||
<div class="glass-card form-card">
|
||||
<h1>✏️ {{ _('common.edit') }} {{ _('subscription.title') }}</h1>
|
||||
|
||||
<form method="POST" class="form">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="name">{{ _('subscription.name') }}</label>
|
||||
<input type="text" id="name" name="name" value="{{ subscription.name }}" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="amount">{{ _('expense.amount') }}</label>
|
||||
<input type="number" id="amount" name="amount" step="0.01" min="0" value="{{ subscription.amount }}" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="frequency">{{ _('subscription.frequency') }}</label>
|
||||
<select id="frequency" name="frequency" required onchange="toggleCustomInterval()">
|
||||
<option value="weekly" {% if subscription.frequency == 'weekly' %}selected{% endif %}>{{ _('subscription.freq_weekly') }}</option>
|
||||
<option value="biweekly" {% if subscription.frequency == 'biweekly' %}selected{% endif %}>{{ _('subscription.freq_biweekly') }}</option>
|
||||
<option value="monthly" {% if subscription.frequency == 'monthly' %}selected{% endif %}>{{ _('subscription.freq_monthly') }}</option>
|
||||
<option value="quarterly" {% if subscription.frequency == 'quarterly' %}selected{% endif %}>{{ _('subscription.freq_quarterly') }}</option>
|
||||
<option value="yearly" {% if subscription.frequency == 'yearly' %}selected{% endif %}>{{ _('subscription.freq_yearly') }}</option>
|
||||
<option value="custom" {% if subscription.frequency == 'custom' %}selected{% endif %}>{{ _('subscription.freq_custom') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="custom-interval-group" style="display: {% if subscription.frequency == 'custom' %}block{% else %}none{% endif %};">
|
||||
<label for="custom_interval_days">{{ _('subscription.custom_interval') }}</label>
|
||||
<input type="number" id="custom_interval_days" name="custom_interval_days" min="1" value="{{ subscription.custom_interval_days or '' }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="category_id">{{ _('category.name') }}</label>
|
||||
<select id="category_id" name="category_id" required>
|
||||
{% for category in categories %}
|
||||
<option value="{{ category.id }}" {% if subscription.category_id == category.id %}selected{% endif %}>{{ category.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="formend_date">{{ _('subscription.end_date') }} ({{ _('common.optional') }})</label>
|
||||
<input type="date" id="end_date" name="end_date" value="{{ subscription.end_date.strftime('%Y-%m-%d') if subscription.end_date else '' }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="total_occurrences">{{ _('subscription.total_occurrences') }} ({{ _('common.optional') }})</label>
|
||||
<input type="number" id="total_occurrences" name="total_occurrences" min="1" value="{{ subscription.total_occurrences or '' }}">
|
||||
<small style="color: var(--text-secondary);">{{ _('subscription.occurrences_remaining') }}: {{ (subscription.total_occurrences - subscription.occurrences_count) if subscription.total_occurrences else '∞' }}</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
|
||||
<input type="checkbox" id="auto_create_expense" name="auto_create_expense" {% if subscription.auto_create_expense %}checked{% endif %} style="width: auto;">
|
||||
<span>{{ _('subscription.auto_create') }}</span>
|
||||
</label>
|
||||
<small style="color: var(--text-secondary);">{{ _('subscription.auto_create_desc') }}</small>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function toggleCustomInterval() {
|
||||
const frequency = document.getElementById('frequency').value;
|
||||
const customGroup = document.getElementById('custom-interval-group');
|
||||
const customInput = document.getElementById('custom_interval_days');
|
||||
|
||||
if (frequency === 'custom') {
|
||||
customGroup.style.display = 'block';
|
||||
customInput.required = true;
|
||||
} else {
|
||||
customGroup.style.display = 'none';
|
||||
customInput.required = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="-group">
|
||||
<label for="next_due_date">{{ _('subscription.next_payment') }}</label>
|
||||
<input type="date" id="next_due_date" name="next_due_date" value="{{ subscription.next_due_date.strftime('%Y-%m-%d') if subscription.next_due_date else '' }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="notes">{{ _('subscription.notes') }}</label>
|
||||
<textarea id="notes" name="notes" rows="3">{{ subscription.notes or '' }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="{{ url_for('subscriptions.index') }}" class="btn btn-secondary">{{ _('common.cancel') }}</a>
|
||||
<button type="submit" class="btn btn-primary">{{ _('common.save') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
207
backup/first -fina app/app/templates/subscriptions/index.html
Normal file
207
backup/first -fina app/app/templates/subscriptions/index.html
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ _('subscription.title') }} - FINA{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="subscriptions-page">
|
||||
<div class="page-header">
|
||||
<h1>🔄 {{ _('subscription.title') }}</h1>
|
||||
<div class="header-actions">
|
||||
<form method="POST" action="{{ url_for('subscriptions.auto_create_expenses') }}" style="display: inline;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button type="submit" class="btn btn-secondary" title="{{ _('subscription.auto_create_tooltip') }}">⚡ {{ _('subscription.create_due') }}</button>
|
||||
</form>
|
||||
<form method="POST" action="{{ url_for('subscriptions.detect') }}" style="display: inline;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button type="submit" class="btn btn-secondary">🔍 {{ _('subscription.detect') }}</button>
|
||||
</form>
|
||||
<a href="{{ url_for('subscriptions.create') }}" class="btn btn-primary">➕ {{ _('subscription.add') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Summary Cards -->
|
||||
<div class="stats-container" style="margin-bottom: 2rem;">
|
||||
<div class="glass-card stat-card">
|
||||
<h3>{{ _('subscription.active') }}</h3>
|
||||
<p class="stat-value">{{ subscriptions|length }}</p>
|
||||
</div>
|
||||
|
||||
<div class="glass-card stat-card">
|
||||
<h3>{{ _('subscription.monthly_cost') }}</h3>
|
||||
<p class="stat-value">{{ monthly_cost|currency }}</p>
|
||||
</div>
|
||||
|
||||
<div class="glass-card stat-card">
|
||||
<h3>{{ _('subscription.yearly_cost') }}</h3>
|
||||
<p class="stat-value">{{ yearly_cost|currency }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Suggestions -->
|
||||
{% if suggestions %}
|
||||
<div class="glass-card suggestions-section" style="margin-bottom: 2rem;">
|
||||
<h2>💡 {{ _('subscription.suggestions') }}</h2>
|
||||
<p style="color: var(--text-secondary); margin-bottom: 1rem;">
|
||||
{{ _('subscription.suggestions_desc') }}
|
||||
</p>
|
||||
|
||||
{% for suggestion in suggestions %}
|
||||
<div class="suggestion-card glass-card" style="margin-bottom: 1rem; padding: 1rem; border-left: 3px solid #f59e0b;">
|
||||
<div class="suggestion-content">
|
||||
<div class="suggestion-header">
|
||||
<h3>{{ suggestion.suggested_name }}</h3>
|
||||
<span class="confidence-badge" style="background: rgba(245, 158, 11, 0.2); padding: 0.25rem 0.75rem; border-radius: 20px; font-size: 0.85rem;">
|
||||
{{ suggestion.confidence_score|round(0)|int }}% {{ _('subscription.confidence') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="suggestion-details" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem; margin: 1rem 0;">
|
||||
<div>
|
||||
<small style="color: var(--text-secondary);">{{ _('expense.amount') }}</small>
|
||||
<p style="font-weight: 600;">{{ suggestion.average_amount|currency }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<small style="color: var(--text-secondary);">{{ _('subscription.frequency') }}</small>
|
||||
<p style="font-weight: 600;">{{ _(('subscription.freq_' + suggestion.detected_frequency)) }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<small style="color: var(--text-secondary);">{{ _('subscription.occurrences') }}</small>
|
||||
<p style="font-weight: 600;">{{ suggestion.occurrence_count }} {{ _('subscription.times') }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<small style="color: var(--text-secondary);">{{ _('subscription.period') }}</small>
|
||||
<p style="font-weight: 600;">{{ suggestion.first_occurrence.strftime('%b %Y') }} - {{ suggestion.last_occurrence.strftime('%b %Y') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="suggestion-actions" style="display: flex; gap: 0.5rem;">
|
||||
<form method="POST" action="{{ url_for('subscriptions.accept_suggestion', pattern_id=suggestion.id) }}" style="display: inline;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button type="submit" class="btn btn-primary btn-sm">✅ {{ _('subscription.accept') }}</button>
|
||||
</form>
|
||||
<form method="POST" action="{{ url_for('subscriptions.dismiss_suggestion', pattern_id=suggestion.id) }}" style="display: inline;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button type="submit" class="btn btn-secondary btn-sm">❌ {{ _('subscription.dismiss') }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Active Subscriptions -->
|
||||
{% if subscriptions %}
|
||||
<div class="glass-card">
|
||||
<h2>{{ _('subscription.active_list') }}</h2>
|
||||
|
||||
<div class="subscriptions-list">
|
||||
{% for sub in subscriptions %}
|
||||
<div class="subscription-item" style="display: flex; justify-content: space-between; align-items: center; padding: 1rem; border-bottom: 1px solid var(--glass-border);">
|
||||
<div class="subscription-info" style="flex: 1;">
|
||||
<h3 style="margin: 0;">
|
||||
{{ sub.name }}
|
||||
{% if sub.auto_create_expense %}
|
||||
<span style="background: rgba(34, 197, 94, 0.2); color: #4ade80; padding: 0.2rem 0.5rem; border-radius: 5px; font-size: 0.75rem; margin-left: 0.5rem;" title="{{ _('subscription.auto_create_tooltip') }}">⚡ {{ _('subscription.auto') }}</span>
|
||||
{% endif %}
|
||||
</h3>
|
||||
<div style="display: flex; gap: 2rem; margin-top: 0.5rem; color: var(--text-secondary); font-size: 0.9rem; flex-wrap: wrap;">
|
||||
<span>💰 {{ sub.amount|currency }} /
|
||||
{% if sub.frequency == 'custom' %}
|
||||
{{ _('subscription.every') }} {{ sub.custom_interval_days }} {{ _('subscription.days') }}
|
||||
{% else %}
|
||||
{{ _(('subscription.freq_' + sub.frequency)) }}
|
||||
{% endif %}
|
||||
</span>
|
||||
{% if sub.next_due_date %}
|
||||
<span>📅 {{ _('subscription.next_payment') }}: {{ sub.next_due_date.strftime('%b %d, %Y') }}</span>
|
||||
{% endif %}
|
||||
<span>📊 {{ _('subscription.annual') }}: {{ sub.get_annual_cost()|currency }}</span>
|
||||
{% if sub.total_occurrences %}
|
||||
<span>🔢 {{ sub.occurrences_count }}/{{ sub.total_occurrences }} {{ _('subscription.times') }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if sub.notes %}
|
||||
<p style="margin-top: 0.5rem; font-size: 0.85rem; color: var(--text-secondary);">{{ sub.notes }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="subscription-actions" style="display: flex; gap: 0.5rem;">
|
||||
<a href="{{ url_for('subscriptions.edit', subscription_id=sub.id) }}" class="btn btn-secondary btn-sm">{{ _('common.edit') }}</a>
|
||||
<form method="POST" action="{{ url_for('subscriptions.toggle', subscription_id=sub.id) }}" style="display: inline;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button type="submit" class="btn btn-secondary btn-sm">
|
||||
{% if sub.is_active %}⏸️{% else %}▶️{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
<form method="POST" action="{{ url_for('subscriptions.delete', subscription_id=sub.id) }}" onsubmit="return confirm('{{ _('subscription.delete_confirm') }}');" style="display: inline;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button type="submit" class="btn btn-secondary btn-sm">🗑️</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="glass-card empty-state">
|
||||
<h2>{{ _('subscription.no_subscriptions') }}</h2>
|
||||
<p>{{ _('subscription.no_subscriptions_desc') }}</p>
|
||||
<div style="display: flex; gap: 1rem; justify-content: center; margin-top: 1rem;">
|
||||
<form method="POST" action="{{ url_for('subscriptions.detect') }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button type="submit" class="btn btn-primary">🔍 {{ _('subscription.detect') }}</button>
|
||||
</form>
|
||||
<a href="{{ url_for('subscriptions.create') }}" class="btn btn-secondary">➕ {{ _('subscription.add_manual') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.subscriptions-page {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.subscription-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.subscription-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.subscription-actions {
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue