Compare commits
7 Commits
4b07e68aa0
...
09329a053e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09329a053e | ||
|
|
b083f3ac77 | ||
|
|
cce632d821 | ||
|
|
7bc93b2bc7 | ||
|
|
7f0b433ecd | ||
|
|
e245aed3d7 | ||
|
|
27361443b9 |
@@ -192,7 +192,7 @@ export default function CompanyCustomersPage() {
|
||||
onClick={() => handleOpenDialog()}
|
||||
disabled={loading || partners.length === 0}
|
||||
className="bg-indigo-600 hover:bg-indigo-700 text-white font-semibold"
|
||||
title={partners.length === 0 ? 'Es müssen zuerst Partner für diese Firma existieren' : ''}
|
||||
title={partners.length === 0 ? 'Es müssen zuerst Benutzer für diese Firma existieren' : ''}
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" /> Endkunde anlegen
|
||||
</Button>
|
||||
|
||||
@@ -91,7 +91,7 @@ export default function CompaniesPage() {
|
||||
|
||||
|
||||
const handleDelete = async (id: string, firmName: string) => {
|
||||
if (!confirm(`Firma "${firmName}" wirklich löschen? Alle zugewiesenen Partner werden getrennt.`)) return
|
||||
if (!confirm(`Firma "${firmName}" wirklich löschen? Alle zugewiesenen Benutzer werden getrennt.`)) return
|
||||
setDeletingId(id)
|
||||
try {
|
||||
await deleteCompany(id)
|
||||
|
||||
@@ -14,12 +14,22 @@ async function getKpis() {
|
||||
{ count: userCount },
|
||||
{ count: productCount },
|
||||
] = await Promise.all([
|
||||
adminDb.from('orders').select('total_price, status'),
|
||||
adminDb.from('orders').select('total_price, status, created_at'),
|
||||
adminDb.from('profiles').select('*', { count: 'exact', head: true }),
|
||||
supabase.from('products').select('*', { count: 'exact', head: true }),
|
||||
])
|
||||
|
||||
const totalRevenue = (orders ?? []).reduce((sum, o) => sum + Number(o.total_price), 0)
|
||||
const now = new Date()
|
||||
const currentYear = now.getFullYear()
|
||||
const currentMonth = now.getMonth()
|
||||
|
||||
const completedOrdersThisMonth = (orders ?? []).filter(o => {
|
||||
if (o.status !== 'completed') return false
|
||||
const orderDate = new Date(o.created_at)
|
||||
return orderDate.getFullYear() === currentYear && orderDate.getMonth() === currentMonth
|
||||
})
|
||||
|
||||
const totalRevenue = completedOrdersThisMonth.reduce((sum, o) => sum + Number(o.total_price), 0)
|
||||
const orderCount = (orders ?? []).length
|
||||
|
||||
return { totalRevenue, orderCount, userCount: userCount ?? 0, productCount: productCount ?? 0 }
|
||||
@@ -36,14 +46,14 @@ export default async function AdminDashboard() {
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
<Card className="glass-dark border-white/5">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium text-slate-300">Umsatz gesamt</CardTitle>
|
||||
<CardTitle className="text-sm font-medium text-slate-300">Umsatz aktueller Monat</CardTitle>
|
||||
<TrendingUp className="h-4 w-4 text-primary" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold text-white">
|
||||
{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(totalRevenue)}
|
||||
</div>
|
||||
<p className="text-xs text-slate-400">Aus allen Bestellungen</p>
|
||||
<p className="text-xs text-slate-400">Nur fertige Bestellungen</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
|
||||
@@ -27,29 +27,93 @@ export default function Datenschutz() {
|
||||
<div className="space-y-6 text-slate-300">
|
||||
<section className="space-y-2">
|
||||
<h2 className="text-xl font-bold text-white">1. Datenschutz auf einen Blick</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h3 className="text-md font-semibold text-slate-200">Allgemeiner Hinweis</h3>
|
||||
<p className="text-sm text-slate-400 leading-relaxed">
|
||||
Wir nehmen den Schutz Ihrer persönlichen Daten sehr ernst. Personenbezogene Daten werden auf dieser Webseite nur im technisch notwendigen Umfang erhoben und verarbeitet. Die nachfolgenden Erklärungen geben Ihnen einen Überblick darüber, wie wir diesen Schutz gewährleisten und welche Art von Daten zu welchem Zweck erhoben werden.
|
||||
Die Betreiber dieser Seiten nehmen den Schutz Ihrer persönlichen Daten sehr ernst. Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend der gesetzlichen Datenschutzvorschriften sowie dieser Datenschutzerklärung. Wir weisen darauf hin, dass die Datenübertragung im Internet (z.B. bei der Kommunikation per E-Mail) Sicherheitslücken aufweisen kann. Ein lückenloser Schutz der Daten vor dem Zugriff durch Dritte ist nicht möglich.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-md font-semibold text-slate-200">Wer ist verantwortlich für die Datenerfassung auf dieser Website?</h3>
|
||||
<p className="text-sm text-slate-400 leading-relaxed">
|
||||
Die Datenverarbeitung auf dieser Website erfolgt durch den Websitebetreiber CASPOS GmbH. Dessen Kontaktdaten können Sie dem Impressum dieser Website entnehmen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-md font-semibold text-slate-200">Welche Rechte haben Sie bezüglich Ihrer Daten?</h3>
|
||||
<p className="text-sm text-slate-400 leading-relaxed">
|
||||
Sie haben jederzeit das Recht unentgeltlich Auskunft über Herkunft, Empfänger und Zweck Ihrer gespeicherten personenbezogenen Daten zu erhalten. Sie haben außerdem ein Recht, die Berichtigung, Sperrung oder Löschung dieser Daten zu verlangen. Hierzu sowie zu weiteren Fragen zum Thema Datenschutz können Sie sich jederzeit unter der im Impressum angegebenen Adresse an uns wenden.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="space-y-2">
|
||||
<h2 className="text-xl font-bold text-white">2. Erhebung, Verarbeitung und Nutzung personenbezogener Daten</h2>
|
||||
<p className="text-sm text-slate-400 leading-relaxed">
|
||||
Sie können unsere Webseiten grundsätzlich besuchen, ohne dass wir personenbezogene Daten von Ihnen erheben. Personenbezogene Daten werden nur erhoben, wenn Sie uns diese von sich aus zur Durchführung eines Vertrages, oder im Rahmen der Kontaktaufnahme mitteilen. Diese Daten werden ohne Ihre ausdrückliche Einwilligung jeweils allein zur Vertragsabwicklung und Bearbeitung Ihrer Anfragen genutzt.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-2">
|
||||
<h2 className="text-xl font-bold text-white">2. Verantwortliche Stelle</h2>
|
||||
<h2 className="text-xl font-bold text-white">3. Analyse-Tools und Tools von Drittanbietern</h2>
|
||||
<p className="text-sm text-slate-400 leading-relaxed">
|
||||
Verantwortlich für die Datenverarbeitung auf dieser Website ist die CASPOS GmbH (Anschrift siehe Impressum).
|
||||
Beim Besuch unserer Website werden keine Analyse-Tools eingesetzt.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-2">
|
||||
<h2 className="text-xl font-bold text-white">3. Datenerfassung auf unserer Website</h2>
|
||||
<h2 className="text-xl font-bold text-white">4. Cookies</h2>
|
||||
<p className="text-sm text-slate-400 leading-relaxed">
|
||||
Ihre Daten werden zum einen dadurch erhoben, dass Sie uns diese mitteilen. Hierbei kann es sich z. B. um Daten handeln, die Sie in ein Kontakt- oder Bestellformular eingeben. Andere Daten werden automatisch beim Besuch der Website durch unsere IT-Systeme erfasst. Das sind vor allem technische Daten (z. B. Internetbrowser, Betriebssystem oder Uhrzeit des Seitenaufrufs).
|
||||
CASPOS GmbH verwendet keine so genannte Cookies.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-2">
|
||||
<h2 className="text-xl font-bold text-white">4. Ihre Rechte</h2>
|
||||
<h2 className="text-xl font-bold text-white">5. Server-Log-Dateien</h2>
|
||||
<p className="text-sm text-slate-400 leading-relaxed">
|
||||
Sie haben jederzeit das Recht, unentgeltlich Auskunft über Herkunft, Empfänger und Zweck Ihrer gespeicherten personenbezogenen Daten zu erhalten. Sie haben außerdem ein Recht, die Berichtigung, Sperrung oder Löschung dieser Daten zu verlangen.
|
||||
Der Provider der Seiten erhebt und speichert automatisch Informationen in so genannten Server-Log-Dateien, die Ihr Browser automatisch an uns übermittelt. Dies sind:
|
||||
</p>
|
||||
<ul className="list-disc pl-5 text-sm text-slate-400 space-y-1">
|
||||
<li>Browsertyp und Browserversion</li>
|
||||
<li>verwendetes Betriebssystem</li>
|
||||
<li>Referrer URL</li>
|
||||
<li>Hostname des zugreifenden Rechners</li>
|
||||
<li>Uhrzeit der Serveranfrage</li>
|
||||
<li>IP-Adresse</li>
|
||||
</ul>
|
||||
<p className="text-sm text-slate-400 leading-relaxed mt-2">
|
||||
Eine Zusammenführung dieser Daten mit anderen Datenquellen wird nicht vorgenommen.
|
||||
</p>
|
||||
<p className="text-sm text-slate-400 leading-relaxed">
|
||||
Grundlage für die Datenverarbeitung ist Art. 6 Abs. 1 lit. f DSGVO, der die Verarbeitung von Daten zur Erfüllung eines Vertrags oder vorvertraglicher Maßnahmen gestattet.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-2">
|
||||
<h2 className="text-xl font-bold text-white">6. Weitergabe personenbezogener Daten</h2>
|
||||
<p className="text-sm text-slate-400 leading-relaxed">
|
||||
Personenbezogenen Daten werden von uns im Rahmen der Vertragsabwicklung an das mit der Lieferung beauftragte Transportunternehmen weitergegeben, soweit dies zur Lieferung der Ware erforderlich ist.
|
||||
</p>
|
||||
<p className="text-sm text-slate-400 leading-relaxed">
|
||||
Bei Vorlage einer richterlichen Anordnung darf CASPOS GmbH Ihre persönlichen Daten an die Strafverfolgungsbehörden weitergeben, ohne Sie davon zu informieren. Ebenso behält sich CASPOS GmbH vor, Ihre persönlichen Daten für gerichtliche Auseinandersetzungen einzusetzen, falls dies notwendig ist.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-2">
|
||||
<h2 className="text-xl font-bold text-white">7. Kontaktformular</h2>
|
||||
<p className="text-sm text-slate-400 leading-relaxed">
|
||||
Wenn Sie uns per Kontaktformular Anfragen zukommen lassen, werden Ihre Angaben aus dem Anfrageformular inklusive der von Ihnen dort angegebenen Kontaktdaten zwecks Bearbeitung der Anfrage und für den Fall von Anschlussfragen bei uns gespeichert.
|
||||
</p>
|
||||
<p className="text-sm text-slate-400 leading-relaxed">
|
||||
Die Verarbeitung der in das Kontaktformular eingegebenen Daten erfolgt somit ausschließlich auf Grundlage Ihrer Einwilligung (Art. 6 Abs. 1 lit. a DSGVO). Sie können diese Einwilligung jederzeit widerrufen. Dazu reicht eine formlose Mitteilung per E-Mail an uns. Die Rechtmäßigkeit der bis zum Widerruf erfolgten Datenverarbeitungsvorgänge bleibt vom Widerruf unberührt.
|
||||
</p>
|
||||
<p className="text-sm text-slate-400 leading-relaxed">
|
||||
Die von Ihnen im Kontaktformular eingegebenen Daten verbleiben bei uns, bis Sie uns zur Löschung auffordern, Ihre Einwilligung zur Speicherung widerrufen oder der Zweck für die Datenspeicherung entfällt (z.B. nach abgeschlossener Bearbeitung Ihrer Anfrage). Zwingende gesetzliche Bestimmungen – insbesondere Aufbewahrungsfristen – bleiben unberührt.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -26,22 +26,22 @@ export default function Impressum() {
|
||||
|
||||
<div className="space-y-6 text-slate-300">
|
||||
<section className="space-y-2">
|
||||
<h2 className="text-lg font-bold text-white">CASPOS GmbH</h2>
|
||||
<h2 className="text-lg font-bold text-white">CASPOS Computerabrechnungssysteme GmbH</h2>
|
||||
<p className="text-sm text-slate-400">
|
||||
Musterstraße 123<br />
|
||||
12345 Musterstadt
|
||||
Alte Bundesstraße 16<br />
|
||||
D-76846 Hauenstein
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-2">
|
||||
<h2 className="text-lg font-bold text-white">Vertreten durch:</h2>
|
||||
<p className="text-sm text-slate-400">Max Mustermann (Geschäftsführer)</p>
|
||||
<h2 className="text-lg font-bold text-white">Vertreten durch die Geschäftsführer:</h2>
|
||||
<p className="text-sm text-slate-400">Helmut Dietz, Tamara Korn, Julian Winter</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-2">
|
||||
<h2 className="text-lg font-bold text-white">Kontakt:</h2>
|
||||
<p className="text-sm text-slate-400">
|
||||
Telefon: +49 (0) 123 456789<br />
|
||||
Telefon: +49 (0) 6392 – 8598 440<br />
|
||||
E-Mail: info@caspos.de
|
||||
</p>
|
||||
</section>
|
||||
@@ -49,19 +49,61 @@ export default function Impressum() {
|
||||
<section className="space-y-2">
|
||||
<h2 className="text-lg font-bold text-white">Registereintrag:</h2>
|
||||
<p className="text-sm text-slate-400">
|
||||
Eintragung im Handelsregister.<br />
|
||||
Registergericht: Amtsgericht Musterstadt<br />
|
||||
Registernummer: HRB 12345
|
||||
Handelsregister: HRB 32806, Amtsgericht Zweibrücken
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-2">
|
||||
<h2 className="text-lg font-bold text-white">Umsatzsteuer-ID:</h2>
|
||||
<h2 className="text-lg font-bold text-white">Umsatzsteuer-ID & Steuernummer:</h2>
|
||||
<p className="text-sm text-slate-400">
|
||||
Umsatzsteuer-Identifikationsnummer gemäß §27 a Umsatzsteuergesetz:<br />
|
||||
DE 123456789
|
||||
Umsatzsteueridentifikationsnummer: DE 158286059<br />
|
||||
Steuernummer: 35/671/01361
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<hr className="border-slate-900" />
|
||||
|
||||
<section className="space-y-2 text-slate-400 text-xs">
|
||||
<p className="font-semibold text-slate-300">
|
||||
© 2022 CASPOS Computerabrechnungssysteme GmbH – Alle Rechte vorbehalten
|
||||
</p>
|
||||
<p className="leading-relaxed">
|
||||
Sämtliches Material auf dieser Website wie Texte, Bilder, Illustrationen, Audio- und Videoclips, um nur ein paar Beispiele zu nennen (allgemein „das Material“ genannt), sind urheberrechtlich geschützt und dürfen für keinen anderen Zweck verwendet werden. Abdruck, kopieren, verteilen, Fremdnutzung, Posting und andere unerlaubte Nutzung des Materials ist hiermit ausdrücklich verboten.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<hr className="border-slate-900" />
|
||||
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-xl font-bold text-white">Haftungsausschluss</h2>
|
||||
|
||||
<section className="space-y-2">
|
||||
<h3 className="text-md font-semibold text-slate-200">1. Inhalt des Onlineangebotes</h3>
|
||||
<p className="text-sm text-slate-400 leading-relaxed">
|
||||
Der Autor übernimmt keinerlei Gewähr für die Aktualität, Korrektheit, Vollständigkeit oder Qualität der bereitgestellten Informationen. Haftungsansprüche gegen den Autor, welche sich auf Schäden materieller oder ideeller Art beziehen, die durch die Nutzung oder Nichtnutzung der dargebotenen Informationen bzw. durch die Nutzung fehlerhafter und unvollständiger Informationen verursacht wurden, sind grundsätzlich ausgeschlossen, sofern seitens des Autors kein nachweislich vorsätzliches oder grob fahrlässiges Verschulden vorliegt.
|
||||
</p>
|
||||
<p className="text-sm text-slate-400 leading-relaxed">
|
||||
Alle Angebote sind freibleibend und unverbindlich. Der Autor behält es sich ausdrücklich vor, Teile der Seiten oder das gesamte Angebot ohne gesonderte Ankündigung zu verändern, zu ergänzen, zu löschen oder die Veröffentlichung zeitweise oder endgültig einzustellen.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-2">
|
||||
<h3 className="text-md font-semibold text-slate-200">2. Verweise und Links</h3>
|
||||
<p className="text-sm text-slate-400 leading-relaxed">
|
||||
Bei direkten oder indirekten Verweisen auf fremde Webseiten („Hyperlinks“), die außerhalb des Verantwortungsbereiches des Autors liegen, würde eine Haftungsverpflichtung ausschließlich in dem Fall in Kraft treten, in dem der Autor von den Inhalten Kenntnis hat und es ihm technisch möglich und zumutbar wäre, die Nutzung im Falle rechtswidriger Inhalte zu verhindern. Der Autor erklärt hiermit ausdrücklich, dass zum Zeitpunkt der Linksetzung keine illegalen Inhalte auf den zu verlinkenden Seiten erkennbar waren.
|
||||
</p>
|
||||
<p className="text-sm text-slate-400 leading-relaxed">
|
||||
Auf die aktuelle und zukünftige Gestaltung, die Inhalte oder die Urheberschaft der gelinkten/verknüpften Seiten hat der Autor keinerlei Einfluss. Deshalb distanziert er sich hiermit ausdrücklich von allen Inhalten aller gelinkten /verknüpften Seiten, die nach der Linksetzung verändert wurden. Diese Feststellung gilt für alle innerhalb des eigenen Internetangebotes gesetzten Links und Verweise sowie für Fremdeinträge in vom Autor eingerichteten Gästebüchern, Diskussionsforen und Mailinglisten. Für illegale, fehlerhafte oder unvollständige Inhalte und insbesondere für Schäden, die aus der Nutzung oder Nichtnutzung solcherart dargebotener Informationen entstehen, haftet allein der Anbieter der Seite, auf welche verwiesen wurde, nicht derjenige, der über Links auf die jeweilige Veröffentlichung lediglich verweist.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="space-y-2">
|
||||
<h3 className="text-md font-semibold text-slate-200">4. Rechtswirksamkeit dieses Haftungsausschlusses</h3>
|
||||
<p className="text-sm text-slate-400 leading-relaxed">
|
||||
Dieser Haftungsausschluss ist als Teil des Internetangebotes zu betrachten, von dem aus auf diese Seite verwiesen wurde. Sofern Teile oder einzelne Formulierungen dieses Textes der geltenden Rechtslage nicht, nicht mehr oder nicht vollständig entsprechen sollten, bleiben die übrigen Teile des Dokumentes in ihrem Inhalt und ihrer Gültigkeit davon unberührt.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@ const defaultUrl = process.env.VERCEL_URL
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL(defaultUrl),
|
||||
title: "CASPOS-Shop | Die Kasse",
|
||||
description: "Konfigurieren und bestellen Sie Ihre maßgeschneiderte Business-Software in Minuten.",
|
||||
description: "Konfigurieren Sie Ihre maßgeschneiderte Business-Software in Minuten und fragen Sie sie an.",
|
||||
};
|
||||
|
||||
const geistSans = Geist({
|
||||
|
||||
@@ -44,10 +44,10 @@ export default async function MyOrdersPage() {
|
||||
<div>
|
||||
<h1 className="text-3xl font-extrabold tracking-tight flex items-center gap-3">
|
||||
<ShoppingBag className="w-8 h-8 text-primary" />
|
||||
Meine Bestellungen
|
||||
Meine Anfragen
|
||||
</h1>
|
||||
<p className="text-slate-400 text-sm mt-1">
|
||||
Alle Ihre Bestellungen auf einen Blick – inkl. aktuellem Status.
|
||||
Alle Ihre Anfragen auf einen Blick – inkl. aktuellem Status.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -55,16 +55,16 @@ export default async function MyOrdersPage() {
|
||||
{/* Fehler */}
|
||||
{error && (
|
||||
<div className="bg-red-500/10 border border-red-500/20 text-red-400 rounded-xl p-4 text-sm">
|
||||
Fehler beim Laden der Bestellungen: {error.message}
|
||||
Fehler beim Laden der Anfragen: {error.message}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Keine Bestellungen */}
|
||||
{/* Keine Anfragen */}
|
||||
{!error && (!orders || orders.length === 0) && (
|
||||
<Card className="glass-dark border-white/10">
|
||||
<CardContent className="flex flex-col items-center justify-center py-16 gap-4">
|
||||
<Package className="w-12 h-12 text-slate-600" />
|
||||
<p className="text-slate-400 text-lg">Sie haben noch keine Bestellungen aufgegeben.</p>
|
||||
<p className="text-slate-400 text-lg">Sie haben noch keine Anfragen aufgegeben.</p>
|
||||
<Link href="/order">
|
||||
<Button>Jetzt konfigurieren</Button>
|
||||
</Link>
|
||||
@@ -72,7 +72,7 @@ export default async function MyOrdersPage() {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Bestellliste */}
|
||||
{/* Anfragenliste */}
|
||||
<div className="space-y-4">
|
||||
{(orders ?? []).map((order) => {
|
||||
const o = order as Order
|
||||
@@ -83,7 +83,7 @@ export default async function MyOrdersPage() {
|
||||
{/* Kopfzeile */}
|
||||
<div className="flex items-start justify-between gap-4 flex-wrap">
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs text-slate-500 uppercase tracking-wider">Bestellnummer</p>
|
||||
<p className="text-xs text-slate-500 uppercase tracking-wider">Anfragenummer</p>
|
||||
<p className="font-mono font-bold text-primary text-lg">#{o.order_number}</p>
|
||||
</div>
|
||||
<div className="space-y-1 text-right">
|
||||
@@ -136,7 +136,7 @@ export default async function MyOrdersPage() {
|
||||
<>
|
||||
<Button variant="ghost" size="sm" asChild className="text-xs text-slate-400 hover:text-white">
|
||||
<a href={o.pdf_url} target="_blank" rel="noopener noreferrer">
|
||||
<ExternalLink className="w-3 h-3 mr-1" /> Angebotsbestätigung ansehen
|
||||
<ExternalLink className="w-3 h-3 mr-1" /> Anfrage ansehen
|
||||
</a>
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" asChild className="text-xs text-slate-400 hover:text-white">
|
||||
|
||||
@@ -50,20 +50,20 @@ export default async function OrderSuccessPage({
|
||||
<CheckCircle2 className="w-12 h-12 text-green-400" />
|
||||
</div>
|
||||
<h1 className="text-4xl font-extrabold tracking-tight text-white">
|
||||
Bestellung eingegangen!
|
||||
Anfrage eingegangen!
|
||||
</h1>
|
||||
<p className="text-slate-400 text-lg">
|
||||
Ihre Bestellung wurde erfolgreich gespeichert.
|
||||
Ihre Anfrage wurde erfolgreich gespeichert.
|
||||
</p>
|
||||
<div className="inline-flex items-center gap-2 bg-white/5 border border-white/10 rounded-full px-5 py-2 font-mono text-lg font-bold text-primary">
|
||||
#{o.order_number}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bestell-Details */}
|
||||
{/* Anfrage-Details */}
|
||||
<Card className="glass-dark border-white/10">
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<CardTitle className="text-white">Bestellübersicht</CardTitle>
|
||||
<CardTitle className="text-white">Anfrageübersicht</CardTitle>
|
||||
<Badge
|
||||
className={
|
||||
o.status === 'active'
|
||||
|
||||
@@ -197,24 +197,24 @@ export function UserDialog({ companies }: UserDialogProps) {
|
||||
render={({ field }) => {
|
||||
const selectedCompany = localCompanies.find((c) => c.id === field.value)
|
||||
return (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormItem className="w-full">
|
||||
<FormLabel className="text-slate-900 dark:text-white">Firma</FormLabel>
|
||||
<FormControl>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="relative" ref={dropdownRef}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Building2 className="w-4 h-4 text-slate-500 shrink-0" />
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<div className="relative w-full" ref={dropdownRef}>
|
||||
<div className="flex flex-col gap-1 w-full">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setDropdownOpen(!dropdownOpen)
|
||||
setSearch('')
|
||||
}}
|
||||
className="w-full text-left text-sm bg-slate-50 dark:bg-white/5 border border-slate-200 dark:border-white/10 rounded-md px-3 py-2 text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-white/10 cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 max-w-full truncate"
|
||||
className="w-full text-left text-sm bg-slate-50 dark:bg-white/5 border border-slate-200 dark:border-white/10 rounded-md px-3 py-2 text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-white/10 cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 truncate"
|
||||
>
|
||||
{selectedCompany ? selectedCompany.name : 'Keine Firma'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{dropdownOpen && (
|
||||
<div className="absolute z-50 mt-1 left-0 w-full bg-white dark:bg-slate-900 border border-slate-200 dark:border-white/10 rounded-lg shadow-xl overflow-hidden">
|
||||
<div className="p-2 border-b border-slate-200 dark:border-white/10">
|
||||
@@ -227,7 +227,7 @@ export function UserDialog({ companies }: UserDialogProps) {
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<div className="max-h-40 overflow-y-auto">
|
||||
<div className="max-h-40 overflow-y-auto flex flex-col">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
@@ -235,8 +235,7 @@ export function UserDialog({ companies }: UserDialogProps) {
|
||||
setDropdownOpen(false)
|
||||
setSearch('')
|
||||
}}
|
||||
className={`w-full text-left text-xs px-3 py-2 hover:bg-slate-100 dark:hover:bg-white/10 transition-colors ${
|
||||
!field.value ? 'text-primary font-semibold' : 'text-slate-700 dark:text-slate-300'
|
||||
className={`w-full text-left text-xs px-3 py-2 hover:bg-slate-100 dark:hover:bg-white/10 transition-colors ${!field.value ? 'text-primary font-semibold' : 'text-slate-700 dark:text-slate-300'
|
||||
}`}
|
||||
>
|
||||
Keine Firma
|
||||
@@ -252,8 +251,7 @@ export function UserDialog({ companies }: UserDialogProps) {
|
||||
setDropdownOpen(false)
|
||||
setSearch('')
|
||||
}}
|
||||
className={`w-full text-left text-xs px-3 py-2 hover:bg-slate-100 dark:hover:bg-white/10 transition-colors ${
|
||||
field.value === c.id ? 'text-primary font-semibold' : 'text-slate-700 dark:text-slate-300'
|
||||
className={`w-full text-left text-xs px-3 py-2 hover:bg-slate-100 dark:hover:bg-white/10 transition-colors truncate ${field.value === c.id ? 'text-primary font-semibold' : 'text-slate-700 dark:text-slate-300'
|
||||
}`}
|
||||
>
|
||||
{c.name}
|
||||
@@ -265,13 +263,14 @@ export function UserDialog({ companies }: UserDialogProps) {
|
||||
</div>
|
||||
|
||||
{showNewCompanyInput ? (
|
||||
<div className="flex gap-2 mt-1">
|
||||
<div className="flex flex-col sm:flex-row gap-2 mt-1 w-full">
|
||||
<Input
|
||||
placeholder="Name der neuen Firma"
|
||||
value={newCompanyName}
|
||||
onChange={(e) => setNewCompanyName(e.target.value)}
|
||||
className="bg-slate-50 dark:bg-white/5 border-slate-200 dark:border-white/10 text-slate-900 dark:text-white text-xs h-8"
|
||||
className="bg-slate-50 dark:bg-white/5 border-slate-200 dark:border-white/10 text-slate-900 dark:text-white text-xs h-8 w-full"
|
||||
/>
|
||||
<div className="flex gap-2 justify-end">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
@@ -313,6 +312,7 @@ export function UserDialog({ companies }: UserDialogProps) {
|
||||
Abbrechen
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -110,17 +110,17 @@ export const InvoicePDF = ({ order, orderSnapshot, customer }: any) => {
|
||||
<Text>Software Solutions</Text>
|
||||
</View>
|
||||
<View style={styles.companyInfo}>
|
||||
<Text>CASPOS GmbH</Text>
|
||||
<Text>Musterstraße 1</Text>
|
||||
<Text>12345 Musterstadt</Text>
|
||||
<Text>CASPOS Computerabrechnungssysteme GmbH</Text>
|
||||
<Text>Alte Bundesstraße 16</Text>
|
||||
<Text>76846 Hauenstein</Text>
|
||||
<Text>Deutschland</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text style={styles.title}>Angebotsbestätigung</Text>
|
||||
<Text style={styles.title}>Anfragebestätigung</Text>
|
||||
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.label}>Besteller / Kunde</Text>
|
||||
<Text style={styles.label}>Anfragender Kunde</Text>
|
||||
<Text>{customer.company_name}</Text>
|
||||
<Text>{customer.first_name} {customer.last_name}</Text>
|
||||
<Text>{customer.address}</Text>
|
||||
@@ -129,8 +129,8 @@ export const InvoicePDF = ({ order, orderSnapshot, customer }: any) => {
|
||||
</View>
|
||||
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.label}>Bestelldetails</Text>
|
||||
<Text>Bestellnummer: {order.order_number || order.id}</Text>
|
||||
<Text style={styles.label}>Anfragedetails</Text>
|
||||
<Text>Anfrage-Nummer: {order.order_number || order.id}</Text>
|
||||
<Text>Datum: {new Date(order.created_at || Date.now()).toLocaleDateString('de-DE')}</Text>
|
||||
</View>
|
||||
|
||||
@@ -213,7 +213,10 @@ export const InvoicePDF = ({ order, orderSnapshot, customer }: any) => {
|
||||
<Text>{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(taxAmount)}</Text>
|
||||
</View>
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginTop: 5, borderTopWidth: 1, borderTopColor: '#000', paddingTop: 5 }}>
|
||||
<Text style={styles.totalLabel}>Gesamtbetrag (netto):</Text>
|
||||
<Text style={styles.totalLabel}>MwSt.Betrag:</Text>
|
||||
<Text style={styles.totalLabel}>Gesamtbetrag (brutto):</Text>
|
||||
|
||||
<Text style={styles.totalLabel}>{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(total)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -133,8 +133,8 @@ export async function anonymizeEndCustomer(id: string): Promise<void> {
|
||||
company_name: '[GELÖSCHT]',
|
||||
vat_id: null,
|
||||
first_name: '[GELÖSCHT]',
|
||||
last_name: null,
|
||||
street: null,
|
||||
last_name: '[GELÖSCHT]',
|
||||
street: '[GELÖSCHT]',
|
||||
zip: null,
|
||||
city: null,
|
||||
email: null,
|
||||
|
||||
@@ -205,7 +205,7 @@ export async function submitOrder(params: {
|
||||
)
|
||||
|
||||
// 6. PDF in Supabase Storage hochladen
|
||||
const fileName = `angebotsbestaetigung_${order.id}.pdf`
|
||||
const fileName = `ab_${order.id}.pdf`
|
||||
const { error: uploadError } = await supabase
|
||||
.storage
|
||||
.from('invoices')
|
||||
@@ -227,18 +227,18 @@ export async function submitOrder(params: {
|
||||
|
||||
await sendMail({
|
||||
to: user.email,
|
||||
subject: `Angebotsbestätigung ${orderNumber}`,
|
||||
text: `Hallo,\n\nvielen Dank für Ihre Bestellung bei CASPOS Shop!\n\nBestelldetails:\n- Bestellnummer: ${orderNumber}\n- Datum: ${formattedDate}\n- Endkunde: ${customerSnapshot.company_name}\n- Gesamtsumme: ${formattedTotal}\n\nIm Anhang dieser E-Mail finden Sie Ihre Angebotsbestätigung als PDF-Dokument.\n\nViele Grüße,\nIhr CASPOS Shop-Team`,
|
||||
subject: `Anfragebestätigung ${orderNumber}`,
|
||||
text: `Hallo,\n\nvielen Dank für Ihre Anfrage bei CASPOS Shop!\n\nAngebotsdetails:\n- Anfrage-Nummer: ${orderNumber}\n- Datum: ${formattedDate}\n- Endkunde: ${customerSnapshot.company_name}\n- Gesamtsumme: ${formattedTotal}\n\nIm Anhang dieser E-Mail finden Sie Ihre Anfragebestätigung als PDF-Dokument.\n\nViele Grüße,\nIhr CASPOS Shop-Team`,
|
||||
html: `
|
||||
<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #e2e8f0; border-radius: 8px;">
|
||||
<h2 style="color: #0f172a; margin-bottom: 16px;">Vielen Dank für Ihre Bestellung!</h2>
|
||||
<h2 style="color: #0f172a; margin-bottom: 16px;">Vielen Dank für Ihre Anfrage!</h2>
|
||||
<p style="color: #475569; font-size: 16px; line-height: 1.5;">Hallo,</p>
|
||||
<p style="color: #475569; font-size: 16px; line-height: 1.5;">wir freuen uns sehr über Ihre Bestellung bei CASPOS Shop. Ihre Bestellung wurde erfolgreich entgegengenommen und wird nun verarbeitet.</p>
|
||||
<p style="color: #475569; font-size: 16px; line-height: 1.5;">wir freuen uns sehr über Ihre Anfrage bei CASPOS Shop. Ihre Anfrage wurde erfolgreich entgegengenommen und wird nun verarbeitet.</p>
|
||||
<div style="background-color: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; padding: 16px; margin: 24px 0;">
|
||||
<h3 style="color: #0f172a; margin-top: 0; margin-bottom: 12px;">Bestelldetails</h3>
|
||||
<h3 style="color: #0f172a; margin-top: 0; margin-bottom: 12px;">Anfrage-Details</h3>
|
||||
<table style="width: 100%; border-collapse: collapse; font-size: 14px; color: #475569;">
|
||||
<tr>
|
||||
<td style="padding: 4px 0; font-weight: bold; width: 140px;">Bestellnummer:</td>
|
||||
<td style="padding: 4px 0; font-weight: bold; width: 140px;">Anfrage-Nummer:</td>
|
||||
<td style="padding: 4px 0;">${orderNumber}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -255,7 +255,7 @@ export async function submitOrder(params: {
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<p style="color: #475569; font-size: 16px; line-height: 1.5;">Im Anhang dieser E-Mail finden Sie Ihre Angebotsbestätigung als PDF-Dokument.</p>
|
||||
<p style="color: #475569; font-size: 16px; line-height: 1.5;">Im Anhang dieser E-Mail finden Sie Ihre Anfragebestätigung als PDF-Dokument.</p>
|
||||
<p style="color: #475569; font-size: 16px; line-height: 1.5; margin-top: 24px;">Mit freundlichen Grüßen,<br>Ihr CASPOS Shop-Team</p>
|
||||
<hr style="border: 0; border-top: 1px solid #e2e8f0; margin: 24px 0;">
|
||||
<p style="color: #94a3b8; font-size: 12px; text-align: center;">Diese E-Mail wurde automatisch generiert. Bitte antworten Sie nicht darauf.</p>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { updateSession } from "@/lib/supabase/proxy";
|
||||
import { type NextRequest } from "next/server";
|
||||
|
||||
export async function middleware(request: NextRequest) {
|
||||
export async function proxy(request: NextRequest) {
|
||||
return await updateSession(request);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user