Files
HouseHub/preview.html
T
FeFeClochette b450a345f6
Deploy HouseHub / deploy (push) Successful in 2s
preview csv mobile
2026-10-12 11:43:14 +02:00

768 lines
23 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<title>Preview Mobile - Import CSV</title>
<style>
/* === Variables HouseHub (Simulées) === */
:root {
--primary: #3b82f6;
--primary-dark: #1d4ed8;
--success: #10b981;
--danger: #ef4444;
--bg-page: #f8fafc;
--bg-panel: #ffffff;
--bg-subtle: #f1f5f9;
--text-main: #0f172a;
--text-muted: #64748b;
--border-light: #e2e8f0;
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.15);
--radius: 12px;
}
* {
box-sizing: border-box;
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
body {
margin: 0;
background: #333;
display: flex;
justify-content: center;
align-items: flex-start;
min-height: 100vh;
}
/* Conteneur simulant un écran mobile */
.mobile-simulator {
width: 100%;
max-width: 414px; /* Taille iPhone Max */
height: 100vh;
max-height: 896px;
background: var(--bg-page);
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: 0 0 40px rgba(0, 0, 0, 0.5);
}
/* En-tête de la modale */
.preview-header {
background: var(--bg-panel);
padding: 16px;
border-bottom: 1px solid var(--border-light);
flex-shrink: 0;
z-index: 10;
}
.preview-title {
margin: 0 0 12px 0;
font-size: 1.1rem;
color: var(--text-main);
display: flex;
justify-content: space-between;
}
.preview-close {
background: none;
border: none;
font-size: 1.5rem;
color: var(--text-muted);
line-height: 1;
cursor: pointer;
}
/* Système d'onglets pour la preview */
.tabs {
display: flex;
background: var(--bg-subtle);
padding: 4px;
border-radius: 8px;
}
.tab-btn {
flex: 1;
padding: 6px;
border: none;
background: transparent;
border-radius: 6px;
font-size: 0.8rem;
font-weight: 600;
color: var(--text-muted);
cursor: pointer;
}
.tab-btn.active {
background: var(--bg-panel);
color: var(--primary);
box-shadow: var(--shadow-sm);
}
/* Conteneur principal scrollable */
.preview-body {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 16px;
padding-bottom: 80px; /* Espace pour le bouton valider */
}
.view-section {
display: none;
animation: fadeIn 0.3s ease;
}
.view-section.active {
display: block;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Composants de formulaire communs */
.pf-label {
display: block;
font-size: 0.8rem;
font-weight: 600;
color: var(--text-muted);
margin-bottom: 4px;
}
.pf-select,
.pf-input {
width: 100%;
padding: 10px;
border: 1px solid var(--border-light);
border-radius: 8px;
font-size: 0.95rem;
background: var(--bg-panel);
color: var(--text-main);
margin-bottom: 10px;
}
.pf-select:focus {
border-color: var(--primary);
outline: none;
}
.amount-badge {
font-weight: 700;
font-size: 1.05rem;
}
.amount-expense {
color: var(--text-main);
}
.amount-income {
color: var(--success);
}
/* =========================================
PROPOSITION 1 : CARTES VERTICALES
========================================= */
.prop1-card {
background: var(--bg-panel);
border: 1px solid var(--border-light);
border-radius: var(--radius);
padding: 16px;
margin-bottom: 12px;
box-shadow: var(--shadow-sm);
}
.prop1-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
}
.prop1-date {
font-size: 0.75rem;
color: var(--text-muted);
font-weight: 600;
}
.prop1-label {
font-size: 0.95rem;
font-weight: 600;
color: var(--text-main);
margin: 4px 0 0 0;
line-height: 1.3;
}
.prop1-ignore-row {
display: flex;
align-items: center;
gap: 8px;
margin-top: 8px;
}
.prop1-ignore-row input[type="checkbox"] {
width: 18px;
height: 18px;
accent-color: var(--danger);
}
/* =========================================
PROPOSITION 2 : ACCORDÉON (Liste compacte)
========================================= */
.prop2-container {
background: var(--bg-panel);
border: 1px solid var(--border-light);
border-radius: var(--radius);
overflow: hidden;
}
.prop2-item {
border-bottom: 1px solid var(--border-light);
}
.prop2-item:last-child {
border-bottom: none;
}
.prop2-summary {
padding: 12px 16px;
display: flex;
justify-content: space-between;
align-items: center;
background: var(--bg-panel);
cursor: pointer;
}
.prop2-summary-left {
flex: 1;
min-width: 0;
padding-right: 12px;
}
.prop2-label {
font-size: 0.9rem;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.prop2-date {
font-size: 0.75rem;
color: var(--text-muted);
}
.prop2-status {
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--danger);
margin-left: 8px;
flex-shrink: 0;
}
.prop2-status.ok {
background: var(--success);
}
.prop2-details {
display: none;
padding: 16px;
background: var(--bg-subtle);
border-top: 1px dashed var(--border-light);
}
.prop2-item.open .prop2-details {
display: block;
}
.prop2-item.open .prop2-summary {
background: var(--bg-soft);
}
/* =========================================
PROPOSITION 3 : BOTTOM SHEET (Native feel)
========================================= */
.prop3-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.prop3-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 16px;
background: var(--bg-panel);
border: 1px solid var(--border-light);
border-radius: 10px;
cursor: pointer;
}
.prop3-item:active {
background: var(--bg-subtle);
}
.prop3-item.categorized {
border-left: 4px solid var(--success);
}
.prop3-item.pending {
border-left: 4px solid var(--warning);
}
.bottom-sheet-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 100;
display: none;
opacity: 0;
transition: opacity 0.3s;
}
.bottom-sheet {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: var(--bg-panel);
border-radius: 20px 20px 0 0;
padding: 24px;
z-index: 101;
transform: translateY(100%);
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
box-shadow: 0 -10px 25px rgba(0, 0, 0, 0.1);
}
.bottom-sheet.open {
transform: translateY(0);
}
.bottom-sheet-overlay.open {
display: block;
opacity: 1;
}
.bs-handle {
width: 40px;
height: 5px;
background: var(--border-strong);
border-radius: 5px;
margin: -10px auto 20px auto;
}
/* Footer fixe */
.preview-footer {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 16px;
background: var(--bg-panel);
border-top: 1px solid var(--border-light);
z-index: 50;
}
.pf-btn {
display: block;
width: 100%;
padding: 14px;
background: var(--primary);
color: white;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 600;
text-align: center;
cursor: pointer;
}
</style>
</head>
<body>
<div class="mobile-simulator">
<div class="preview-header">
<h2 class="preview-title">
<span>📥 Importation CSV</span>
<button class="preview-close">×</button>
</h2>
<div class="tabs">
<button class="tab-btn active" onclick="switchView('prop1')">
1. Cartes
</button>
<button class="tab-btn" onclick="switchView('prop2')">
2. Accordéon
</button>
<button class="tab-btn" onclick="switchView('prop3')">
3. Bottom Sheet
</button>
</div>
</div>
<div class="preview-body">
<!-- ======================================================= -->
<!-- PROPOSITION 1 : CARTES VERTICALES -->
<!-- ======================================================= -->
<div id="prop1" class="view-section active">
<p
style="font-size: 0.85rem; color: var(--text-muted); margin-top: 0"
>
Vision aérée. Parfait s'il y a peu de lignes à importer.
</p>
<!-- Ligne Dépense -->
<div class="prop1-card">
<div class="prop1-header">
<div>
<div class="prop1-date">12/10/2026</div>
<h4 class="prop1-label">AUCHAN SUPERMARCHE</h4>
</div>
<div class="amount-badge amount-expense">- 45,20 €</div>
</div>
<label class="pf-label">Catégorie</label>
<select
class="pf-select"
onchange="toggleLinkedItem(this, 'prop1-linked-1')"
>
<option value="">-- À définir --</option>
<option value="FMCG">🛒 Courses (FMCG)</option>
<option value="FIXED">🏢 Charges Fixes</option>
<option value="INCOME">💵 Revenus</option>
</select>
<div id="prop1-linked-1" style="display: none">
<label class="pf-label">Lier à une charge fixe</label>
<select class="pf-select">
<option value="">-- Sélectionner --</option>
<option value="1">Assurance Auto</option>
<option value="2">Loyer</option>
</select>
</div>
<label
class="prop1-ignore-row pf-label"
style="margin: 0; font-weight: 500; cursor: pointer"
>
<input type="checkbox" /> Ignorer cette ligne
</label>
</div>
<!-- Ligne Abonnement -->
<div class="prop1-card">
<div class="prop1-header">
<div>
<div class="prop1-date">14/10/2026</div>
<h4 class="prop1-label">NETFLIX.COM</h4>
</div>
<div class="amount-badge amount-expense">- 13,49 €</div>
</div>
<label class="pf-label">Catégorie</label>
<select
class="pf-select"
onchange="toggleLinkedItem(this, 'prop1-linked-2')"
>
<option value="FIXED" selected>🏢 Charges Fixes</option>
<option value="FMCG">🛒 Courses (FMCG)</option>
</select>
<div id="prop1-linked-2" style="display: block">
<label class="pf-label">Lier à une charge fixe</label>
<select class="pf-select">
<option value="3" selected>Abonnement Netflix</option>
<option value="1">Assurance Auto</option>
</select>
</div>
</div>
<!-- Ligne Revenu -->
<div class="prop1-card">
<div class="prop1-header">
<div>
<div class="prop1-date">15/10/2026</div>
<h4 class="prop1-label">VIR SEPA M. PERCO</h4>
</div>
<div class="amount-badge amount-income">+ 1 500,00 €</div>
</div>
<label class="pf-label">Catégorie</label>
<select class="pf-select">
<option value="INCOME" selected>💵 Revenus</option>
<option value="FMCG">🛒 Courses (FMCG)</option>
</select>
</div>
</div>
<!-- ======================================================= -->
<!-- PROPOSITION 2 : ACCORDÉON -->
<!-- ======================================================= -->
<div id="prop2" class="view-section">
<p
style="font-size: 0.85rem; color: var(--text-muted); margin-top: 0"
>
Vision compacte. Idéal pour un gros volume d'imports.
</p>
<div class="prop2-container">
<!-- Item 1 -->
<div class="prop2-item" onclick="toggleAccordion(this)">
<div class="prop2-summary">
<div class="prop2-summary-left">
<div class="prop2-date">12/10/2026</div>
<div class="prop2-label">AUCHAN SUPERMARCHE</div>
</div>
<div class="amount-badge amount-expense">- 45,20 €</div>
<div class="prop2-status" title="À définir"></div>
</div>
<div class="prop2-details" onclick="event.stopPropagation()">
<label class="pf-label">Catégorie</label>
<select
class="pf-select"
onchange="
toggleLinkedItem(this, 'prop2-linked-1');
updateAccordionStatus(this);
"
>
<option value="">-- À définir --</option>
<option value="FMCG">🛒 Courses (FMCG)</option>
<option value="FIXED">🏢 Charges Fixes</option>
</select>
<div id="prop2-linked-1" style="display: none">
<label class="pf-label">Lier à une charge fixe</label>
<select class="pf-select">
<option value="">-- Sélectionner --</option>
<option value="1">Assurance Auto</option>
</select>
</div>
<label
class="prop1-ignore-row pf-label"
style="margin: 0; font-weight: 500"
>
<input
type="checkbox"
onchange="updateAccordionStatus(this, true)"
/>
Ignorer
</label>
</div>
</div>
<!-- Item 2 -->
<div class="prop2-item">
<div
class="prop2-summary"
onclick="toggleAccordion(this.parentElement)"
>
<div class="prop2-summary-left">
<div class="prop2-date">14/10/2026</div>
<div class="prop2-label">NETFLIX.COM</div>
</div>
<div class="amount-badge amount-expense">- 13,49 €</div>
<div class="prop2-status ok" title="Validé"></div>
</div>
<div class="prop2-details" onclick="event.stopPropagation()">
<label class="pf-label">Catégorie</label>
<select class="pf-select">
<option value="FIXED" selected>🏢 Charges Fixes</option>
</select>
<label class="pf-label">Lien</label>
<select class="pf-select">
<option value="3" selected>Abonnement Netflix</option>
</select>
</div>
</div>
</div>
</div>
<!-- ======================================================= -->
<!-- PROPOSITION 3 : LISTE + BOTTOM SHEET -->
<!-- ======================================================= -->
<div id="prop3" class="view-section">
<p
style="font-size: 0.85rem; color: var(--text-muted); margin-top: 0"
>
Expérience 100% Mobile Native. Garde la liste toujours visible.
</p>
<div class="prop3-list">
<div
class="prop3-item pending"
onclick="
openBottomSheet('AUCHAN SUPERMARCHE', '-45.20', 'expense', '')
"
>
<div>
<div class="prop1-date">12/10/2026</div>
<div class="prop1-label" style="font-size: 0.85rem">
AUCHAN SUPERMARCHE
</div>
<div
style="
font-size: 0.7rem;
color: var(--danger);
margin-top: 2px;
"
>
À définir
</div>
</div>
<div class="amount-badge amount-expense">- 45,20 €</div>
</div>
<div
class="prop3-item categorized"
onclick="
openBottomSheet('NETFLIX.COM', '-13.49', 'expense', 'FIXED')
"
>
<div>
<div class="prop1-date">14/10/2026</div>
<div class="prop1-label" style="font-size: 0.85rem">
NETFLIX.COM
</div>
<div
style="
font-size: 0.7rem;
color: var(--success);
margin-top: 2px;
"
>
Charges Fixes
</div>
</div>
<div class="amount-badge amount-expense">- 13,49 €</div>
</div>
</div>
</div>
</div>
<!-- Footer Fixe -->
<div class="preview-footer">
<button class="pf-btn">Valider l'importation</button>
</div>
<!-- Modale Bottom Sheet (Pour Prop 3) -->
<div
class="bottom-sheet-overlay"
id="bs-overlay"
onclick="closeBottomSheet()"
></div>
<div class="bottom-sheet" id="bs-modal">
<div class="bs-handle"></div>
<div
style="
display: flex;
justify-content: space-between;
margin-bottom: 20px;
"
>
<div>
<h3
id="bs-title"
style="margin: 0; font-size: 1.1rem; color: var(--text-main)"
>
Titre
</h3>
<div
id="bs-date"
style="font-size: 0.8rem; color: var(--text-muted)"
>
12/10/2026
</div>
</div>
<div id="bs-amount" class="amount-badge">0,00 €</div>
</div>
<label class="pf-label">Catégorie</label>
<select
class="pf-select"
id="bs-select-cat"
onchange="toggleLinkedItem(this, 'bs-linked-item')"
>
<option value="">-- À définir --</option>
<option value="FMCG">🛒 Courses (FMCG)</option>
<option value="FIXED">🏢 Charges Fixes</option>
<option value="INCOME">💵 Revenus</option>
</select>
<div id="bs-linked-item" style="display: none">
<label class="pf-label">Lier à une charge / revenu</label>
<select class="pf-select">
<option value="">-- Sélectionner --</option>
<option value="1">Loyer</option>
<option value="2">Assurance</option>
</select>
</div>
<div style="display: flex; gap: 10px; margin-top: 20px">
<button
class="pf-btn"
style="background: var(--bg-subtle); color: var(--danger); flex: 1"
onclick="closeBottomSheet()"
>
Ignorer
</button>
<button class="pf-btn" style="flex: 2" onclick="closeBottomSheet()">
Appliquer
</button>
</div>
</div>
</div>
<script>
// Navigation entre les propositions
function switchView(id) {
document
.querySelectorAll(".view-section")
.forEach((el) => el.classList.remove("active"));
document
.querySelectorAll(".tab-btn")
.forEach((el) => el.classList.remove("active"));
document.getElementById(id).classList.add("active");
event.target.classList.add("active");
}
// Affichage conditionnel des selects liés (Charges fixes / Revenus)
function toggleLinkedItem(selectEl, targetId) {
const target = document.getElementById(targetId);
if (selectEl.value === "FIXED" || selectEl.value === "INCOME") {
target.style.display = "block";
} else {
target.style.display = "none";
}
}
// Logique Accordéon (Prop 2)
function toggleAccordion(itemEl) {
const isOpen = itemEl.classList.contains("open");
document
.querySelectorAll(".prop2-item")
.forEach((el) => el.classList.remove("open"));
if (!isOpen) {
itemEl.classList.add("open");
}
}
function updateAccordionStatus(element, isIgnore = false) {
const itemEl = element.closest(".prop2-item");
const statusDot = itemEl.querySelector(".prop2-status");
if (isIgnore && element.checked) {
statusDot.className = "prop2-status ok";
statusDot.style.background = "var(--text-muted)";
} else if (itemEl.querySelector("select").value !== "") {
statusDot.className = "prop2-status ok";
statusDot.style.background = "var(--success)";
} else {
statusDot.className = "prop2-status";
statusDot.style.background = "var(--danger)";
}
}
// Logique Bottom Sheet (Prop 3)
function openBottomSheet(title, amount, type, currentCat) {
document.getElementById("bs-title").innerText = title;
document.getElementById("bs-amount").innerText = amount + " €";
document.getElementById("bs-amount").className =
"amount-badge " +
(type === "expense" ? "amount-expense" : "amount-income");
const select = document.getElementById("bs-select-cat");
select.value = currentCat;
toggleLinkedItem(select, "bs-linked-item");
document.getElementById("bs-overlay").classList.add("open");
document.getElementById("bs-modal").classList.add("open");
}
function closeBottomSheet() {
document.getElementById("bs-overlay").classList.remove("open");
document.getElementById("bs-modal").classList.remove("open");
}
</script>
</body>
</html>