Added template creator (doesn't work without db)
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import type { Component } from 'solid-js';
|
||||
import { For } from 'solid-js';
|
||||
import { Plus } from 'lucide-solid';
|
||||
import type { TemplateItem } from '../../types';
|
||||
import TemplateItemRow from './TemplateItemRow';
|
||||
|
||||
interface TemplateItemListProps {
|
||||
items: TemplateItem[];
|
||||
onUpdateItem: (id: string, updates: Partial<TemplateItem>) => void;
|
||||
onDeleteItem: (id: string) => void;
|
||||
onAddItem: () => void;
|
||||
}
|
||||
|
||||
const TemplateItemList: Component<TemplateItemListProps> = (props) => {
|
||||
return (
|
||||
<div class="mt-8 space-y-4">
|
||||
<div class="flex items-center justify-between pb-2 border-b border-border/60">
|
||||
<h3 class="text-lg font-semibold text-foreground/90">Template Items</h3>
|
||||
<button
|
||||
onClick={props.onAddItem}
|
||||
class="flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors shadow-sm"
|
||||
>
|
||||
<Plus class="w-4 h-4" />
|
||||
Add Item
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<For each={props.items}>
|
||||
{(item) => (
|
||||
<TemplateItemRow
|
||||
item={item}
|
||||
onUpdate={props.onUpdateItem}
|
||||
onDelete={props.onDeleteItem}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
|
||||
{props.items.length === 0 && (
|
||||
<div class="text-center py-12 text-muted-foreground/60 border-2 border-dashed border-border/60 rounded-xl bg-card">
|
||||
<p>No items added yet. Click "Add Item" to start building this template.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TemplateItemList;
|
||||
@@ -0,0 +1,97 @@
|
||||
import type { Component } from 'solid-js';
|
||||
import { createSignal, createEffect } from 'solid-js';
|
||||
import { Trash2, GripVertical } from 'lucide-solid';
|
||||
import type { TemplateItem } from '../../types';
|
||||
|
||||
interface TemplateItemRowProps {
|
||||
item: TemplateItem;
|
||||
onUpdate: (id: string, updates: Partial<TemplateItem>) => void;
|
||||
onDelete: (id: string) => void;
|
||||
}
|
||||
|
||||
const TemplateItemRow: Component<TemplateItemRowProps> = (props) => {
|
||||
let descriptionRef: HTMLTextAreaElement | undefined;
|
||||
const [isEditing, setIsEditing] = createSignal(false);
|
||||
const [localDescription, setLocalDescription] = createSignal(props.item.description);
|
||||
|
||||
createEffect(() => {
|
||||
if (!isEditing()) {
|
||||
setLocalDescription(props.item.description);
|
||||
}
|
||||
});
|
||||
|
||||
const handleBlur = () => {
|
||||
const currentDesc = localDescription();
|
||||
if (currentDesc !== props.item.description) {
|
||||
props.onUpdate(props.item.id, { description: currentDesc });
|
||||
}
|
||||
setIsEditing(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class={`group flex items-start gap-4 p-3 border transition-all animate-in fade-in slide-in-from-left-2 rounded-2xl bg-card border-border hover:shadow-premium-hover hover:border-border/80 ${isEditing() ? 'ring-1 ring-primary/20 shadow-premium border-primary/30' : ''}`}>
|
||||
|
||||
<div class="cursor-grab transition-colors shrink-0 w-4 mt-2 p-1 -m-1 rounded hover:bg-muted text-muted-foreground/30 hover:text-muted-foreground/60" title="Drag to reorder (Coming soon)">
|
||||
<GripVertical class="w-4 h-4" />
|
||||
</div>
|
||||
|
||||
<div class="flex-1 relative">
|
||||
<textarea
|
||||
ref={descriptionRef}
|
||||
value={localDescription()}
|
||||
onFocus={() => setIsEditing(true)}
|
||||
onBlur={handleBlur}
|
||||
onInput={(e) => {
|
||||
setLocalDescription(e.currentTarget.value);
|
||||
if (!('fieldSizing' in document.documentElement.style)) {
|
||||
e.currentTarget.style.height = 'auto';
|
||||
e.currentTarget.style.height = e.currentTarget.scrollHeight + 'px';
|
||||
}
|
||||
}}
|
||||
rows="1"
|
||||
class="w-full bg-transparent border-b border-transparent hover:border-border/40 focus:border-primary outline-none py-1 transition-all resize-none block overflow-hidden align-top leading-normal text-base font-medium text-foreground/90"
|
||||
style={{ "field-sizing": "content", "min-height": "1.5em" }}
|
||||
placeholder="Item description..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="w-24 shrink-0 mt-1">
|
||||
<input
|
||||
type="number"
|
||||
value={props.item.quantity}
|
||||
onInput={(e) => props.onUpdate(props.item.id, { quantity: parseFloat(e.currentTarget.value) || 0 })}
|
||||
class="w-full bg-muted/30 border border-border/60 rounded-xl px-2 py-1.5 text-xs text-right focus:bg-background focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none font-bold text-foreground/80 transition-all"
|
||||
placeholder="Qty"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="w-32 shrink-0 mt-1">
|
||||
<div class="relative">
|
||||
<span class="absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground/50 text-[10px]">$</span>
|
||||
<input
|
||||
type="number"
|
||||
step="0.1"
|
||||
value={props.item.unitPrice}
|
||||
onInput={(e) => props.onUpdate(props.item.id, { unitPrice: parseFloat(e.currentTarget.value) || 0 })}
|
||||
class="w-full bg-muted/30 border border-border/60 rounded-xl pl-5 pr-2 py-1.5 text-xs text-right focus:bg-background focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none font-bold text-foreground/80 transition-all"
|
||||
placeholder="Price"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-10 shrink-0 flex items-center justify-end gap-1.5 mt-1.5 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button
|
||||
onClick={() => props.onDelete(props.item.id)}
|
||||
class="text-muted-foreground/40 hover:text-destructive transition-colors"
|
||||
title="Delete Item"
|
||||
>
|
||||
<div class="p-1 px-1.5 bg-muted rounded-lg border border-border/40 hover:bg-destructive/10">
|
||||
<Trash2 class="w-3 h-3" />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TemplateItemRow;
|
||||
Reference in New Issue
Block a user