Initial commit - PromptTech

This commit is contained in:
2026-01-27 18:07:00 -06:00
commit 3959a223bf
262 changed files with 128736 additions and 0 deletions

View File

@@ -0,0 +1,332 @@
# Product Rich Text Editor & Multi-Image Upload Feature
## Overview
This feature enhancement adds:
1. **Rich Text Editor** for product descriptions with HTML support
2. **Multiple Image Upload** system with drag-and-drop reordering
3. **Image Carousel** for frontend product display
## Backend Changes
### Database Schema
**New Table: `product_images`**
```sql
- id (UUID, Primary Key)
- product_id (UUID, Foreign Key products.id, CASCADE DELETE)
- image_url (VARCHAR, NOT NULL)
- display_order (INTEGER, DEFAULT 0)
- is_primary (BOOLEAN, DEFAULT FALSE)
- created_at (TIMESTAMP)
```
**Indexes:**
- `idx_product_images_product_id` on `product_id`
- `idx_product_images_display_order` on `(product_id, display_order)`
### Models (`backend/models.py`)
```python
class ProductImage(Base):
__tablename__ = "product_images"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
product_id = Column(UUID(as_uuid=True), ForeignKey("products.id", ondelete="CASCADE"))
image_url = Column(String, nullable=False)
display_order = Column(Integer, default=0)
is_primary = Column(Boolean, default=False)
created_at = Column(DateTime(timezone=True), server_default=func.now())
product = relationship("Product", back_populates="images")
# Updated Product model:
images = relationship("ProductImage", back_populates="product",
cascade="all, delete-orphan",
order_by="ProductImage.display_order")
```
### API Endpoints (`backend/server.py`)
#### Image Upload
- **POST `/api/upload/image`** - Upload single image
- Accepts: multipart/form-data with `file` field
- Returns: `{url, filename}`
- Validates: Only image file types
- Saves to: `backend/uploads/products/`
#### Product Image Management
- **POST `/api/admin/products/{product_id}/images`** - Add multiple images
- Body: `{image_urls: List[str]}`
- Auto-sets first image as primary if none exist
- **DELETE `/api/admin/products/{product_id}/images/{image_id}`** - Delete image
- Removes from database and filesystem
- **PUT `/api/admin/products/{product_id}/images/reorder`** - Reorder images
- Body: `{image_id: display_order}` mapping
#### Updated Product CRUD
- **POST `/api/admin/products`** - Create product with images
- Accepts `images: List[str]` in request body
- Creates ProductImage records for each URL
- **PUT `/api/admin/products/{product_id}`** - Update product with images
- Accepts `images: List[str]` (optional)
- Replaces all existing images if provided
- **GET `/api/products`** - List products (includes images array)
- **GET `/api/products/{id}`** - Get product detail (includes images array)
- **GET `/api/admin/products`** - Admin product list (includes images array)
#### Static File Serving
- **GET `/uploads/**`** - Serve uploaded images
- Mounted: `/uploads``backend/uploads/`
### Pydantic Models
```python
class ProductCreate(BaseModel):
images: List[str] = [] # Added
description: str # Now supports HTML
class ProductUpdate(BaseModel):
images: Optional[List[str]] = None # Added
description: Optional[str] = None # Supports HTML
```
### Serializer (`product_to_dict`)
```python
"images": [
{
"id": str(img.id),
"image_url": img.image_url,
"display_order": img.display_order,
"is_primary": img.is_primary
}
for img in sorted(product.images, key=lambda x: x.display_order)
]
```
## Frontend Changes
### New Components
#### 1. RichTextEditor (`frontend/src/components/RichTextEditor.js`)
- Uses TipTap editor with StarterKit
- Features:
- Bold, Italic formatting
- Heading 2
- Bullet/Numbered lists
- Blockquotes
- Undo/Redo
- Props: `{content, onChange, placeholder}`
- Returns HTML string
#### 2. ImageUploadManager (`frontend/src/components/ImageUploadManager.js`)
- Multi-image upload with preview
- Features:
- File upload button
- Drag-and-drop reordering
- Delete individual images
- Primary image indicator (first = primary)
- Image preview grid
- Props: `{images, onChange, token}`
#### 3. ProductImageCarousel (`frontend/src/components/ProductImageCarousel.js`)
- Image slider with thumbnails
- Features:
- Navigation arrows (prev/next)
- Thumbnail strip navigation
- Image counter
- Keyboard navigation support
- Responsive design
- Props: `{images, alt}`
### Updated Pages
#### AdminDashboard.js
```javascript
// Added imports
import RichTextEditor from '../components/RichTextEditor';
import ImageUploadManager from '../components/ImageUploadManager';
// Updated product form state
productForm: {
images: [], // New field
description: "" // Now holds HTML
}
// Updated dialog
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<RichTextEditor
content={productForm.description}
onChange={(html) => setProductForm({...productForm, description: html})}
/>
<ImageUploadManager
images={productForm.images}
onChange={(images) => setProductForm({...productForm, images})}
token={token}
/>
</DialogContent>
// Edit product - populate images
setProductForm({
images: product.images?.map(img => img.image_url) || []
})
```
#### ProductDetail.js
```javascript
import ProductImageCarousel from '../components/ProductImageCarousel';
// Replace single image with carousel
<ProductImageCarousel
images={product.images?.length > 0 ? product.images : [product.image_url]}
alt={product.name}
/>
// Display HTML description
<div
className="prose prose-sm"
dangerouslySetInnerHTML={{ __html: product.description }}
/>
```
#### ProductCard.js (Products listing)
```javascript
// Get primary image or first image
const getImageUrl = () => {
if (product.images?.length > 0) {
const primaryImage = product.images.find(img => img.is_primary) || product.images[0];
const url = primaryImage.image_url || primaryImage;
return url.startsWith('/uploads')
? `${process.env.REACT_APP_BACKEND_URL}${url}`
: url;
}
return product.image_url;
};
<img src={getImageUrl()} alt={product.name} />
```
### CSS Styles (`frontend/src/index.css`)
```css
/* TipTap Editor Styles */
.ProseMirror { outline: none; }
.ProseMirror h2 { font-size: 1.5em; font-weight: 600; }
.ProseMirror ul, .ProseMirror ol { padding-left: 1.5em; }
.ProseMirror blockquote { border-left: 3px solid; padding-left: 1em; }
/* Product Description Display */
.prose h2 { font-size: 1.5em; font-weight: 600; }
.prose ul, .prose ol { padding-left: 1.5em; }
.prose blockquote { border-left: 3px solid; font-style: italic; }
```
## Usage Flow
### Admin Creating Product with Multiple Images
1. Open "Add Product" dialog in Admin Dashboard
2. Fill in product name, price, category, etc.
3. Use rich text editor to write formatted description
4. Click "Add Images" button to upload multiple images
5. Drag images to reorder (first = primary)
6. Click "Create" - product saved with all images
### Admin Editing Product
1. Click Edit on existing product
2. Dialog loads with current data including images
3. Rich text editor shows formatted description
4. Images displayed in grid with reorder/delete options
5. Add more images or reorder existing ones
6. Click "Update" - changes saved
### Customer Viewing Product
1. Browse products - sees primary image on card
2. Click product for details
3. Image carousel displays all product images
4. Navigate with arrows or click thumbnails
5. Description renders with HTML formatting
## Backwards Compatibility
- `image_url` field retained for legacy support
- Products without images fallback to `image_url`
- Frontend handles both old (single URL) and new (images array)
- Database migrations non-breaking
## File Structure
```
backend/
├── models.py # Added ProductImage model
├── server.py # Added endpoints, updated CRUD
└── uploads/
└── products/ # Image storage directory
frontend/src/
├── components/
│ ├── RichTextEditor.js # NEW
│ ├── ImageUploadManager.js # NEW
│ └── ProductImageCarousel.js # NEW
├── pages/
│ ├── AdminDashboard.js # Updated
│ ├── ProductDetail.js # Updated
│ └── Products.js # Updated (via ProductCard)
└── index.css # Added prose/editor styles
```
## Dependencies
**Already Installed:**
- `@tiptap/react` - Rich text editor
- `@tiptap/starter-kit` - Editor extensions
- `@tiptap/extension-placeholder` - Placeholder text
**Backend:**
- `shutil` - File operations (built-in)
- `uuid` - Unique filenames (built-in)
## Testing Checklist
- [ ] Create product with multiple images
- [ ] Upload images via drag-and-drop
- [ ] Reorder images by dragging
- [ ] Delete individual images
- [ ] Edit product preserves existing images
- [ ] Rich text formatting saves correctly
- [ ] HTML renders properly on product detail page
- [ ] Image carousel navigation works
- [ ] Primary image displays on product cards
- [ ] Fallback to `image_url` for legacy products
- [ ] Image files serve correctly via `/uploads`
- [ ] Deleting product cascades to delete images
## Security Notes
- File upload validates image MIME types only
- Unique UUIDs prevent filename conflicts
- CASCADE DELETE ensures orphaned images cleaned up
- Static file serving restricted to `/uploads` directory
- Admin authentication required for all mutations

View File

@@ -0,0 +1,277 @@
# Inventory Management Features - Complete Reference
## ✅ Verified Working Features
### 1. **Inventory List Endpoint**
- **URL**: `GET /api/admin/inventory`
- **Status**: ✅ Working
- **Features**:
- Lists all products sorted by stock level (lowest first)
- Returns full product details: name, price, stock, threshold, category
- Includes `is_low_stock` boolean flag for each product
- Eager loads product images to avoid N+1 queries
- Admin authentication required
**Example Response**:
```json
[
{
"id": "67cc9684-ea8f-4b60-bd51-b764995a6a43",
"name": "Test",
"description": "<p>Amazing product to have</p>",
"price": 50.0,
"category": "laptops",
"stock": 7,
"low_stock_threshold": 5,
"is_low_stock": true,
"images": [
{
"id": "72d3f194-3ae1-4e19-a41c-d71a62efacff",
"url": "/uploads/products/e0f80d0a-9ae6-4065-ac47-08802a876ac9.jpg",
"is_primary": true
}
]
}
]
```
### 2. **Inventory Adjustment Endpoint**
- **URL**: `POST /api/admin/inventory/{product_id}/adjust`
- **Status**: ✅ Working & Database Persistence Verified
- **Features**:
- Adjusts product stock levels
- Creates InventoryLog entry for audit trail
- Validates stock doesn't go negative
- Returns updated stock level
- Saves immediately to database
**Request Body**:
```json
{
"quantity_change": 5,
"notes": "Received new shipment"
}
```
**Response**:
```json
{
"message": "Inventory adjusted successfully",
"new_stock": 12,
"previous_stock": 7
}
```
**Verified**: Changes persist to database immediately - tested and confirmed stock updates saved
### 3. **Inventory Logs Endpoint**
- **URL**: `GET /api/admin/inventory/{product_id}/logs`
- **Status**: ✅ Implemented
- **Features**:
- Returns complete history of inventory adjustments
- Shows quantity changes, dates, and notes
- Useful for audit trails and tracking
- Admin authentication required
### 4. **Low Stock Detection**
- **Status**: ✅ Working
- **Logic**: `stock <= low_stock_threshold`
- **Features**:
- Automatic flag calculation on each product
- Used for dashboard alerts
- Configurable threshold per product
- Default threshold: 5 units
**Current Status**: 1 product currently flagged as low stock
### 5. **Frontend Admin Dashboard - Inventory Tab**
- **File**: `frontend/src/pages/AdminDashboard.js` (lines 1509-1630)
- **Status**: ✅ Fully Implemented
- **Features**:
- Inventory table with sortable columns
- Shows: Product name, category, stock, threshold, status
- Status badge (red for low stock, green for in stock)
- Adjust inventory dialog with:
- Quantity change input (+ or -)
- Notes textarea for audit trail
- Real-time stock calculation preview
- Export buttons:
- Export to CSV
- Export to PDF
- Search/filter functionality
- Responsive design
### 6. **Database Schema**
- **Models**: Product, InventoryLog
- **Product Fields**:
- `stock` (Integer): Current stock level
- `low_stock_threshold` (Integer, default=5): Alert threshold
- `inventory_logs` (Relationship): Linked adjustment history
- `images` (Relationship): Product images with cascade delete
- **InventoryLog Fields**:
- `product_id`: Foreign key to Product
- `quantity_change`: Amount added/removed
- `previous_stock`: Stock before change
- `new_stock`: Stock after change
- `notes`: Reason for adjustment
- `created_at`: Timestamp
## 📊 Current Inventory Status
### Products in Database: 9
- **In Stock**: 8 products
- **Low Stock**: 1 product
- **Categories**: laptops, desktops, accessories, phones
### Services in Database: 8
- **Repair Services**: 3
- **Data Services**: 1
- **Software Services**: 1
- **Setup Services**: 2
## 🔄 Recent Fixes Applied
### Inventory Endpoint Fix (2026-01-12)
**Problem**: Internal Server Error 500 when fetching inventory
**Solution**:
1. Added proper error handling with try/except
2. Added eager loading: `selectinload(Product.images)`
3. Added detailed error logging
4. Structured response building instead of dict unpacking
**Code Change** (backend/server.py lines 1519-1538):
```python
@api_router.get("/admin/inventory")
async def admin_get_inventory(
current_user: dict = Depends(verify_admin),
db: AsyncSession = Depends(get_db)
):
try:
result = await db.execute(
select(Product)
.options(selectinload(Product.images))
.where(Product.is_active == True)
.order_by(Product.stock)
)
products = result.scalars().all()
inventory_data = []
for p in products:
product_dict = product_to_dict(p)
product_dict["is_low_stock"] = p.stock <= p.low_stock_threshold
inventory_data.append(product_dict)
return inventory_data
except Exception as e:
logger.error(f"Error fetching inventory: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to fetch inventory: {str(e)}")
```
**Result**: Endpoint now working perfectly, returns all inventory data with proper low stock flagging
## 🧪 Testing Results
### Automated Test Results
```
✅ Services Loading: 8 services
✅ Inventory Management: 9 products
✅ Low Stock Detection: Working (1 item)
✅ Inventory Adjustments: Working
✅ Database Persistence: Working
✅ Frontend Connection: Working
```
### Manual Verification
1. ✅ Inventory list loads in admin dashboard
2. ✅ Adjust inventory creates database record
3. ✅ Stock changes persist after page reload
4. ✅ Low stock badge displays correctly
5. ✅ Inventory logs track all changes
## 🎯 Key Endpoints Summary
| Endpoint | Method | Auth | Status | Purpose |
|----------|--------|------|--------|---------|
| `/api/admin/inventory` | GET | Admin | ✅ | List all products with stock info |
| `/api/admin/inventory/{id}/adjust` | POST | Admin | ✅ | Adjust stock level |
| `/api/admin/inventory/{id}/logs` | GET | Admin | ✅ | View adjustment history |
| `/api/services` | GET | Public | ✅ | List services (with category filter) |
| `/api/products` | GET | Public | ✅ | List products |
## 🚀 How to Use Inventory Features
### Viewing Inventory
1. Log in as admin
2. Navigate to Admin Dashboard
3. Click "Inventory" tab
4. View all products with stock levels
### Adjusting Stock
1. In inventory tab, click "Adjust" button on any product
2. Enter quantity change (positive to add, negative to remove)
3. Add notes for audit trail
4. Click "Adjust Inventory"
5. Changes saved immediately to database
### Monitoring Low Stock
- Products with stock ≤ threshold show red "Low Stock" badge
- Sort by stock to see lowest items first
- Export reports for purchasing decisions
## 🔧 Configuration
### Setting Low Stock Threshold
Edit product in database or via admin panel:
```python
product.low_stock_threshold = 10 # Alert when stock ≤ 10
```
### Viewing Inventory Logs
```bash
curl http://localhost:8181/api/admin/inventory/{product_id}/logs \
-H "Authorization: Bearer {admin_token}"
```
## 📝 Additional Notes
- All inventory changes create audit log entries
- Stock cannot be adjusted below 0
- Frontend and backend fully synchronized
- Real-time updates without page refresh
- Export functionality ready for reporting
- Database persistence verified and working
## ✨ System Health
**Backend**: Running on port 8181 ✅
**Frontend**: Running on port 5300 ✅
**Database**: PostgreSQL connected ✅
**API Version**: 2.0.0 ✅
**Last Verified**: 2026-01-12 03:45 UTC ✅
---
**All inventory management features are fully functional and connected to the database!** 🎉

View File

@@ -0,0 +1,240 @@
# User Management Feature
## Overview
Complete user management system implemented in the Admin Dashboard with role-based access control (RBAC) supporting 5 different user roles.
## Features Implemented
### 1. User Roles
The system now supports 5 distinct user roles:
- **Admin** - Full system access
- **User** - Standard customer access
- **Employee** - Staff member access
- **Accountant** - Financial management access
- **Sales Manager** - Sales oversight access
### 2. User Management UI (Admin Dashboard)
- New "Users" tab in the Admin Dashboard
- Located between "Categories" and "Reports" tabs
- Fully integrated with existing dashboard layout
### 3. User Creation & Editing
- **Create New User**
- Name, Email, Password fields
- Role selection dropdown (all 5 roles)
- Active/Inactive toggle (default: Active)
- Form validation
- **Edit Existing User**
- Update name, email, role
- Change active status
- Password field hidden (edit doesn't change password)
### 4. User Table Features
- **Display Columns:**
- Name
- Email
- Role (with badge styling)
- Status (Active/Inactive with color coding)
- Created date
- Actions column
- **Actions:**
- Toggle Active/Inactive status
- Edit user details
- Delete user (with confirmation)
### 5. Filters & Search
- **Search:** Filter by name or email (real-time)
- **Role Filter:** Filter by specific role or view all
- **Status Filter:** Filter by Active, Inactive, or all
- **Apply Button:** Manually trigger filter refresh
### 6. Pagination
- Items per page: 10, 20, 50, or 100 (default: 20)
- Previous/Next navigation
- Page count display (e.g., "Page 1 of 3")
- Total users count
### 7. Safety Features
- **Cannot deactivate your own account** - Admin protection
- **Cannot delete your own account** - Admin protection
- **Email uniqueness validation** - Prevents duplicate emails
- **Confirmation dialogs** - Before user deletion
## Backend Changes
### Database Schema
```sql
-- Added to users table
ALTER TABLE users ADD COLUMN is_active BOOLEAN DEFAULT TRUE NOT NULL;
```
### User Model (models.py)
```python
class UserRole(enum.Enum):
USER = "user"
ADMIN = "admin"
EMPLOYEE = "employee"
ACCOUNTANT = "accountant"
SALES_MANAGER = "sales_manager"
class User(Base):
# ... existing fields ...
role = Column(SQLEnum(UserRole), default=UserRole.USER)
is_active = Column(Boolean, default=True, nullable=False) # NEW
# ... existing fields ...
```
### API Endpoints (server.py)
All endpoints require admin authentication.
1. **GET /api/admin/users**
- List all users with filters
- Query params: skip, limit, search, role, status
- Returns: users array, total count, pagination info
2. **POST /api/admin/users**
- Create new user
- Body: email, name, password, role, is_active
- Returns: success message, created user
3. **PUT /api/admin/users/{user_id}**
- Update user details
- Body: email, name, role, is_active (all optional)
- Returns: success message, updated user
4. **PUT /api/admin/users/{user_id}/toggle-active**
- Toggle active/inactive status
- No body required
- Returns: success message, updated user
5. **DELETE /api/admin/users/{user_id}**
- Delete user permanently
- Returns: success message
### Pydantic Schemas
```python
class UserCreateAdmin(BaseModel):
email: EmailStr
name: str
password: str
role: str
is_active: bool = True
class UserUpdateAdmin(BaseModel):
email: Optional[EmailStr] = None
name: Optional[str] = None
role: Optional[str] = None
is_active: Optional[bool] = None
```
## Frontend Changes
### AdminDashboard.js
- Added users state variables (users, userForm, filters, pagination)
- Added fetchUsers() function with filter support
- Added handleUserSubmit() for create/update
- Added handleToggleUserActive() for status toggle
- Added handleDeleteUser() for user deletion
- Added Users tab UI with full table, dialog, and filters
- Added useEffect for filter changes
- Updated TabsList grid from 7 to 9 columns (dashboard + 8 tabs)
### State Management
```javascript
const [users, setUsers] = useState([]);
const [usersTotal, setUsersTotal] = useState(0);
const [userDialog, setUserDialog] = useState(false);
const [editingUser, setEditingUser] = useState(null);
const [userForm, setUserForm] = useState({
name: "",
email: "",
password: "",
role: "user",
is_active: true,
});
const [userSearch, setUserSearch] = useState("");
const [userRoleFilter, setUserRoleFilter] = useState("");
const [userStatusFilter, setUserStatusFilter] = useState("");
const [usersPerPage, setUsersPerPage] = useState(20);
const [currentUsersPage, setCurrentUsersPage] = useState(1);
```
## How to Use
### Creating a New User
1. Go to Admin Dashboard
2. Click the "Users" tab
3. Click "Add User" button
4. Fill in the form:
- Name (required)
- Email (required, must be unique)
- Password (required for new users)
- Role (select from dropdown)
- Active toggle (checked = active)
5. Click "Create User"
### Editing a User
1. Find the user in the table
2. Click the edit icon (pencil)
3. Update the fields you want to change
4. Click "Update User"
### Toggling User Status
1. Find the user in the table
2. Click "Activate" or "Deactivate" button
3. Status updates immediately
### Deleting a User
1. Find the user in the table
2. Click the delete icon (trash)
3. Confirm deletion in the dialog
### Searching & Filtering
1. Use the search box to find users by name or email
2. Select a role filter to view specific roles
3. Select a status filter to view active or inactive users
4. Click "Apply" to refresh with filters
## File Locations
### Backend
- `/backend/models.py` - User model and UserRole enum
- `/backend/server.py` - User management API endpoints
### Frontend
- `/frontend/src/pages/AdminDashboard.js` - User management UI
## Build Information
- Frontend build: 302.05 kB (+1.28 kB from previous)
- Backend restart: Successful (PID 4079466)
- Frontend restart: Successful (PID 4081381)
## Testing Checklist
- [x] Database migration successful
- [x] Backend API endpoints working
- [x] Frontend UI displays correctly
- [x] User creation works
- [x] User editing works
- [x] User status toggle works
- [x] User deletion works
- [x] Filters work (search, role, status)
- [x] Pagination works
- [x] Safety features work (cannot delete/deactivate self)
- [x] Build successful
- [x] Deployment successful
## Future Enhancements (Not Implemented)
- Password never change option (mentioned in requirements but not implemented)
- Password reset functionality
- User activity logs
- Bulk user operations
- Export users to CSV
- User permissions management beyond roles
## Notes
- The "password never change" option was mentioned in the requirements but not implemented in this iteration. This can be added as an additional Boolean field in the User model if needed.
- All user passwords are hashed using bcrypt before storage.
- Email validation is enforced both in frontend (EmailStr type) and backend.
- The system prevents admins from accidentally locking themselves out by blocking self-deactivation and self-deletion.