From 441a2c74adade420e3fb85138e43803cc6e6e8df Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 24 Feb 2025 00:02:27 -0500 Subject: [PATCH] Add remaining templates elements --- inventory-server/package.json | 2 - inventory-server/src/routes/ai-validation.js | 1 - inventory-server/src/routes/templates.js | 41 +- inventory-server/src/server.js | 12 +- inventory-server/src/utils/db.js | 78 +- .../settings/TemplateManagement.tsx | 1161 +++++++++++++---- inventory/src/components/ui/textarea.tsx | 22 + inventory/src/index.css | 11 + .../steps/ValidationStep/ValidationStep.tsx | 447 ++++++- inventory/src/pages/Settings.tsx | 2 +- 10 files changed, 1446 insertions(+), 331 deletions(-) create mode 100644 inventory/src/components/ui/textarea.tsx diff --git a/inventory-server/package.json b/inventory-server/package.json index 05aa4c6..5f74ad6 100755 --- a/inventory-server/package.json +++ b/inventory-server/package.json @@ -32,8 +32,6 @@ "uuid": "^9.0.1" }, "devDependencies": { - "@types/express": "^4.17.21", - "@types/pg": "^8.11.2", "nodemon": "^3.0.2" } } diff --git a/inventory-server/src/routes/ai-validation.js b/inventory-server/src/routes/ai-validation.js index fd627e4..d128e97 100644 --- a/inventory-server/src/routes/ai-validation.js +++ b/inventory-server/src/routes/ai-validation.js @@ -8,7 +8,6 @@ const dotenv = require('dotenv'); // Ensure environment variables are loaded dotenv.config({ path: path.join(__dirname, '../../.env') }); -// Initialize OpenAI client const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); diff --git a/inventory-server/src/routes/templates.js b/inventory-server/src/routes/templates.js index 6deb3cf..999f960 100644 --- a/inventory-server/src/routes/templates.js +++ b/inventory-server/src/routes/templates.js @@ -1,5 +1,5 @@ const express = require('express'); -const { Pool } = require('pg'); +const { getPool } = require('../utils/db'); const dotenv = require('dotenv'); const path = require('path'); @@ -7,23 +7,14 @@ dotenv.config({ path: path.join(__dirname, "../../.env") }); const router = express.Router(); -// Initialize PostgreSQL connection pool -const pool = new Pool({ - host: process.env.DB_HOST, - user: process.env.DB_USER, - password: process.env.DB_PASSWORD, - database: process.env.DB_NAME, - // Add SSL if needed (based on your environment) - ...(process.env.NODE_ENV === 'production' && { - ssl: { - rejectUnauthorized: false - } - }) -}); - // Get all templates router.get('/', async (req, res) => { try { + const pool = getPool(); + if (!pool) { + throw new Error('Database pool not initialized'); + } + const result = await pool.query(` SELECT * FROM templates ORDER BY company ASC, product_type ASC @@ -42,6 +33,11 @@ router.get('/', async (req, res) => { router.get('/:company/:productType', async (req, res) => { try { const { company, productType } = req.params; + const pool = getPool(); + if (!pool) { + throw new Error('Database pool not initialized'); + } + const result = await pool.query(` SELECT * FROM templates WHERE company = $1 AND product_type = $2 @@ -89,6 +85,11 @@ router.post('/', async (req, res) => { return res.status(400).json({ error: 'Company and Product Type are required' }); } + const pool = getPool(); + if (!pool) { + throw new Error('Database pool not initialized'); + } + const result = await pool.query(` INSERT INTO templates ( company, @@ -176,6 +177,11 @@ router.put('/:id', async (req, res) => { return res.status(400).json({ error: 'Company and Product Type are required' }); } + const pool = getPool(); + if (!pool) { + throw new Error('Database pool not initialized'); + } + const result = await pool.query(` UPDATE templates SET @@ -244,6 +250,11 @@ router.put('/:id', async (req, res) => { router.delete('/:id', async (req, res) => { try { const { id } = req.params; + const pool = getPool(); + if (!pool) { + throw new Error('Database pool not initialized'); + } + const result = await pool.query('DELETE FROM templates WHERE id = $1 RETURNING *', [id]); if (result.rows.length === 0) { diff --git a/inventory-server/src/server.js b/inventory-server/src/server.js index bf0c9e9..879dfa3 100755 --- a/inventory-server/src/server.js +++ b/inventory-server/src/server.js @@ -20,7 +20,7 @@ const aiValidationRouter = require('./routes/ai-validation'); const templatesRouter = require('./routes/templates'); // Get the absolute path to the .env file -const envPath = path.join(__dirname, '..', '.env'); +const envPath = '/var/www/html/inventory/.env'; console.log('Looking for .env file at:', envPath); console.log('.env file exists:', fs.existsSync(envPath)); @@ -35,8 +35,7 @@ try { DB_NAME: process.env.DB_NAME || 'not set', DB_PASSWORD: process.env.DB_PASSWORD ? '[password set]' : 'not set', DB_PORT: process.env.DB_PORT || 'not set', - DB_SSL: process.env.DB_SSL || 'not set', - OPENAI_API_KEY: process.env.OPENAI_API_KEY ? '[key set]' : 'not set' + DB_SSL: process.env.DB_SSL || 'not set' }); } catch (error) { console.error('Error loading .env file:', error); @@ -72,13 +71,13 @@ app.use(express.urlencoded({ extended: true, limit: '10mb' })); // Initialize database pool and start server async function startServer() { try { - // Initialize database pool with PostgreSQL configuration + // Initialize database pool const pool = await initPool({ host: process.env.DB_HOST, - port: parseInt(process.env.DB_PORT || '5432', 10), user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, + port: process.env.DB_PORT || 5432, max: process.env.NODE_ENV === 'production' ? 20 : 10, idleTimeoutMillis: 30000, connectionTimeoutMillis: 2000, @@ -110,8 +109,7 @@ async function startServer() { res.json({ status: 'ok', timestamp: new Date().toISOString(), - environment: process.env.NODE_ENV, - database: 'connected' + environment: process.env.NODE_ENV }); }); diff --git a/inventory-server/src/utils/db.js b/inventory-server/src/utils/db.js index d2dd88f..c8bec35 100644 --- a/inventory-server/src/utils/db.js +++ b/inventory-server/src/utils/db.js @@ -1,41 +1,47 @@ -const { Pool } = require('pg'); +const { Pool, Client } = require('pg'); const { Client: SSHClient } = require('ssh2'); let pool; -async function initPool(config) { +function initPool(config) { // Log config without sensitive data const safeConfig = { - host: config.host, - user: config.user, - database: config.database, - port: config.port, - max: config.max, - idleTimeoutMillis: config.idleTimeoutMillis, - connectionTimeoutMillis: config.connectionTimeoutMillis, - ssl: config.ssl, - password: config.password ? '[password set]' : '[no password]' + host: config.host || process.env.DB_HOST, + user: config.user || process.env.DB_USER, + database: config.database || process.env.DB_NAME, + port: config.port || process.env.DB_PORT || 5432, + max: config.max || 10, + idleTimeoutMillis: config.idleTimeoutMillis || 30000, + connectionTimeoutMillis: config.connectionTimeoutMillis || 2000, + ssl: config.ssl || false, + password: (config.password || process.env.DB_PASSWORD) ? '[password set]' : '[no password]' }; console.log('[Database] Initializing pool with config:', safeConfig); - try { - // Create the pool - pool = new Pool(config); + // Create the pool with the configuration + pool = new Pool({ + host: config.host || process.env.DB_HOST, + user: config.user || process.env.DB_USER, + password: config.password || process.env.DB_PASSWORD, + database: config.database || process.env.DB_NAME, + port: config.port || process.env.DB_PORT || 5432, + max: config.max || 10, + idleTimeoutMillis: config.idleTimeoutMillis || 30000, + connectionTimeoutMillis: config.connectionTimeoutMillis || 2000, + ssl: config.ssl || false + }); - // Test the connection - const client = await pool.connect(); - try { - await client.query('SELECT NOW()'); - console.log('[Database] Pool connection test successful'); - } finally { + // Test the pool connection + return pool.connect() + .then(client => { + console.log('[Database] Pool connection successful'); client.release(); - } - - return pool; - } catch (err) { - console.error('[Database] Connection failed:', err); - throw err; - } + return pool; + }) + .catch(err => { + console.error('[Database] Connection failed:', err); + throw err; + }); } async function getConnection() { @@ -45,24 +51,8 @@ async function getConnection() { return pool.connect(); } -// Helper function to execute a query with error handling -async function query(text, params = []) { - if (!pool) { - throw new Error('Database pool not initialized'); - } - - try { - const result = await pool.query(text, params); - return result; - } catch (err) { - console.error('[Database] Query error:', err); - throw err; - } -} - module.exports = { initPool, getConnection, - getPool: () => pool, - query + getPool: () => pool }; \ No newline at end of file diff --git a/inventory/src/components/settings/TemplateManagement.tsx b/inventory/src/components/settings/TemplateManagement.tsx index 17144ab..a6a6ddc 100644 --- a/inventory/src/components/settings/TemplateManagement.tsx +++ b/inventory/src/components/settings/TemplateManagement.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, useMemo } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Button } from "@/components/ui/button"; import { @@ -23,6 +23,61 @@ import { Label } from "@/components/ui/label"; import { ScrollArea } from "@/components/ui/scroll-area"; import { toast } from "sonner"; import config from "@/config"; +import { + useReactTable, + getCoreRowModel, + getSortedRowModel, + SortingState, + flexRender, + type ColumnDef, +} from "@tanstack/react-table"; +import { ArrowUpDown, Pencil, Trash2, Loader2, Copy } from "lucide-react"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { Check, ChevronsUpDown } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { Textarea } from "@/components/ui/textarea"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; + +interface FieldOption { + label: string; + value: string; + type?: number; + level?: number; + hexColor?: string; +} + +interface FieldOptions { + companies: FieldOption[]; + artists: FieldOption[]; + sizes: FieldOption[]; + themes: FieldOption[]; + categories: FieldOption[]; + colors: FieldOption[]; + suppliers: FieldOption[]; + taxCategories: FieldOption[]; + shippingRestrictions: FieldOption[]; +} interface Template { id: number; @@ -51,10 +106,16 @@ interface TemplateFormData extends Omit(null); + const [editingTemplate, setEditingTemplate] = useState