importer feature

This commit is contained in:
2026-05-10 21:23:56 +02:00
parent a47e3ae4e6
commit f2f1317660
77 changed files with 4884 additions and 1810 deletions

View File

@@ -33,6 +33,7 @@
{% if let Some(uid) = ctx.user_id %}
<a href="/users/{{ uid }}">Profile</a>
<a href="/reviews/new">Add Review</a>
<a href="/import">Import</a>
<a href="/logout">Logout</a>
{% else %}
<a href="/login">Login</a>

View File

@@ -0,0 +1,52 @@
{% extends "base.html" %}
{% block content %}
<h1>Map Columns</h1>
{% if let Some(err) = error %}
<p class="error">{{ err }}</p>
{% endif %}
<p>Showing up to 5 sample rows. Map each column to a diary field.</p>
<form method="POST" action="/import/{{ session_id }}/mapping">
<table>
<thead>
<tr>
{% for col in columns %}<th>{{ col }}</th>{% endfor %}
</tr>
</thead>
<tbody>
{% for row in sample_rows %}
<tr>{% for cell in row %}<td>{{ cell }}</td>{% endfor %}</tr>
{% endfor %}
</tbody>
</table>
{% for col in columns %}
<fieldset>
<legend>{{ col }}</legend>
<label>Maps to
<select name="mapping_{{ loop.index0 }}_field">
<option value="">— skip —</option>
{% for (val, label) in domain_fields %}
<option value="{{ val }}">{{ label }}</option>
{% endfor %}
</select>
</label>
<label>Rating scale
<select name="mapping_{{ loop.index0 }}_scale">
<option value="1.0">Same (05)</option>
<option value="0.5">10-point (/2)</option>
<option value="0.05">Percentage (/20)</option>
</select>
</label>
<label>Date format
<input type="text" name="mapping_{{ loop.index0 }}_datefmt" placeholder="%Y-%m-%d">
</label>
<input type="hidden" name="mapping_{{ loop.index0 }}_col" value="{{ col }}">
</fieldset>
{% endfor %}
<input type="hidden" name="_csrf" value="{{ ctx.csrf_token }}">
<button type="submit">Preview Import</button>
</form>
{% endblock %}

View File

@@ -0,0 +1,45 @@
{% extends "base.html" %}
{% block content %}
<h1>Preview Import</h1>
<form method="POST" action="/import/{{ session_id }}/confirm">
<table>
<thead>
<tr>
<th>Include?</th>
{% for col in columns %}<th>{{ col }}</th>{% endfor %}
<th>Status</th>
</tr>
</thead>
<tbody>
{% for row in rows %}
<tr>
<td>
{% match row.status %}
{% when ImportRowStatus::Invalid with (_e) %}
<input type="checkbox" disabled>
{% when _ %}
<input type="checkbox" name="confirmed" value="{{ row.index }}" checked>
{% endmatch %}
</td>
{% for cell in row.cells %}<td>{{ cell }}</td>{% endfor %}
<td>
{% match row.status %}
{% when ImportRowStatus::Valid %}&#10003;
{% when ImportRowStatus::Duplicate %}&#9888; duplicate
{% when ImportRowStatus::Invalid with (e) %}&#10007; {{ e }}
{% endmatch %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<label>Save this mapping as a profile?
<input type="text" name="profile_name" placeholder="e.g. Letterboxd">
</label>
<input type="hidden" name="_csrf" value="{{ ctx.csrf_token }}">
<button type="submit">Import Selected</button>
</form>
{% endblock %}

View File

@@ -0,0 +1,42 @@
{% extends "base.html" %}
{% block content %}
<h1>Import Reviews</h1>
{% if let Some(err) = error %}
<p class="error">{{ err }}</p>
{% endif %}
{% if !profiles.is_empty() %}
<section>
<h2>Saved Profiles</h2>
<ul>
{% for p in profiles %}
<li>
{{ p.name }}
<form method="POST" action="/import/profiles/{{ p.id }}/delete" style="display:inline">
<input type="hidden" name="_csrf" value="{{ ctx.csrf_token }}">
<button type="submit">Delete</button>
</form>
</li>
{% endfor %}
</ul>
</section>
{% endif %}
<h2>Upload File</h2>
<form method="POST" action="/import/upload" enctype="multipart/form-data">
<label>
File (CSV, TSV, JSON, XLSX)<br>
<input type="file" name="file" accept=".csv,.tsv,.json,.xlsx" required>
</label>
<label>
Format<br>
<select name="format">
<option value="csv">CSV / TSV</option>
<option value="json">JSON</option>
<option value="xlsx">XLSX</option>
</select>
</label>
<input type="hidden" name="_csrf" value="{{ ctx.csrf_token }}">
<button type="submit">Upload</button>
</form>
{% endblock %}