feat: Full responsive design - mobile sidebar, card lists, modals

This commit is contained in:
FluxKit
2026-02-22 15:08:09 +00:00
parent ba1646d863
commit e58dfc9a39
5 changed files with 384 additions and 126 deletions

View File

@@ -46,14 +46,14 @@ function getInitials(contact) {
</script>
<template>
<div class="p-6">
<div class="p-4 sm:p-6">
<!-- Header -->
<div class="flex items-center justify-between mb-6">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6">
<div>
<h1 class="text-2xl font-bold text-white">Kontakte</h1>
<p class="text-pulse-muted">{{ contacts.meta.total }} Kontakte</p>
<h1 class="text-xl sm:text-2xl font-bold text-white">Kontakte</h1>
<p class="text-pulse-muted text-sm">{{ contacts.meta.total }} Kontakte</p>
</div>
<button @click="showNewModal = true" class="btn-primary">
<button @click="showNewModal = true" class="btn-primary w-full sm:w-auto">
<svg class="w-5 h-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
</svg>
@@ -63,21 +63,21 @@ function getInitials(contact) {
<!-- Search -->
<div class="mb-6">
<div class="relative max-w-md">
<div class="relative w-full sm:max-w-md">
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-pulse-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
<input
v-model="search"
type="text"
class="input pl-10"
class="input pl-10 w-full"
placeholder="Suchen..."
/>
</div>
</div>
<!-- Table -->
<div class="card">
<!-- Desktop Table -->
<div class="card hidden md:block">
<div class="table-container">
<table class="table">
<thead>
@@ -98,7 +98,7 @@ function getInitials(contact) {
>
<td>
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-full bg-primary-600 flex items-center justify-center text-white text-sm font-medium">
<div class="w-9 h-9 rounded-full bg-primary-600 flex items-center justify-center text-white text-sm font-medium flex-shrink-0">
{{ getInitials(contact) }}
</div>
<div>
@@ -117,29 +117,84 @@ function getInitials(contact) {
</tbody>
</table>
</div>
</div>
<!-- Empty State -->
<div v-if="!contacts.loading && !contacts.contacts.length" class="p-12 text-center">
<svg class="w-16 h-16 mx-auto mb-4 text-pulse-muted opacity-50" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
<p class="text-pulse-muted">Noch keine Kontakte vorhanden</p>
<button @click="showNewModal = true" class="btn-primary mt-4">
Ersten Kontakt anlegen
</button>
<!-- Mobile Card List -->
<div class="md:hidden space-y-3">
<div
v-for="contact in contacts.contacts"
:key="contact.id"
class="card p-4 cursor-pointer active:scale-[0.98] transition-transform"
@click="router.push(`/contacts/${contact.id}`)"
>
<div class="flex items-start gap-3">
<div class="w-10 h-10 rounded-full bg-primary-600 flex items-center justify-center text-white text-sm font-medium flex-shrink-0">
{{ getInitials(contact) }}
</div>
<div class="flex-1 min-w-0">
<p class="font-medium text-white">{{ contact.firstName }} {{ contact.lastName }}</p>
<p v-if="contact.position" class="text-sm text-pulse-muted">{{ contact.position }}</p>
<p v-if="contact.company" class="text-sm text-primary-400">{{ contact.company.name }}</p>
<div class="mt-2 flex flex-wrap gap-2">
<a
v-if="contact.email"
:href="`mailto:${contact.email}`"
@click.stop
class="inline-flex items-center gap-1 text-xs text-pulse-muted hover:text-white px-2 py-1 bg-pulse-dark rounded"
>
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
{{ contact.email }}
</a>
<a
v-if="contact.phone"
:href="`tel:${contact.phone}`"
@click.stop
class="inline-flex items-center gap-1 text-xs text-pulse-muted hover:text-white px-2 py-1 bg-pulse-dark rounded"
>
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
</svg>
{{ contact.phone }}
</a>
</div>
</div>
<svg class="w-5 h-5 text-pulse-muted flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</div>
</div>
</div>
<!-- Empty State -->
<div v-if="!contacts.loading && !contacts.contacts.length" class="card p-8 sm:p-12 text-center">
<svg class="w-12 sm:w-16 h-12 sm:h-16 mx-auto mb-4 text-pulse-muted opacity-50" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
<p class="text-pulse-muted">Noch keine Kontakte vorhanden</p>
<button @click="showNewModal = true" class="btn-primary mt-4">
Ersten Kontakt anlegen
</button>
</div>
<!-- New Contact Modal -->
<Teleport to="body">
<div v-if="showNewModal" class="fixed inset-0 z-50 flex items-center justify-center">
<div v-if="showNewModal" class="fixed inset-0 z-50 flex items-end sm:items-center justify-center p-0 sm:p-4">
<div class="absolute inset-0 bg-black/60" @click="showNewModal = false"></div>
<div class="relative card w-full max-w-lg mx-4">
<div class="px-6 py-4 border-b border-pulse-border">
<h2 class="text-lg font-semibold text-white">Neuer Kontakt</h2>
<div class="relative card w-full sm:max-w-lg rounded-b-none sm:rounded-b-xl max-h-[90vh] overflow-y-auto">
<div class="px-4 sm:px-6 py-4 border-b border-pulse-border sticky top-0 bg-pulse-card z-10">
<div class="flex items-center justify-between">
<h2 class="text-lg font-semibold text-white">Neuer Kontakt</h2>
<button @click="showNewModal = false" class="p-2 text-pulse-muted hover:text-white sm:hidden">
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
<form @submit.prevent="createContact" class="p-6 space-y-4">
<div class="grid grid-cols-2 gap-4">
<form @submit.prevent="createContact" class="p-4 sm:p-6 space-y-4">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="label">Vorname *</label>
<input v-model="newContact.firstName" type="text" class="input" required />
@@ -161,7 +216,7 @@ function getInitials(contact) {
<label class="label">Position</label>
<input v-model="newContact.position" type="text" class="input" placeholder="z.B. Geschäftsführer" />
</div>
<div class="flex gap-3 pt-2">
<div class="flex flex-col-reverse sm:flex-row gap-3 pt-2">
<button type="button" @click="showNewModal = false" class="btn-secondary flex-1">
Abbrechen
</button>