diff --git a/package.json b/package.json index b1bec53..9e8beda 100644 --- a/package.json +++ b/package.json @@ -17,10 +17,12 @@ "bcryptjs": "^3.0.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cn": "^0.2.6", "date-fns": "^4.4.0", "jose": "^6.2.10", "lucide-react": "^1.34.0", "next": "16.3.3", + "next-themes": "^0.4.6", "prisma": "^5.22.0", "react": "19.2.8", "react-day-picker": "^10.0.1", @@ -28,6 +30,7 @@ "react-hook-form": "^7.86.0", "recharts": "^3.10.1", "shadcn": "^4.19.0", + "sonner": "^2.0.8", "tailwind-merge": "^3.6.0", "tw-animate-css": "^1.4.0", "uuid": "^14.0.2", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index fc469bb..2148637 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,6 +1,3 @@ -// This is your Prisma schema file, -// learn more about it in the docs: https://pris.ly/d/prisma-schema - generator client { provider = "prisma-client-js" } @@ -25,28 +22,28 @@ model User { } model Order { - id String @id @default(uuid()) - title String - description String? - date DateTime - allow_custom Boolean @default(false) - status String @default("DRAFT") - creator_id String - creator User @relation("CreatedOrders", fields: [creator_id], references: [id]) + id String @id @default(uuid()) + title String + date DateTime + allow_custom Boolean @default(false) + status String @default("DRAFT") + creator_id String + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + description String? available_items AvailableItem[] - submissions Submission[] - created_at DateTime @default(now()) - updated_at DateTime @updatedAt + creator User @relation("CreatedOrders", fields: [creator_id], references: [id]) + submissions Submission[] } model AvailableItem { id String @id @default(uuid()) order_id String name String + created_at DateTime @default(now()) + updated_at DateTime @updatedAt is_sold_out Boolean @default(false) order Order @relation(fields: [order_id], references: [id], onDelete: Cascade) - created_at DateTime @default(now()) - updated_at DateTime @updatedAt } model Submission { @@ -54,13 +51,13 @@ model Submission { order_id String user_id String bill Int? - paid_amount Int? payment_status String @default("BELUM_BAYAR") + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + paid_amount Int? order Order @relation(fields: [order_id], references: [id], onDelete: Cascade) user User @relation(fields: [user_id], references: [id], onDelete: Cascade) items SubmissionItem[] - created_at DateTime @default(now()) - updated_at DateTime @updatedAt } model SubmissionItem { @@ -69,7 +66,13 @@ model SubmissionItem { name String qty Int @default(1) is_custom Boolean @default(false) - submission Submission @relation(fields: [submission_id], references: [id], onDelete: Cascade) created_at DateTime @default(now()) updated_at DateTime @updatedAt + submission Submission @relation(fields: [submission_id], references: [id], onDelete: Cascade) +} + +model Setting { + key String @id + value String + updated_at DateTime @updatedAt } diff --git a/src/app/(app)/my-orders/[id]/page.tsx b/src/app/(app)/my-orders/[id]/page.tsx index f31c1b9..767dfec 100644 --- a/src/app/(app)/my-orders/[id]/page.tsx +++ b/src/app/(app)/my-orders/[id]/page.tsx @@ -283,6 +283,7 @@ export default function OrderDetailPage() { {order.status === 'OPEN' && ( diff --git a/src/app/(app)/settings/integrations/page.tsx b/src/app/(app)/settings/integrations/page.tsx new file mode 100644 index 0000000..2b80152 --- /dev/null +++ b/src/app/(app)/settings/integrations/page.tsx @@ -0,0 +1,132 @@ +'use client' + +import { useEffect, useState } from 'react' +import { getSettings, updateSettings } from '@/app/actions' +import { toast } from 'sonner' +import { Loader2, Save, MessageSquare, MessageCircle, AlertCircle } from 'lucide-react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { Textarea } from '@/components/ui/textarea' + +export default function IntegrationsPage() { + const [loading, setLoading] = useState(true) + const [saving, setSaving] = useState(false) + const [settings, setSettings] = useState({ + MATTERMOST_WEBHOOK_URL: '', + MATTERMOST_TEMPLATE: '', + WHATSAPP_TEMPLATE: '' + }) + + useEffect(() => { + const fetchSettings = async () => { + const data = await getSettings() + if (data) { + setSettings({ + MATTERMOST_WEBHOOK_URL: data.MATTERMOST_WEBHOOK_URL || '', + MATTERMOST_TEMPLATE: data.MATTERMOST_TEMPLATE || 'Halo tim! Lagi ada orderan open nih untuk {title}. Mumpung belum di-checkout, yang mau ikutan nitip bisa langsung cek ke sini ya: {url}', + WHATSAPP_TEMPLATE: data.WHATSAPP_TEMPLATE || 'Halo tim! Lagi ada orderan open nih untuk {title}. Mumpung belum di-checkout, yang mau ikutan nitip bisa langsung cek ke sini ya: {url}' + }) + } + setLoading(false) + } + fetchSettings() + }, []) + + const handleChange = (key: string, value: string) => { + setSettings(prev => ({ ...prev, [key]: value })) + } + + const handleSave = async () => { + setSaving(true) + const res = await updateSettings(settings) + if (res.success) { + toast.success('Berhasil Disimpan', { + description: 'Pengaturan integrasi berhasil disimpan!', + duration: 3000 + }) + } else { + toast.error('Gagal', { + description: 'Gagal menyimpan pengaturan', + duration: 3000 + }) + } + setSaving(false) + } + + if (loading) { + return ( +
+ +
+ ) + } + + return ( +
+
+

Integrasi & Sharing

+

Konfigurasi endpoint API dan template pesan otomatis.

+
+ +
+ {/* Mattermost Section */} +
+
+ +

Mattermost Webhook

+
+

Gunakan URL Incoming Webhook dari Mattermost untuk mengirim broadcast PO baru secara otomatis.

+ +
+ + handleChange('MATTERMOST_WEBHOOK_URL', e.target.value)} + className="h-11 rounded-xl bg-white dark:bg-slate-950" + placeholder="https://mattermost.yourdomain.com/hooks/xxx" + /> +
+ +
+ +