Skip to content

Commit

Permalink
Merge branch 'feat/create-and-delete-shopping-lists' into feat/sync
Browse files Browse the repository at this point in the history
  • Loading branch information
jdoppelhofer committed Apr 11, 2024
2 parents 2093e87 + 739e93e commit e6a6155
Show file tree
Hide file tree
Showing 13 changed files with 404 additions and 20 deletions.
4 changes: 0 additions & 4 deletions .vscode/settings.json

This file was deleted.

8 changes: 4 additions & 4 deletions STORIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
| ID | Description | SP | HEAD | Prio | Status |
| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- | ---- | ---- | -------------------------------------------------------------------- |
| 1 | Als Benutzer möchte ich Einkaufslisten, für die Geschäfte bei denen ich einkaufen möchte, erstellen und löschen können, um meine Einkäufe effektiv zu organisieren und zu verwalten. | 2 | JD | MH | [#6](https://github.com/Gschaftlhaberer/gschaeftlhaberer/issues/6) |
| 2 | Als Benutzer möchte ich in Einkaufslisten Produkte, bestehend aus Namen und Anzahl, erstellen und entfernen können, um zu wissen, welche Produkte ich bei einem Einkauf mitnehmen möchte. | 2 | JD | MH | [#7](https://github.com/Gschaftlhaberer/gschaeftlhaberer/issues/7) |
| 2 | Als Benutzer möchte ich in Einkaufslisten Produkte, bestehend aus Namen und Anzahl, erstellen und entfernen können, um zu wissen, welche Produkte ich bei einem Einkauf mitnehmen möchte. | 2 | PD | MH | [#7](https://github.com/Gschaftlhaberer/gschaeftlhaberer/issues/7) |
| 3 | Als Benutzer möchte ich die Möglichkeit haben, meine Einkaufsliste über eine synchronisierte URL zu teilen, damit andere Personen darauf zugreifen und sie in Echtzeit sehen können, was eine effiziente Koordination von gemeinsamen Einkäufen ermöglicht. | 4 | JP | MH | [#8](https://github.com/Gschaftlhaberer/gschaeftlhaberer/issues/8) |
| 4 | Als Nutzer möchte ich auch ohne Internetverbindung auf meine Einkaufslistendaten zugreifen zu können, um auch im hintersten Eck eines Geschäftes Zugriff zu haben. | 4 | LB | MH | [#9](https://github.com/Gschaftlhaberer/gschaeftlhaberer/issues/9) |
| 5 | Als Benutzer möchte ich eine Auflistung aller meiner Einkaufsliste haben, um alle Geschäfte zu sehen, in denen man einkaufen möchte. | 2 | PD | MH | [#10](https://github.com/Gschaftlhaberer/gschaeftlhaberer/issues/10) |
| 6 | Als Benutzer möchte ich eine Übersicht über alle Produkte, die ich in einer Einklaufsliste habe, bekommen, um zu wissen welche Produkte ich in einem Geschäft kaufe. | 2 | PD | MH | [#11](https://github.com/Gschaftlhaberer/gschaeftlhaberer/issues/11) |
| 5 | Als Benutzer möchte ich eine Auflistung aller meiner Einkaufsliste haben, um alle Geschäfte zu sehen, in denen man einkaufen möchte. | 2 | PD | MH | [#10](https://github.com/Gschaftlhaberer/gschaeftlhaberer/issues/10) |
| 6 | Als Benutzer möchte ich eine Übersicht über alle Produkte, die ich in einer Einklaufsliste habe, bekommen, um zu wissen welche Produkte ich in einem Geschäft kaufe. | 2 | JD | MH | [#11](https://github.com/Gschaftlhaberer/gschaeftlhaberer/issues/11) |
| 7 | Als Benutzer möchte ich eine Suchfunktion nutzen können, um schnell bestimmte Produkte oder Einkaufslisten zu finden, indem ich Stichworte eingeben kann. Dies ermöglicht mir, effizient durch meine Listen zu navigieren und die benötigten Informationen ohne Umwege zu finden. | 2 | JP | SH | [#18](https://github.com/Gschaftlhaberer/gschaeftlhaberer/issues/18) |
| 8 | Als Benutzer möchte ich Listen und Produkte bearbeiten können, um auch im Nachhinein Änderungen vornehmen zu können. | 2 | JD | SH | [#13](https://github.com/Gschaftlhaberer/gschaeftlhaberer/issues/13) |
| 9 | Als Nutzer möchte ich Produkte zwischen Listen verschieben können, um Produkte verschiedenen Geschäften zuordnen zu können. | 2 | LB | SH | [#14](https://github.com/Gschaftlhaberer/gschaeftlhaberer/issues/14) |
| 9 | Als Nutzer möchte ich Produkte zwischen Listen verschieben können, um Produkte verschiedenen Geschäften zuordnen zu können. | 2 | LB | SH | [#14](https://github.com/Gschaftlhaberer/gschaeftlhaberer/issues/14) |
| 10 | Als Benutzer möchte ich meine Produkte mit Tags versehen können, um sie der Abteilung, in der ich mich in einem Geschäft (z.B. Obst, Gemüse) befinde, entsprechend filtern zu können. | 2 | PD | SH | [#15](https://github.com/Gschaftlhaberer/gschaeftlhaberer/issues/15) |
| 11 | Als Benutzer möchte ich ein sicheres Authentifizierungssystem haben, das sicherstellt, dass nur autorisierte Benutzer Zugriff auf meine Einkaufslisten haben, um die Sicherheit und Privatsphäre meiner Daten zu gewährleisten. | 2 | JP | N2H | [#12](https://github.com/Gschaftlhaberer/gschaeftlhaberer/issues/12) |
| 12 | Als Benutzer möchte ich die Möglichkeit haben, zusätzliche Informationen zu Produkten hinzufügen zu können, wie z.B. Preise, Markennamen oder spezielle Angebote. Dies hilft mir, mein Einkaufserlebnis besser zu planen und zu budgetieren. | 2 | PD | N2H | [#19](https://github.com/Gschaftlhaberer/gschaeftlhaberer/issues/19) |
Expand Down
15 changes: 15 additions & 0 deletions app/src/lib/components/ListenItem.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script>
export let liste;
// Hier könntest du Funktionen für das Löschen oder Bearbeiten hinzufügen
</script>

<div>
<h3>{liste.titel}</h3>
<ul>
{#each liste.artikel as artikel}
<li>{artikel.name} ({artikel.menge}) - {artikel.geschäft}</li>
{/each}
</ul>
// Buttons für Bearbeiten und Löschen hier einfügen
</div>
18 changes: 18 additions & 0 deletions app/src/lib/components/ListenÜbersicht.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script>
import { onMount } from 'svelte';
import { db } from '$lib/db';
import ListenItem from './ListenItem.svelte';
let listen = [];
onMount(async () => {
const result = await db.allDocs({ include_docs: true, descending: true });
listen = result.rows.map((row) => row.doc);
});
</script>

<div>
{#each listen as liste}
<ListenItem {liste} />
{/each}
</div>
39 changes: 39 additions & 0 deletions app/src/lib/components/Product.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<script lang="ts">
import type { ExistingDocument, Product, ProductList } from '$lib/db';
import { db as DB } from '$lib/db';
import { get } from 'svelte/store';
import { Card, Heading, Span, Button } from 'flowbite-svelte';
export let product: ExistingDocument<Product>;
async function deleteProduct() {
const db = get(DB);
await db.remove(product);
}
</script>

<Card {...$$restProps} class="flex flex-row items-center justify-between">
<div class="flex-grow">
<Heading class="inline-block flex-shrink" tag="h4">{product.name}</Heading>
</div>
<div class="flex flex-col items-center text-neutral-500 dark:text-neutral-300">
<span class="text-xl">{product.count}</span>
<span class="text-sm">Stück</span>
</div>
<Button on:click={deleteProduct} class="ml-4">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
class="h-6 w-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
></path>
</svg>
</Button>
</Card>
30 changes: 29 additions & 1 deletion app/src/lib/components/ProductList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,36 @@
import { Card, Heading } from 'flowbite-svelte';
export let productlist: ExistingDocument<ProductList>;
import { db as DB } from '$lib/db';
import { get } from 'svelte/store';
async function deleteList(id: string) {
const db = get(DB);
const list = await db.get(id);
await db.remove(list);
}
</script>

<head>
<link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"
rel="stylesheet"
/>
</head>

<Card href={`/items/${productlist._id}`} {...$$restProps}>
<Heading tag="h4">{productlist.name}</Heading>
<div class="flex items-center justify-between">
<Heading tag="h4">{productlist.name}</Heading>
<button
class="inline-flex items-center justify-center rounded bg-red-500 px-4 py-2 font-bold text-white hover:bg-red-700"
on:click|stopPropagation|preventDefault={() => {
if (confirm('Möchten Sie diese Liste wirklich löschen?')) {
deleteList(productlist._id);
}
}}
>
<i class="fas fa-trash-alt"></i>
</button>
</div>
</Card>
9 changes: 8 additions & 1 deletion app/src/lib/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,11 @@ export interface ProductList {
name: string;
}

export const db = writable(new PouchDB<ProductList>('shopping'));
export interface Product {
type: 'product';
list: string;
name: string;
count: number;
}

export const db = writable(new PouchDB<ProductList | Product>('shopping'));
25 changes: 15 additions & 10 deletions app/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
import type { PageData } from './$types';
import { db, type ExistingDocument, type ProductList as PL } from '$lib/db';
import ProductList from '$lib/components/ProductList.svelte';
import { onMount } from 'svelte';
import { browser } from '$app/environment';
export let data: PageData;
console.log($db);
export let lists: ExistingDocument<PL>[] = [];
let cancel_changes: (() => void)[] = [];
Expand All @@ -22,26 +21,23 @@
});
data.lists.then((result) => {
lists = result.docs;
cancel_changes = [
...cancel_changes,
lists = result.docs as never[] | ExistingDocument<PL>[];
if (browser) {
$db
.changes({
since: 'now',
live: true,
include_docs: true,
filter: (doc) => {
console.log(doc);
return doc.type === 'list' || doc._deleted;
}
})
.on('change', (change) => {
console.log('change', change);
if (change.deleted) {
lists = lists.filter((list) => list._id !== change.id);
} else {
const index = lists.findIndex((list) => list._id === change.id);
if (change.doc) {
if (change.doc && change.doc.type === 'list') {
if (index === -1) {
lists = [...lists, change.doc];
} else {
Expand All @@ -51,8 +47,8 @@
console.error('Unreachable. Change event without doc.');
}
}
}).cancel
];
}).cancel;
}
return result;
});
</script>
Expand All @@ -75,4 +71,13 @@
{:catch error}
<Alert color="red">Ein Fehler ist aufgetreten: {error.message}</Alert>
{/await}

<!-- Button zum Erstellen einer neuen Liste -->
<a
href="/createlist"
class="mt-5 inline-flex items-center justify-center rounded bg-green-500 px-4 py-2 font-bold text-white hover:bg-green-700"
>
<i class="fas fa-plus mr-2"></i>
<span>Neue Liste erstellen</span>
</a>
</div>
37 changes: 37 additions & 0 deletions app/src/routes/createlist/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<script>
import { db as DB } from '$lib/db';
import { get } from 'svelte/store';
import { Button, Input, Label } from 'flowbite-svelte';
import { goto } from '$app/navigation';
let name = '';
async function addList() {
if (name.trim() !== '') {
const db = get(DB);
await db.post({
type: 'list',
name: name,
version: ''
});
name = '';
goto('/');
}
}
</script>

<div class="flex min-h-screen flex-col items-center justify-center">
<h1 class="mb-8 text-3xl font-bold text-gray-700">Neue Einkaufsliste erstellen</h1>

<form
class="w-full max-w-md rounded bg-white px-8 py-6 shadow-md"
on:submit|preventDefault={addList}
>
<div class="mb-4">
<Label for="list-name" class="mb-2">Name der Einkaufsliste:</Label>
<Input id="list-name" bind:value={name} placeholder="List name" required class="w-full" />
</div>

<Button type="submit" variant="primary" class="w-full">Liste hinzufügen</Button>
</form>
</div>
113 changes: 113 additions & 0 deletions app/src/routes/items/[id]/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<script lang="ts">
import Product from '$lib/components/Product.svelte';
import { Heading, ListPlaceholder, Alert, P } from 'flowbite-svelte';
import type { PageData } from './$types';
import { db, type ExistingDocument, type Product as PT } from '$lib/db';
import { browser } from '$app/environment';
console.log($db);
export let data: PageData;
export let products: ExistingDocument<PT>[] = [];
data.products.then((result) => {
products = result.docs as ExistingDocument<PT>[];
if (browser) {
$db
.changes({
since: 'now',
live: true,
include_docs: true,
filter: (doc) => {
return doc.type === 'product' || doc._deleted;
}
})
.on('change', (change) => {
if (change.deleted) {
products = products.filter((list) => list._id !== change.id);
} else {
const index = products.findIndex((list) => list._id === change.id);
if (change.doc && change.doc.type === 'product') {
if (index === -1) {
products = [...products, change.doc];
} else {
products[index] = change.doc;
}
} else {
console.error('Unreachable. Change event without doc.');
}
}
}).cancel;
}
return result;
});
import { db as DB } from '$lib/db';
import { get } from 'svelte/store';
import { Button, Card, Input } from 'flowbite-svelte';
let showPopup = false;
let productName = '';
let productCount = 1;
async function createProduct() {
if (productName.trim() !== '') {
console.log(`Produkt erstellt: ${productName}`);
const db = get(DB);
await db.put({
_id: `${data.id}:${productName}`,
type: 'product',
list: data.id,
name: productName,
count: productCount
});
productName = '';
productCount = 1;
showPopup = false;
}
}
</script>

<svelte:head>
<title>G'schäft'lhaberer</title>
</svelte:head>

<Heading class="my-5 text-center">G'schäft'l<wbr />Haberer</Heading>

<div class="mb-5 mt-12 flex flex-col items-center">
{#await data.products}
<ListPlaceholder class="w-full max-w-sm" />
{:then}
{#each products as product}
<Product {product} class="my-2" />
{:else}
<P>Keine Produkte vorhanden</P>
{/each}
{:catch error}
<Alert color="red">Ein Fehler ist aufgetreten: {error.message}</Alert>
{/await}
</div>

<div class="flex items-center justify-center">
<Button on:click={() => (showPopup = true)}>Produkt erstellen</Button>
</div>

{#if showPopup}
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
<Card class="rounded bg-white p-6 shadow-lg">
<h2 class="mb-4 text-center text-xl font-bold">Produkt erstellen</h2>
<Input bind:value={productName} placeholder="Produktname" class="mb-4" />
<Input type="number" bind:value={productCount} placeholder="Anzahl" class="mb-4" />
<div class="flex justify-end space-x-2">
<Button on:click={createProduct} class="bg-blue-500 text-white hover:bg-blue-700"
>Erstellen</Button
>
<Button
variant="secondary"
on:click={() => (showPopup = false)}
class="bg-gray-500 text-white hover:bg-gray-700">Schließen</Button
>
</div>
</Card>
</div>
{/if}
Loading

0 comments on commit e6a6155

Please sign in to comment.