added buckets
This commit is contained in:
+162
-2
@@ -1,8 +1,8 @@
|
||||
import { type Component, For, Show, createSignal, lazy, Suspense } from "solid-js";
|
||||
import { ThemeToggle } from "../components/ThemeToggle";
|
||||
import { store, restoreTask, deleteTaskPermanently, setMatrixScaleDays, addTemplate, removeTemplate, updateTemplate, upsertTagDefinition, removeTagDefinition, renameTagDefinition, addShareRule, removeShareRule } from "@/store";
|
||||
import { store, restoreTask, deleteTaskPermanently, setMatrixScaleDays, addTemplate, removeTemplate, updateTemplate, upsertTagDefinition, removeTagDefinition, renameTagDefinition, addShareRule, removeShareRule, createBucket, updateBucket, deleteBucket, toggleBucketSubscription } from "@/store";
|
||||
import { useTheme } from "@/components/ThemeProvider";
|
||||
import { Trash2, Undo2, ArrowLeftRight, Hash, Copy, Plus, ChevronDown, ChevronRight, Type, Share2, Users, Tag, Upload } from "lucide-solid";
|
||||
import { Trash2, Undo2, ArrowLeftRight, Hash, Copy, Plus, ChevronDown, ChevronRight, Type, Share2, Users, Tag, Upload, Box, Edit2, Archive } from "lucide-solid";
|
||||
import { TagPicker } from "@/components/TagPicker";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -22,6 +22,7 @@ export const SettingsView: Component = () => {
|
||||
const [isTrashOpen, setIsTrashOpen] = createSignal(false);
|
||||
const [isSharingOpen, setIsSharingOpen] = createSignal(false);
|
||||
const [isImportOpen, setIsImportOpen] = createSignal(false);
|
||||
const [isBucketsOpen, setIsBucketsOpen] = createSignal(false);
|
||||
const [expandedTemplateId, setExpandedTemplateId] = createSignal<string | null>(null);
|
||||
const [shareUserId, setShareUserId] = createSignal("");
|
||||
const [shareType, setShareType] = createSignal<'all' | 'tag'>('all');
|
||||
@@ -292,6 +293,165 @@ export const SettingsView: Component = () => {
|
||||
</Show>
|
||||
</section>
|
||||
|
||||
|
||||
{/* Buckets Manager */}
|
||||
<section class="p-4 sm:p-5 rounded-2xl border border-border bg-card shadow-sm space-y-4 overflow-hidden">
|
||||
<div
|
||||
class="flex items-center justify-between cursor-pointer group"
|
||||
onClick={() => setIsBucketsOpen(!isBucketsOpen())}
|
||||
>
|
||||
<div class="space-y-0.5">
|
||||
<h3 class="text-base font-semibold flex items-center gap-2">
|
||||
<Box size={16} />
|
||||
Buckets (Global Workspaces)
|
||||
</h3>
|
||||
<p class="text-sm text-muted-foreground">Manage public task buckets and your subscriptions.</p>
|
||||
</div>
|
||||
<div class="w-8 h-8 rounded-full flex items-center justify-center bg-muted/50 group-hover:bg-muted transition-colors">
|
||||
{isBucketsOpen() ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Show when={isBucketsOpen()}>
|
||||
<div class="space-y-4 pt-2 animate-in fade-in slide-in-from-top-2 duration-300">
|
||||
{/* Create Bucket */}
|
||||
<div class="p-3 rounded-xl bg-muted/30 border border-border/50 space-y-3">
|
||||
<h4 class="text-[10px] font-black uppercase tracking-widest text-muted-foreground">Create New Bucket</h4>
|
||||
<form
|
||||
class="flex gap-2"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
const form = e.currentTarget;
|
||||
const nameInput = form.elements.namedItem('name') as HTMLInputElement;
|
||||
const colorInput = form.elements.namedItem('color') as HTMLInputElement;
|
||||
const descInput = form.elements.namedItem('desc') as HTMLInputElement;
|
||||
|
||||
if (nameInput.value) {
|
||||
createBucket(nameInput.value, colorInput.value || "#6366f1", descInput.value);
|
||||
nameInput.value = "";
|
||||
descInput.value = "";
|
||||
form.reset();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input
|
||||
name="color"
|
||||
type="color"
|
||||
class="w-9 h-9 rounded-lg border border-input p-0.5 bg-background cursor-pointer"
|
||||
value="#6366f1"
|
||||
/>
|
||||
<input
|
||||
name="name"
|
||||
class="flex h-9 w-full sm:w-48 rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
placeholder="Bucket Name"
|
||||
required
|
||||
/>
|
||||
<input
|
||||
name="desc"
|
||||
class="flex flex-1 h-9 rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
placeholder="Description (optional)"
|
||||
/>
|
||||
<Button size="sm" type="submit" class="h-9 gap-2">
|
||||
<Plus size={14} />
|
||||
Create
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{/* List Buckets */}
|
||||
<div class="space-y-2">
|
||||
<For each={store.buckets} fallback={
|
||||
<div class="text-center py-8 text-muted-foreground text-sm italic border border-dashed border-border rounded-xl">
|
||||
No buckets found.
|
||||
</div>
|
||||
}>
|
||||
{(bucket) => {
|
||||
const isSubscribed = () => store.subscribedBuckets.includes(bucket.id);
|
||||
const [isEditing, setIsEditing] = createSignal(false);
|
||||
const [editName, setEditName] = createSignal(bucket.name);
|
||||
|
||||
return (
|
||||
<div class="flex items-center justify-between p-3 rounded-xl bg-muted/20 border border-border/40 gap-3 group/bucket hover:bg-muted/30 transition-all">
|
||||
<div class="flex items-center gap-3 min-w-0 flex-1">
|
||||
<div
|
||||
class="w-2 h-8 rounded-full shrink-0"
|
||||
style={{ "background-color": bucket.color }}
|
||||
/>
|
||||
<div class="min-w-0 flex-1">
|
||||
<Show when={isEditing()} fallback={
|
||||
<div class="flex items-center gap-2">
|
||||
<p class="font-bold text-sm truncate">{bucket.name}</p>
|
||||
<button onClick={() => setIsEditing(true)} class="opacity-0 group-hover/bucket:opacity-50 hover:opacity-100 transition-opacity">
|
||||
<Edit2 size={10} />
|
||||
</button>
|
||||
</div>
|
||||
}>
|
||||
<input
|
||||
value={editName()}
|
||||
onInput={(e) => setEditName(e.currentTarget.value)}
|
||||
onBlur={() => {
|
||||
if (editName() !== bucket.name) updateBucket(bucket.id, { name: editName() });
|
||||
setIsEditing(false);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
if (editName() !== bucket.name) updateBucket(bucket.id, { name: editName() });
|
||||
setIsEditing(false);
|
||||
}
|
||||
}}
|
||||
class="h-6 w-full max-w-[150px] rounded border border-input bg-transparent px-1 text-sm"
|
||||
autofocus
|
||||
/>
|
||||
</Show>
|
||||
<p class="text-[10px] text-muted-foreground truncate opacity-80">{bucket.description || "No description"}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<Button
|
||||
variant={isSubscribed() ? "secondary" : "outline"}
|
||||
size="sm"
|
||||
class={cn(
|
||||
"h-8 text-xs gap-2 transition-all",
|
||||
isSubscribed() ? "bg-primary/20 text-primary hover:bg-primary/30" : "opacity-70 group-hover/bucket:opacity-100"
|
||||
)}
|
||||
onClick={() => toggleBucketSubscription(bucket.id)}
|
||||
>
|
||||
{isSubscribed() ? (
|
||||
<>
|
||||
<Archive size={12} class="rotate-180" />
|
||||
Pinned
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Archive size={12} />
|
||||
Pin
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8 text-muted-foreground hover:text-destructive hover:bg-destructive/10 opacity-0 group-hover/bucket:opacity-100 transition-opacity"
|
||||
onClick={() => {
|
||||
if (confirm(`Delete the bucket "${bucket.name}"? This cannot be undone and will affect all users.`)) {
|
||||
deleteBucket(bucket.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</section>
|
||||
|
||||
{/* Matrix Configuration */}
|
||||
<section class="p-4 sm:p-5 rounded-2xl border border-border bg-card shadow-sm space-y-4">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
|
||||
Reference in New Issue
Block a user