Dashboard: formatted timestamps (MM/DD/YYYY HH:MM TZ), inline category edit dropdown
This commit is contained in:
parent
3a6aa8cbda
commit
dabd97e13c
23
main.go
23
main.go
|
|
@ -67,6 +67,7 @@ func main() {
|
|||
"truncate": truncateText,
|
||||
"categoryIcon": categoryIcon,
|
||||
"formatDate": formatDate,
|
||||
"formatDateTime": formatDateTime,
|
||||
"lower": strings.ToLower,
|
||||
"title": strings.Title,
|
||||
"safe": func(s string) template.HTML { return template.HTML(s) },
|
||||
|
|
@ -164,6 +165,28 @@ func formatDate(s string) string {
|
|||
return s
|
||||
}
|
||||
|
||||
func formatDateTime(s string) string {
|
||||
formats := []string{
|
||||
"2006-01-02T15:04:05-07:00",
|
||||
"2006-01-02T15:04:05.999999-07:00",
|
||||
"2006-01-02T15:04:05.999999",
|
||||
"2006-01-02T15:04:05",
|
||||
"2006-01-02 15:04:05",
|
||||
"2006-01-02",
|
||||
}
|
||||
loc, _ := time.LoadLocation("America/New_York")
|
||||
if loc == nil {
|
||||
loc = time.UTC
|
||||
}
|
||||
for _, f := range formats {
|
||||
if t, err := time.Parse(f, s); err == nil {
|
||||
t = t.In(loc)
|
||||
return t.Format("01/02/2006 3:04 PM EST")
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Template rendering
|
||||
|
||||
func renderTemplate(w http.ResponseWriter, name string, data interface{}) {
|
||||
|
|
|
|||
|
|
@ -120,23 +120,17 @@
|
|||
{{if .Stats.RecentUploads}}
|
||||
<div class="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
{{range .Stats.RecentUploads}}
|
||||
<a href="/document/{{.ID}}" class="flex items-center px-5 py-4 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors group">
|
||||
<div class="flex-shrink-0 p-2 bg-gray-100 dark:bg-gray-700 rounded-lg mr-4">
|
||||
<div class="flex items-center px-5 py-4 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors group">
|
||||
<a href="/document/{{.ID}}" class="flex-shrink-0 p-2 bg-gray-100 dark:bg-gray-700 rounded-lg mr-4">
|
||||
<svg class="w-6 h-6 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
</a>
|
||||
<a href="/document/{{.ID}}" class="flex-1 min-w-0">
|
||||
<p class="font-medium text-gray-900 dark:text-white truncate group-hover:text-brand-600 dark:group-hover:text-brand-400 transition-colors">{{.Title}}</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 truncate">{{truncate .Summary 100}}</p>
|
||||
</div>
|
||||
{{if .Vendor}}
|
||||
<div class="hidden lg:block flex-shrink-0 w-40 ml-4 text-sm text-gray-600 dark:text-gray-400 truncate">{{.Vendor}}</div>
|
||||
{{end}}
|
||||
{{if .Amount}}
|
||||
<div class="hidden lg:block flex-shrink-0 w-28 ml-4 text-sm font-medium text-gray-900 dark:text-white text-right">{{.Amount}}</div>
|
||||
{{end}}
|
||||
<div class="flex-shrink-0 ml-4 text-right w-28">
|
||||
</a>
|
||||
<div class="flex-shrink-0 ml-4 text-right">
|
||||
{{if eq .Status "processing"}}
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-amber-100 dark:bg-amber-900/30 text-amber-700 dark:text-amber-300" data-processing="{{.ID}}">
|
||||
<svg class="animate-spin -ml-0.5 mr-1.5 h-3 w-3" fill="none" viewBox="0 0 24 24">
|
||||
|
|
@ -150,13 +144,18 @@
|
|||
Error
|
||||
</span>
|
||||
{{else}}
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-brand-100 dark:bg-brand-900/30 text-brand-700 dark:text-brand-300">
|
||||
{{title .Category}}
|
||||
</span>
|
||||
<select onchange="updateCategory('{{.ID}}', '{{.Title}}', this.value)"
|
||||
data-category="{{.Category}}"
|
||||
class="text-xs font-medium bg-brand-100 dark:bg-brand-900/30 text-brand-700 dark:text-brand-300 rounded-full px-2.5 py-0.5 border-0 cursor-pointer hover:bg-brand-200 dark:hover:bg-brand-900/50">
|
||||
{{$cat := .Category}}
|
||||
{{range $.Categories}}
|
||||
<option value="{{.}}" {{if eq . $cat}}selected{{end}}>{{title .}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
{{end}}
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">{{formatDate .ProcessedAt}}</p>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">{{formatDateTime .ProcessedAt}}</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{else}}
|
||||
|
|
@ -398,6 +397,23 @@
|
|||
}, 2000);
|
||||
}
|
||||
|
||||
async function updateCategory(id, title, newCategory) {
|
||||
try {
|
||||
const res = await fetch('/api/document/' + id, {
|
||||
method: 'PUT',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({title: title, category: newCategory, notes: ''})
|
||||
});
|
||||
if (res.ok) {
|
||||
showToast('Moved to ' + newCategory, 'success');
|
||||
} else {
|
||||
showToast('Failed to update', 'error');
|
||||
}
|
||||
} catch {
|
||||
showToast('Failed to update', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function showToast(message, type = 'info') {
|
||||
const colors = {
|
||||
success: 'bg-green-500',
|
||||
|
|
|
|||
Loading…
Reference in New Issue