lets add settings new to css
This commit is contained in:
152
py_app/CSS_MODULAR_GUIDE.md
Normal file
152
py_app/CSS_MODULAR_GUIDE.md
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# CSS Modular Structure Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This guide explains how to migrate from a monolithic CSS file to a modular CSS structure for better maintainability and organization.
|
||||||
|
|
||||||
|
## New CSS Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
app/static/css/
|
||||||
|
├── base.css # Global styles, header, buttons, theme
|
||||||
|
├── login.css # Login page specific styles
|
||||||
|
├── dashboard.css # Dashboard and module cards
|
||||||
|
├── warehouse.css # Warehouse module styles
|
||||||
|
├── etichete.css # Labels/etiquette module styles (to be created)
|
||||||
|
├── quality.css # Quality module styles (to be created)
|
||||||
|
└── scan.css # Scan module styles (to be created)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Strategy
|
||||||
|
|
||||||
|
### Phase 1: Setup Modular Structure ✅
|
||||||
|
- [x] Created `css/` directory
|
||||||
|
- [x] Created `base.css` with global styles
|
||||||
|
- [x] Created `login.css` for login page
|
||||||
|
- [x] Created `warehouse.css` for warehouse module
|
||||||
|
- [x] Updated `base.html` to include modular CSS
|
||||||
|
- [x] Updated `login.html` to use new structure
|
||||||
|
|
||||||
|
### Phase 2: Migration Plan (Next Steps)
|
||||||
|
|
||||||
|
1. **Extract module-specific styles from style.css:**
|
||||||
|
- Etiquette/Labels module → `etichete.css`
|
||||||
|
- Quality module → `quality.css`
|
||||||
|
- Scan module → `scan.css`
|
||||||
|
|
||||||
|
2. **Update templates to use modular CSS:**
|
||||||
|
```html
|
||||||
|
{% block head %}
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/module-name.css') }}">
|
||||||
|
{% endblock %}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Clean up original style.css:**
|
||||||
|
- Remove extracted styles
|
||||||
|
- Keep only legacy/common styles temporarily
|
||||||
|
- Eventually eliminate when all modules migrated
|
||||||
|
|
||||||
|
## Template Usage Pattern
|
||||||
|
|
||||||
|
### Standard Template Structure:
|
||||||
|
```html
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}Page Title{% endblock %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<!-- Include module-specific CSS -->
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/module-name.css') }}">
|
||||||
|
<!-- Page-specific overrides -->
|
||||||
|
<style>
|
||||||
|
/* Only use this for page-specific customizations */
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<!-- Page content -->
|
||||||
|
{% endblock %}
|
||||||
|
```
|
||||||
|
|
||||||
|
## CSS Loading Order
|
||||||
|
|
||||||
|
1. `base.css` - Global styles, header, buttons, theme
|
||||||
|
2. `style.css` - Legacy styles (temporary, for backward compatibility)
|
||||||
|
3. Module-specific CSS (e.g., `warehouse.css`)
|
||||||
|
4. Inline `<style>` blocks for page-specific overrides
|
||||||
|
|
||||||
|
## Benefits of This Structure
|
||||||
|
|
||||||
|
### 1. **Maintainability**
|
||||||
|
- Easy to find and edit module-specific styles
|
||||||
|
- Reduced conflicts between different modules
|
||||||
|
- Clear separation of concerns
|
||||||
|
|
||||||
|
### 2. **Performance**
|
||||||
|
- Only load CSS needed for specific pages
|
||||||
|
- Smaller file sizes per page
|
||||||
|
- Better caching (module CSS rarely changes)
|
||||||
|
|
||||||
|
### 3. **Team Development**
|
||||||
|
- Different developers can work on different modules
|
||||||
|
- Less merge conflicts in CSS files
|
||||||
|
- Clear ownership of styles
|
||||||
|
|
||||||
|
### 4. **Scalability**
|
||||||
|
- Easy to add new modules
|
||||||
|
- Simple to deprecate old styles
|
||||||
|
- Clear migration path
|
||||||
|
|
||||||
|
## Migration Checklist
|
||||||
|
|
||||||
|
### For Each Template:
|
||||||
|
- [ ] Identify module/page type
|
||||||
|
- [ ] Extract relevant styles to module CSS file
|
||||||
|
- [ ] Update template to include module CSS
|
||||||
|
- [ ] Test styling works correctly
|
||||||
|
- [ ] Remove old styles from style.css
|
||||||
|
|
||||||
|
### Current Status:
|
||||||
|
- [x] Login page - Fully migrated
|
||||||
|
- [x] Warehouse module - Partially migrated (create_locations.html updated)
|
||||||
|
- [ ] Dashboard - CSS created, templates need updating
|
||||||
|
- [ ] Etiquette module - Needs CSS extraction
|
||||||
|
- [ ] Quality module - Needs CSS extraction
|
||||||
|
- [ ] Scan module - Needs CSS extraction
|
||||||
|
|
||||||
|
## Example: Migrating a Template
|
||||||
|
|
||||||
|
### Before:
|
||||||
|
```html
|
||||||
|
{% block head %}
|
||||||
|
<style>
|
||||||
|
.my-module-specific-class {
|
||||||
|
/* styles here */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
```
|
||||||
|
|
||||||
|
### After:
|
||||||
|
1. Move styles to `css/module-name.css`
|
||||||
|
2. Update template:
|
||||||
|
```html
|
||||||
|
{% block head %}
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/module-name.css') }}">
|
||||||
|
{% endblock %}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Use semantic naming:** `warehouse.css`, `login.css`, not `page1.css`
|
||||||
|
2. **Keep base.css minimal:** Only truly global styles
|
||||||
|
3. **Avoid deep nesting:** Keep CSS selectors simple
|
||||||
|
4. **Use consistent naming:** Follow existing patterns
|
||||||
|
5. **Document changes:** Update this guide when adding new modules
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Extract etiquette module styles to `etichete.css`
|
||||||
|
2. Update all etiquette templates to use new CSS
|
||||||
|
3. Extract quality module styles to `quality.css`
|
||||||
|
4. Extract scan module styles to `scan.css`
|
||||||
|
5. Gradually remove migrated styles from `style.css`
|
||||||
|
6. Eventually remove `style.css` dependency from `base.html`
|
||||||
133
py_app/MOBILE_LOGIN_GUIDE.md
Normal file
133
py_app/MOBILE_LOGIN_GUIDE.md
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
# Mobile-Responsive Login Page
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
The login page has been enhanced with comprehensive mobile-responsive CSS to provide an optimal user experience across all device types and screen sizes.
|
||||||
|
|
||||||
|
## Mobile-Responsive Features Added
|
||||||
|
|
||||||
|
### 1. **Responsive Breakpoints**
|
||||||
|
- **Tablet (≤768px)**: Column layout, optimized logo and form sizing
|
||||||
|
- **Mobile (≤480px)**: Enhanced touch targets, better spacing
|
||||||
|
- **Small Mobile (≤320px)**: Minimal padding, compact design
|
||||||
|
- **Landscape (height ≤500px)**: Horizontal layout for landscape phones
|
||||||
|
|
||||||
|
### 2. **Layout Adaptations**
|
||||||
|
|
||||||
|
#### Desktop (>768px)
|
||||||
|
- Side-by-side logo and form layout
|
||||||
|
- Large logo (90vh height)
|
||||||
|
- Fixed form width (600px)
|
||||||
|
|
||||||
|
#### Tablet (≤768px)
|
||||||
|
- Vertical stacked layout
|
||||||
|
- Logo height reduced to 30vh
|
||||||
|
- Form width becomes responsive (100%, max 400px)
|
||||||
|
|
||||||
|
#### Mobile (≤480px)
|
||||||
|
- Optimized touch targets (44px minimum)
|
||||||
|
- Increased padding and margins
|
||||||
|
- Better visual hierarchy
|
||||||
|
- Enhanced shadows and border radius
|
||||||
|
|
||||||
|
#### Small Mobile (≤320px)
|
||||||
|
- Minimal padding to maximize space
|
||||||
|
- Compact logo (20vh height)
|
||||||
|
- Reduced font sizes where appropriate
|
||||||
|
|
||||||
|
### 3. **Touch Optimizations**
|
||||||
|
|
||||||
|
#### iOS/Safari Specific
|
||||||
|
- `font-size: 16px` on inputs prevents automatic zoom
|
||||||
|
- Proper touch target sizing (44px minimum)
|
||||||
|
|
||||||
|
#### Touch Device Enhancements
|
||||||
|
- Active states for button presses
|
||||||
|
- Optimized image rendering for high DPI screens
|
||||||
|
- Hover effects disabled on touch devices
|
||||||
|
|
||||||
|
### 4. **Accessibility Improvements**
|
||||||
|
- Proper contrast ratios maintained
|
||||||
|
- Touch targets meet accessibility guidelines
|
||||||
|
- Readable font sizes across all devices
|
||||||
|
- Smooth transitions and animations
|
||||||
|
|
||||||
|
### 5. **Performance Considerations**
|
||||||
|
- CSS-only responsive design (no JavaScript required)
|
||||||
|
- Efficient media queries
|
||||||
|
- Optimized image rendering for retina displays
|
||||||
|
|
||||||
|
## Key CSS Features
|
||||||
|
|
||||||
|
### Flexible Layout
|
||||||
|
```css
|
||||||
|
.login-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column; /* Mobile */
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Responsive Images
|
||||||
|
```css
|
||||||
|
.login-logo {
|
||||||
|
max-height: 25vh; /* Mobile */
|
||||||
|
max-width: 85vw;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Touch-Friendly Inputs
|
||||||
|
```css
|
||||||
|
.form-container input {
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 16px; /* Prevents iOS zoom */
|
||||||
|
min-height: 44px; /* Touch target size */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Landscape Optimization
|
||||||
|
```css
|
||||||
|
@media screen and (max-height: 500px) and (orientation: landscape) {
|
||||||
|
.login-page {
|
||||||
|
flex-direction: row; /* Back to horizontal */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Recommendations
|
||||||
|
|
||||||
|
### Device Testing
|
||||||
|
- [ ] iPhone (various sizes)
|
||||||
|
- [ ] Android phones (various sizes)
|
||||||
|
- [ ] iPad/Android tablets
|
||||||
|
- [ ] Desktop browsers with responsive mode
|
||||||
|
|
||||||
|
### Orientation Testing
|
||||||
|
- [ ] Portrait mode on all devices
|
||||||
|
- [ ] Landscape mode on phones
|
||||||
|
- [ ] Landscape mode on tablets
|
||||||
|
|
||||||
|
### Browser Testing
|
||||||
|
- [ ] Safari (iOS)
|
||||||
|
- [ ] Chrome (Android/iOS)
|
||||||
|
- [ ] Firefox Mobile
|
||||||
|
- [ ] Samsung Internet
|
||||||
|
- [ ] Desktop browsers (Chrome, Firefox, Safari, Edge)
|
||||||
|
|
||||||
|
## Browser Support
|
||||||
|
- Modern browsers (ES6+ support)
|
||||||
|
- iOS Safari 12+
|
||||||
|
- Android Chrome 70+
|
||||||
|
- Desktop browsers (last 2 versions)
|
||||||
|
|
||||||
|
## Performance Impact
|
||||||
|
- **CSS Size**: Increased by ~2KB (compressed)
|
||||||
|
- **Load Time**: No impact (CSS only)
|
||||||
|
- **Rendering**: Optimized for mobile GPUs
|
||||||
|
- **Memory**: Minimal additional usage
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
1. **Dark mode mobile optimizations**
|
||||||
|
2. **Progressive Web App (PWA) features**
|
||||||
|
3. **Biometric authentication UI**
|
||||||
|
4. **Loading states and animations**
|
||||||
|
5. **Error message responsive design**
|
||||||
BIN
py_app/app/__pycache__/__init__.cpython-312.pyc
Executable file → Normal file
BIN
py_app/app/__pycache__/__init__.cpython-312.pyc
Executable file → Normal file
Binary file not shown.
BIN
py_app/app/__pycache__/models.cpython-312.pyc
Executable file → Normal file
BIN
py_app/app/__pycache__/models.cpython-312.pyc
Executable file → Normal file
Binary file not shown.
BIN
py_app/app/__pycache__/permissions.cpython-312.pyc
Executable file → Normal file
BIN
py_app/app/__pycache__/permissions.cpython-312.pyc
Executable file → Normal file
Binary file not shown.
BIN
py_app/app/__pycache__/routes.cpython-312.pyc
Executable file → Normal file
BIN
py_app/app/__pycache__/routes.cpython-312.pyc
Executable file → Normal file
Binary file not shown.
BIN
py_app/app/__pycache__/settings.cpython-312.pyc
Executable file → Normal file
BIN
py_app/app/__pycache__/settings.cpython-312.pyc
Executable file → Normal file
Binary file not shown.
BIN
py_app/app/__pycache__/warehouse.cpython-312.pyc
Executable file → Normal file
BIN
py_app/app/__pycache__/warehouse.cpython-312.pyc
Executable file → Normal file
Binary file not shown.
@@ -2382,6 +2382,41 @@ def generate_location_label_pdf():
|
|||||||
from app.warehouse import generate_location_label_pdf
|
from app.warehouse import generate_location_label_pdf
|
||||||
return generate_location_label_pdf()
|
return generate_location_label_pdf()
|
||||||
|
|
||||||
|
@warehouse_bp.route('/update_location', methods=['POST'])
|
||||||
|
def update_location():
|
||||||
|
from app.warehouse import update_location
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
location_id = data.get('location_id')
|
||||||
|
location_code = data.get('location_code')
|
||||||
|
size = data.get('size')
|
||||||
|
description = data.get('description')
|
||||||
|
|
||||||
|
if not location_id or not location_code:
|
||||||
|
return jsonify({'success': False, 'error': 'Location ID and code are required'})
|
||||||
|
|
||||||
|
result = update_location(location_id, location_code, size, description)
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'success': False, 'error': str(e)})
|
||||||
|
|
||||||
|
@warehouse_bp.route('/delete_location', methods=['POST'])
|
||||||
|
def delete_location():
|
||||||
|
from app.warehouse import delete_location_by_id
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
location_id = data.get('location_id')
|
||||||
|
|
||||||
|
if not location_id:
|
||||||
|
return jsonify({'success': False, 'error': 'Location ID is required'})
|
||||||
|
|
||||||
|
result = delete_location_by_id(location_id)
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'success': False, 'error': str(e)})
|
||||||
|
|
||||||
|
|
||||||
# NOTE for frontend/extension developers:
|
# NOTE for frontend/extension developers:
|
||||||
# To print labels, call the Chrome extension and pass the PDF URL:
|
# To print labels, call the Chrome extension and pass the PDF URL:
|
||||||
|
|||||||
149
py_app/app/static/css/base.css
Normal file
149
py_app/app/static/css/base.css
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
/* Global Base Styles - Common across all pages */
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #f4f4f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
width: 100vw;
|
||||||
|
max-width: 100vw;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: none;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header Styles */
|
||||||
|
header {
|
||||||
|
padding: 10px 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-header .logo {
|
||||||
|
width: 60px;
|
||||||
|
height: auto;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-header .page-title {
|
||||||
|
font-size: 1.5em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-header .user-info {
|
||||||
|
margin-right: 15px;
|
||||||
|
font-size: 1em;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-header .logout-button {
|
||||||
|
padding: 5px 10px;
|
||||||
|
font-size: 1em;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-header .logout-button:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Common Button Styles */
|
||||||
|
.btn {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
background-color: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success:hover {
|
||||||
|
background-color: #218838;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background-color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:hover {
|
||||||
|
background-color: #c82333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-info {
|
||||||
|
background-color: #17a2b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-info:hover {
|
||||||
|
background-color: #138496;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Theme Toggle */
|
||||||
|
.theme-toggle {
|
||||||
|
background-color: #6c757d;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle:hover {
|
||||||
|
background-color: #5a6268;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark Mode Base Styles */
|
||||||
|
body.dark-mode {
|
||||||
|
background-color: #121212;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode header {
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .user-info {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
67
py_app/app/static/css/dashboard.css
Normal file
67
py_app/app/static/css/dashboard.css
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/* Dashboard and General Page Styles */
|
||||||
|
|
||||||
|
.dashboard-container {
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-card h3 {
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
color: #333;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-card p {
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-card a {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-card a:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode for dashboard */
|
||||||
|
body.dark-mode .module-card {
|
||||||
|
background: #2d2d2d;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .module-card h3 {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .module-card p {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
216
py_app/app/static/css/login.css
Normal file
216
py_app/app/static/css/login.css
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
/* Login Page Specific Styles */
|
||||||
|
|
||||||
|
.login-page {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: #f4f4f9;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-container {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-logo {
|
||||||
|
max-height: 90vh;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container {
|
||||||
|
width: 600px;
|
||||||
|
background: #fff;
|
||||||
|
padding: 15px 30px;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
margin: 0;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container h2 {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container label {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container input {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 16px; /* Prevents zoom on iOS */
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container button {
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container button:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide the header only on the login page */
|
||||||
|
body.light-mode.login-page header,
|
||||||
|
body.dark-mode.login-page header {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile Responsive Design */
|
||||||
|
|
||||||
|
/* Tablet styles (768px and down) */
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.login-page {
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px 15px;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-container {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-logo {
|
||||||
|
max-height: 30vh;
|
||||||
|
max-width: 80vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
padding: 20px 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile styles (480px and down) */
|
||||||
|
@media screen and (max-width: 480px) {
|
||||||
|
.login-page {
|
||||||
|
padding: 15px 10px;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-logo {
|
||||||
|
max-height: 25vh;
|
||||||
|
max-width: 85vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container {
|
||||||
|
padding: 20px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container h2 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container input {
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container button {
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container label {
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Small mobile styles (320px and down) */
|
||||||
|
@media screen and (max-width: 320px) {
|
||||||
|
.login-page {
|
||||||
|
padding: 10px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-logo {
|
||||||
|
max-height: 20vh;
|
||||||
|
max-width: 90vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container {
|
||||||
|
padding: 15px;
|
||||||
|
width: calc(100% - 16px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container h2 {
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Landscape orientation adjustments */
|
||||||
|
@media screen and (max-height: 500px) and (orientation: landscape) {
|
||||||
|
.login-page {
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-container {
|
||||||
|
flex: 1;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-logo {
|
||||||
|
max-height: 80vh;
|
||||||
|
max-width: 40vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container {
|
||||||
|
flex: 1;
|
||||||
|
max-width: 350px;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Touch device optimizations */
|
||||||
|
@media (hover: none) and (pointer: coarse) {
|
||||||
|
.form-container input {
|
||||||
|
min-height: 44px; /* Apple's recommended touch target size */
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container button {
|
||||||
|
min-height: 44px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container button:active {
|
||||||
|
background-color: #004494;
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* High DPI displays */
|
||||||
|
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
|
||||||
|
.login-logo {
|
||||||
|
image-rendering: -webkit-optimize-contrast;
|
||||||
|
image-rendering: crisp-edges;
|
||||||
|
}
|
||||||
|
}
|
||||||
759
py_app/app/static/css/warehouse.css
Normal file
759
py_app/app/static/css/warehouse.css
Normal file
@@ -0,0 +1,759 @@
|
|||||||
|
|
||||||
|
/* Ensure .main-content is full width for warehouse pages */
|
||||||
|
/* Fix horizontal overflow: use 100% width, not 100vw */
|
||||||
|
.main-content {
|
||||||
|
width: 100% !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
background: transparent !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
/* Normalize box-sizing for all elements */
|
||||||
|
*, *::before, *::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
/* WAREHOUSE PAGE CSS - FRESH START */
|
||||||
|
|
||||||
|
/* Reset and override all base styles for warehouse pages */
|
||||||
|
.main-content {
|
||||||
|
width: 100% !important;
|
||||||
|
height: calc(100vh - 60px) !important; /* Full height minus header */
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 10px !important; /* 10px margin from page edges */
|
||||||
|
background: #f4f4f9 !important; /* Match body background */
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main warehouse container - full width with 10px padding */
|
||||||
|
.warehouse-page-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px; /* 10px between containers */
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The 3 main containers with 1:2:1 ratio */
|
||||||
|
.warehouse-container-1 {
|
||||||
|
flex: 1; /* 1 part */
|
||||||
|
background: #f8fafc; /* Theme card background */
|
||||||
|
color: #1e293b; /* Theme text color */
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow-y: auto;
|
||||||
|
align-self: flex-start;
|
||||||
|
max-height: 640px;
|
||||||
|
min-height: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warehouse-container-2 {
|
||||||
|
flex: 2; /* 2 parts */
|
||||||
|
background: #f8fafc; /* Theme card background */
|
||||||
|
color: #1e293b; /* Theme text color */
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 640px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warehouse-container-3 {
|
||||||
|
flex: 1; /* 1 part */
|
||||||
|
background: #f8fafc; /* Theme card background */
|
||||||
|
color: #1e293b; /* Theme text color */
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 640px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form and table styles for warehouse components */
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px; /* Reduced gap for compactness */
|
||||||
|
}
|
||||||
|
|
||||||
|
form label {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 2px; /* Reduced margin for compactness */
|
||||||
|
}
|
||||||
|
|
||||||
|
form input, form select {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
background: #e2e8f0; /* Theme input background */
|
||||||
|
color: #1e293b; /* Theme input text */
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
background-color: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-info {
|
||||||
|
background-color: #17a2b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background-color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table styles */
|
||||||
|
.scan-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scan-table th, .scan-table td {
|
||||||
|
border: 1px solid #cbd5e1; /* Theme border color */
|
||||||
|
padding: 8px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scan-table th {
|
||||||
|
background-color: #e2e8f0; /* Theme input background for headers */
|
||||||
|
font-weight: bold;
|
||||||
|
color: #334155; /* Theme label color */
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-row {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-row:hover {
|
||||||
|
background-color: #e2e8f0; /* Theme hover background */
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-row.selected {
|
||||||
|
background-color: #ffb300 !important;
|
||||||
|
color: #222 !important;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Edit/Delete card styles */
|
||||||
|
.edit-delete-card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-message {
|
||||||
|
color: #64748b; /* Slightly lighter theme text */
|
||||||
|
font-style: italic;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-info {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Barcode preview */
|
||||||
|
#barcode-label-preview {
|
||||||
|
width: 8cm;
|
||||||
|
height: 4cm;
|
||||||
|
border: 2px solid #333;
|
||||||
|
background: white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 10px auto;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#barcode-label-preview .location-text {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal styles */
|
||||||
|
.modal {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1000;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0,0,0,0.5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
|
||||||
|
width: 90%;
|
||||||
|
max-width: 500px;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close:hover {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-table td {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-table td:first-child {
|
||||||
|
width: 30%;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-top: 15px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
width: 100% !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
.scan-container {
|
||||||
|
width: 100vw !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
display: block !important; /* Override any grid from style.css */
|
||||||
|
grid-template-columns: unset !important; /* Remove any grid columns */
|
||||||
|
gap: unset !important; /* Remove any gap */
|
||||||
|
max-width: 100vw !important; /* Override max-width from style.css */
|
||||||
|
box-shadow: none !important; /* Remove box shadow */
|
||||||
|
border-radius: 0 !important; /* Remove border radius */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Full-width container for warehouse pages - avoids scan-container restrictions */
|
||||||
|
.warehouse-full-container {
|
||||||
|
width: 100% !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
display: block !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Container card with left/right margins */
|
||||||
|
.warehouse-container-card {
|
||||||
|
margin-left: 15px;
|
||||||
|
margin-right: 15px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
width: calc(100% - 30px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3-column layout for warehouse create locations */
|
||||||
|
.warehouse-grid {
|
||||||
|
display: grid !important;
|
||||||
|
grid-template-columns: 1fr 2fr 1fr !important;
|
||||||
|
gap: 20px;
|
||||||
|
margin: 0;
|
||||||
|
min-height: auto;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make cards equal height in grid with higher specificity */
|
||||||
|
.warehouse-grid .card {
|
||||||
|
height: fit-content;
|
||||||
|
align-self: start;
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: none !important;
|
||||||
|
margin-left: 8px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive grid - collapse to single column on smaller screens */
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.warehouse-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.warehouse-grid {
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scan-container {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 0;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scan-form-card {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto 20px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scan-table-card {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scan-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 20px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scan-table th, .scan-table td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scan-table th {
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Location row selection styles */
|
||||||
|
.location-row {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-row:hover {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.location-row.selected, .location-row.selected td {
|
||||||
|
background-color: #ffb300 !important;
|
||||||
|
color: #222 !important;
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 2px 8px rgba(255,179,0,0.12);
|
||||||
|
border-left: 4px solid #ff9800;
|
||||||
|
transition: background 0.2s, color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Print section styles */
|
||||||
|
.print-section {
|
||||||
|
margin-top: 24px;
|
||||||
|
padding: 16px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Edit/Delete card styles - compact version */
|
||||||
|
.edit-delete-card {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-delete-card h3 {
|
||||||
|
font-size: 1.05em;
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-delete-card .selection-message {
|
||||||
|
color: #666;
|
||||||
|
font-style: italic;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-delete-card .selection-info {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-delete-card .button-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-delete-card .btn {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 5px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Barcode label preview */
|
||||||
|
#barcode-label-preview {
|
||||||
|
width: 8cm;
|
||||||
|
height: 4cm;
|
||||||
|
border: 2px solid #333;
|
||||||
|
background: white;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 10px auto;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.07);
|
||||||
|
}
|
||||||
|
|
||||||
|
#barcode-label-preview .location-text {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#barcode-label-preview #location-barcode {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 90%;
|
||||||
|
max-height: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#barcode-label-preview #barcode-placeholder {
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode for warehouse */
|
||||||
|
body.dark-mode .card {
|
||||||
|
background: #2d2d2d;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .scan-table {
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .scan-table th {
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .scan-table td {
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .scan-table tr:nth-child(even) {
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .print-section {
|
||||||
|
background-color: #333;
|
||||||
|
border-color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .edit-delete-card {
|
||||||
|
background-color: #333;
|
||||||
|
border-color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .edit-delete-card h3 {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .edit-delete-card .selection-message {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal Styles */
|
||||||
|
.modal {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1000;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0,0,0,0.5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
|
||||||
|
width: 90%;
|
||||||
|
max-width: 500px;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close:hover {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-top: 15px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-table td {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-table td:first-child {
|
||||||
|
width: 30%;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background-color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background-color: #5a6268;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode for modals */
|
||||||
|
body.dark-mode .modal-content {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .modal-header {
|
||||||
|
background-color: #333;
|
||||||
|
border-bottom-color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .modal-header h3 {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .close {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .close:hover {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .form-group input {
|
||||||
|
background-color: #333;
|
||||||
|
border-color: #555;
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .confirm-table td:first-child {
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .modal-buttons {
|
||||||
|
border-top-color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ENHANCED DARK MODE SUPPORT FOR NEW WAREHOUSE LAYOUT */
|
||||||
|
body.dark-mode .main-content {
|
||||||
|
background: #121212 !important; /* Match body dark background */
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .warehouse-container-1,
|
||||||
|
body.dark-mode .warehouse-container-2,
|
||||||
|
body.dark-mode .warehouse-container-3 {
|
||||||
|
background: #1e1e1e !important; /* Match header dark background */
|
||||||
|
color: #e0e0e0 !important; /* Match body dark text */
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode form label {
|
||||||
|
color: #e0e0e0 !important; /* Match body dark text */
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode form input,
|
||||||
|
body.dark-mode form select {
|
||||||
|
background: #2d2d2d !important; /* Slightly lighter than containers */
|
||||||
|
color: #e0e0e0 !important; /* Match body dark text */
|
||||||
|
border: 1px solid #444 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .btn {
|
||||||
|
background-color: #4299e1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .btn:hover {
|
||||||
|
background-color: #3182ce !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .btn-success {
|
||||||
|
background-color: #48bb78 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .btn-info {
|
||||||
|
background-color: #38b2ac !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .btn-danger {
|
||||||
|
background-color: #f56565 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .scan-table {
|
||||||
|
background-color: #1e1e1e !important; /* Match header background */
|
||||||
|
color: #e0e0e0 !important; /* Match body dark text */
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .scan-table th {
|
||||||
|
background-color: #2d2d2d !important; /* Slightly lighter */
|
||||||
|
color: #e0e0e0 !important; /* Match body dark text */
|
||||||
|
border: 1px solid #444 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .scan-table td {
|
||||||
|
color: #e0e0e0 !important; /* Match body dark text */
|
||||||
|
border: 1px solid #444 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .location-row:hover {
|
||||||
|
background-color: #2d2d2d !important; /* Slightly lighter than table */
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .location-row.selected {
|
||||||
|
background-color: #ed8936 !important;
|
||||||
|
color: #1a202c !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .selection-message {
|
||||||
|
color: #999 !important; /* Lighter gray for secondary text */
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .selection-info {
|
||||||
|
color: #e0e0e0 !important; /* Match body dark text */
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode #barcode-label-preview {
|
||||||
|
background: #f8fafc !important; /* Keep white for readability */
|
||||||
|
border: 2px solid #444 !important;
|
||||||
|
}
|
||||||
@@ -4,7 +4,12 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{% block title %}Flask App{% endblock %}</title>
|
<title>{% block title %}Flask App{% endblock %}</title>
|
||||||
|
<!-- Base CSS for common styles -->
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/base.css') }}">
|
||||||
|
<!-- Legacy CSS for backward compatibility (temporarily) -->
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||||
|
<!-- Page-specific CSS -->
|
||||||
|
{% block head %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body class="light-mode">
|
<body class="light-mode">
|
||||||
{% if request.endpoint != 'main.login' %}
|
{% if request.endpoint != 'main.login' %}
|
||||||
@@ -38,7 +43,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="container">
|
<div class="main-content">
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
||||||
|
|||||||
@@ -2,72 +2,13 @@
|
|||||||
{% block title %}Create Warehouse Locations{% endblock %}
|
{% block title %}Create Warehouse Locations{% endblock %}
|
||||||
|
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<style>
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/warehouse.css') }}">
|
||||||
/* Barcode label preview */
|
|
||||||
#barcode-label-preview {
|
|
||||||
width: 8cm;
|
|
||||||
height: 4cm;
|
|
||||||
border: 2px solid #333;
|
|
||||||
background: white;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin: 10px auto;
|
|
||||||
position: relative;
|
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.07);
|
|
||||||
}
|
|
||||||
|
|
||||||
#barcode-label-preview .location-text {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#barcode-label-preview #location-barcode {
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto;
|
|
||||||
max-width: 90%;
|
|
||||||
max-height: 70%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#barcode-label-preview #barcode-placeholder {
|
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.print-section {
|
|
||||||
margin-top: 24px;
|
|
||||||
padding: 16px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 5px;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-row.selected {
|
|
||||||
background-color: #ffb300 !important; /* Accent color: Amber */
|
|
||||||
color: #222 !important;
|
|
||||||
font-weight: bold;
|
|
||||||
box-shadow: 0 2px 8px rgba(255,179,0,0.12);
|
|
||||||
border-left: 4px solid #ff9800;
|
|
||||||
transition: background 0.2s, color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-row {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-row:hover {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="scan-container">
|
<div class="warehouse-page-container">
|
||||||
<!-- Add Warehouse Location Card -->
|
<!-- Container 1: Add Location (1 part width) -->
|
||||||
<div class="card scan-form-card">
|
<div class="warehouse-container-1">
|
||||||
<h3>Add Warehouse Location</h3>
|
<h3>Add Warehouse Location</h3>
|
||||||
{% if message %}
|
{% if message %}
|
||||||
<div class="form-message">{{ message }}</div>
|
<div class="form-message">{{ message }}</div>
|
||||||
@@ -81,7 +22,7 @@
|
|||||||
<input type="text" name="description" maxlength="250"><br>
|
<input type="text" name="description" maxlength="250"><br>
|
||||||
<button type="submit" class="btn">Add Location</button>
|
<button type="submit" class="btn">Add Location</button>
|
||||||
</form>
|
</form>
|
||||||
<!-- Import from CSV content moved here -->
|
<!-- Import from CSV section -->
|
||||||
<div style="margin-top: 24px;">
|
<div style="margin-top: 24px;">
|
||||||
<h3 style="font-size: 1.1em;">Import Locations from CSV</h3>
|
<h3 style="font-size: 1.1em;">Import Locations from CSV</h3>
|
||||||
<div style="display: flex; flex-direction: row; gap: 16px; align-items: center;">
|
<div style="display: flex; flex-direction: row; gap: 16px; align-items: center;">
|
||||||
@@ -89,11 +30,75 @@
|
|||||||
<a href="{{ url_for('warehouse.import_locations_csv') }}" class="btn" style="padding: 4px 12px; font-size: 0.95em;">Go to Import Page</a>
|
<a href="{{ url_for('warehouse.import_locations_csv') }}" class="btn" style="padding: 4px 12px; font-size: 0.95em;">Go to Import Page</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Print Barcode Section -->
|
<!-- Container 2: Warehouse Locations Table (2 parts width) -->
|
||||||
<div class="print-section">
|
<div class="warehouse-container-2">
|
||||||
|
<h3>Warehouse Locations</h3>
|
||||||
|
<div id="location-selection-hint" style="margin-bottom: 10px; font-size: 11px; color: #666; text-align: center;">
|
||||||
|
Click on a row to select a location for printing
|
||||||
|
</div>
|
||||||
|
<table class="scan-table" id="locations-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Location Code</th>
|
||||||
|
<th>Size</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for loc in locations %}
|
||||||
|
<tr class="location-row" data-location-code="{{ loc[1] }}"
|
||||||
|
data-location-id="{{ loc[0] }}"
|
||||||
|
data-location-size="{{ loc[2] or '' }}"
|
||||||
|
data-location-description="{{ loc[3] or '' }}">
|
||||||
|
<td>{{ loc[0] }}</td>
|
||||||
|
<td>{{ loc[1] }}</td>
|
||||||
|
<td>{{ loc[2] or '' }}</td>
|
||||||
|
<td>{{ loc[3] or '' }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Container 3: Edit/Delete and Print (1 part width) -->
|
||||||
|
<div class="warehouse-container-3">
|
||||||
|
<div class="edit-delete-card">
|
||||||
|
<h3>Edit/Delete Selected Location</h3>
|
||||||
|
<div id="no-selection-message" class="selection-message">
|
||||||
|
Select a location from the table to edit or delete it.
|
||||||
|
</div>
|
||||||
|
<div id="selected-location-info" class="selection-info" style="display: none;">
|
||||||
|
<strong>Selected:</strong> <span id="selected-location-display"></span>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<button id="edit-location-btn" class="btn btn-info" disabled>
|
||||||
|
✏️ Edit Selected Location
|
||||||
|
</button>
|
||||||
|
<button id="delete-location-btn" class="btn btn-danger" disabled>
|
||||||
|
🗑️ Delete Selected Location
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if session['role'] in ['administrator', 'management'] %}
|
||||||
|
<div style="margin-top: 32px; padding: 12px; border-top: 1px solid #eee;">
|
||||||
|
<label style="font-weight:bold;">Delete location from table</label>
|
||||||
|
<div style="font-size:0.95em; margin-bottom:8px;">To delete a location, enter the ID of the location and press delete.<br>To delete 2 or multiple locations, enter the IDs separated by "," and then press delete.</div>
|
||||||
|
<form method="POST" style="display:flex; gap:8px; align-items:center;" onsubmit="return confirmDeleteLocations();">
|
||||||
|
<input type="text" name="delete_ids" placeholder="e.g. 5,7,12" style="width:160px;">
|
||||||
|
<button type="submit" name="delete_locations" value="1" class="btn" style="padding:4px 16px;">Delete Locations</button>
|
||||||
|
</form>
|
||||||
|
<script>
|
||||||
|
function confirmDeleteLocations() {
|
||||||
|
return confirm('Do you really want to delete the selected locations?');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<hr style="margin: 32px 0 24px 0;">
|
||||||
<h3 style="font-size: 1.1em; margin-bottom: 12px;">Print Location Barcode</h3>
|
<h3 style="font-size: 1.1em; margin-bottom: 12px;">Print Location Barcode</h3>
|
||||||
|
|
||||||
<!-- Label Preview -->
|
<!-- Label Preview -->
|
||||||
<div id="barcode-label-preview" style="display: flex; align-items: center; justify-content: center;">
|
<div id="barcode-label-preview" style="display: flex; align-items: center; justify-content: center;">
|
||||||
<div style="width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center;">
|
<div style="width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center;">
|
||||||
@@ -104,7 +109,6 @@
|
|||||||
<div id="barcode-placeholder" style="color: #999; font-style: italic; text-align: center;">No location selected</div>
|
<div id="barcode-placeholder" style="color: #999; font-style: italic; text-align: center;">No location selected</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Print Controls -->
|
<!-- Print Controls -->
|
||||||
<div style="text-align: center; margin-top: 15px;">
|
<div style="text-align: center; margin-top: 15px;">
|
||||||
<div style="margin-bottom: 10px;">
|
<div style="margin-bottom: 10px;">
|
||||||
@@ -121,51 +125,29 @@
|
|||||||
</button>
|
</button>
|
||||||
<div id="print-status" style="margin-top: 8px; font-size: 11px;"></div>
|
<div id="print-status" style="margin-top: 8px; font-size: 11px;"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Delete Location Area -->
|
|
||||||
{% if session['role'] in ['administrator', 'management'] %}
|
|
||||||
<div style="margin-top: 32px; padding: 12px; border-top: 1px solid #eee;">
|
|
||||||
<label style="font-weight:bold;">Delete location from table</label>
|
|
||||||
<div style="font-size:0.95em; margin-bottom:8px;">To delete a location, enter the ID of the location and press delete.<br>To delete 2 or multiple locations, enter the IDs separated by "," and then press delete.</div>
|
|
||||||
<form method="POST" style="display:flex; gap:8px; align-items:center;" onsubmit="return confirmDeleteLocations();">
|
|
||||||
<input type="text" name="delete_ids" placeholder="e.g. 5,7,12" style="width:160px;">
|
|
||||||
<button type="submit" name="delete_locations" value="1" class="btn" style="padding:4px 16px;">Delete Locations</button>
|
|
||||||
</form>
|
|
||||||
<script>
|
|
||||||
function confirmDeleteLocations() {
|
|
||||||
return confirm('Do you really want to delete the selected locations?');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
<!-- Locations Table Card -->
|
</div>
|
||||||
<div class="card scan-table-card">
|
|
||||||
<h3>Warehouse Locations</h3>
|
<!-- Delete Confirmation Modal -->
|
||||||
<div id="location-selection-hint" style="margin-bottom: 10px; font-size: 11px; color: #666; text-align: center;">
|
<div id="delete-modal" class="modal" style="display: none;">
|
||||||
Click on a row to select a location for printing
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Confirm Delete</h3>
|
||||||
|
<span class="close" onclick="closeDeleteModal()">×</span>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Are you sure you want to delete the following location?</p>
|
||||||
|
<table class="confirm-table">
|
||||||
|
<tr><td><strong>ID:</strong></td><td id="delete-confirm-id"></td></tr>
|
||||||
|
<tr><td><strong>Location Code:</strong></td><td id="delete-confirm-code"></td></tr>
|
||||||
|
<tr><td><strong>Size:</strong></td><td id="delete-confirm-size"></td></tr>
|
||||||
|
<tr><td><strong>Description:</strong></td><td id="delete-confirm-description"></td></tr>
|
||||||
|
</table>
|
||||||
|
<div class="modal-buttons">
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="closeDeleteModal()">Cancel</button>
|
||||||
|
<button type="button" class="btn btn-danger" onclick="confirmDelete()">Delete Location</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="scan-table" id="locations-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>ID</th>
|
|
||||||
<th>Location Code</th>
|
|
||||||
<th>Size</th>
|
|
||||||
<th>Description</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for loc in locations %}
|
|
||||||
<tr class="location-row" data-location-code="{{ loc[1] }}">
|
|
||||||
<td>{{ loc[0] }}</td>
|
|
||||||
<td>{{ loc[1] }}</td>
|
|
||||||
<td>{{ loc[2] }}</td>
|
|
||||||
<td>{{ loc[3] }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -369,6 +351,13 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const locationRows = document.querySelectorAll('.location-row');
|
const locationRows = document.querySelectorAll('.location-row');
|
||||||
const locationCodeDisplay = document.getElementById('location-code-display');
|
const locationCodeDisplay = document.getElementById('location-code-display');
|
||||||
const printButton = document.getElementById('print-barcode-btn');
|
const printButton = document.getElementById('print-barcode-btn');
|
||||||
|
const editButton = document.getElementById('edit-location-btn');
|
||||||
|
const deleteButton = document.getElementById('delete-location-btn');
|
||||||
|
const noSelectionMessage = document.getElementById('no-selection-message');
|
||||||
|
const selectedLocationInfo = document.getElementById('selected-location-info');
|
||||||
|
const selectedLocationDisplay = document.getElementById('selected-location-display');
|
||||||
|
|
||||||
|
let selectedLocation = null;
|
||||||
|
|
||||||
locationRows.forEach(row => {
|
locationRows.forEach(row => {
|
||||||
row.addEventListener('click', function() {
|
row.addEventListener('click', function() {
|
||||||
@@ -378,22 +367,49 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
// Select this row
|
// Select this row
|
||||||
this.classList.add('selected');
|
this.classList.add('selected');
|
||||||
|
|
||||||
// Get location code
|
// Get location data
|
||||||
selectedLocationCode = this.dataset.locationCode;
|
selectedLocationCode = this.dataset.locationCode;
|
||||||
|
selectedLocation = {
|
||||||
|
id: this.cells[0].textContent,
|
||||||
|
code: this.cells[1].textContent,
|
||||||
|
size: this.cells[2].textContent,
|
||||||
|
description: this.cells[3].textContent
|
||||||
|
};
|
||||||
|
|
||||||
// Update display
|
// Update display
|
||||||
locationCodeDisplay.textContent = selectedLocationCode;
|
locationCodeDisplay.textContent = selectedLocationCode;
|
||||||
|
selectedLocationDisplay.textContent = `${selectedLocation.code} (ID: ${selectedLocation.id})`;
|
||||||
|
|
||||||
|
// Show/hide selection info
|
||||||
|
noSelectionMessage.style.display = 'none';
|
||||||
|
selectedLocationInfo.style.display = 'block';
|
||||||
|
|
||||||
// Generate barcode
|
// Generate barcode
|
||||||
generateLocationBarcode(selectedLocationCode);
|
generateLocationBarcode(selectedLocationCode);
|
||||||
|
|
||||||
// Enable print button
|
// Enable buttons
|
||||||
printButton.disabled = false;
|
printButton.disabled = false;
|
||||||
|
editButton.disabled = false;
|
||||||
|
deleteButton.disabled = false;
|
||||||
|
|
||||||
showNotification(`📍 Selected location: ${selectedLocationCode}`, 'info');
|
showNotification(`📍 Selected location: ${selectedLocationCode}`, 'info');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Edit button functionality
|
||||||
|
editButton.addEventListener('click', function() {
|
||||||
|
if (selectedLocation) {
|
||||||
|
openEditModal(selectedLocation);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete button functionality
|
||||||
|
deleteButton.addEventListener('click', function() {
|
||||||
|
if (selectedLocation) {
|
||||||
|
openDeleteModal(selectedLocation);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Initialize QZ Tray
|
// Initialize QZ Tray
|
||||||
initializeQZTray();
|
initializeQZTray();
|
||||||
});
|
});
|
||||||
@@ -519,5 +535,111 @@ async function testQZConnection() {
|
|||||||
|
|
||||||
// Add test button functionality (for debugging - can be removed in production)
|
// Add test button functionality (for debugging - can be removed in production)
|
||||||
console.log('🔧 QZ Tray test function available: testQZConnection()');
|
console.log('🔧 QZ Tray test function available: testQZConnection()');
|
||||||
|
|
||||||
|
// Modal Functions for Edit/Delete
|
||||||
|
function openEditModal(location) {
|
||||||
|
document.getElementById('edit-location-id').value = location.id;
|
||||||
|
document.getElementById('edit-location-code').value = location.code;
|
||||||
|
document.getElementById('edit-size').value = location.size || '';
|
||||||
|
document.getElementById('edit-description').value = location.description || '';
|
||||||
|
document.getElementById('edit-modal').style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeEditModal() {
|
||||||
|
document.getElementById('edit-modal').style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDeleteModal(location) {
|
||||||
|
document.getElementById('delete-confirm-id').textContent = location.id;
|
||||||
|
document.getElementById('delete-confirm-code').textContent = location.code;
|
||||||
|
document.getElementById('delete-confirm-size').textContent = location.size || '-';
|
||||||
|
document.getElementById('delete-confirm-description').textContent = location.description || '-';
|
||||||
|
document.getElementById('delete-modal').style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDeleteModal() {
|
||||||
|
document.getElementById('delete-modal').style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle edit form submission
|
||||||
|
document.getElementById('edit-form').addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const formData = new FormData(this);
|
||||||
|
const data = {
|
||||||
|
location_id: formData.get('location_id'),
|
||||||
|
location_code: formData.get('location_code'),
|
||||||
|
size: formData.get('size'),
|
||||||
|
description: formData.get('description')
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send update request
|
||||||
|
fetch('/update_location', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(result => {
|
||||||
|
if (result.success) {
|
||||||
|
showNotification('✅ Location updated successfully!', 'success');
|
||||||
|
closeEditModal();
|
||||||
|
// Reload page to show changes
|
||||||
|
setTimeout(() => window.location.reload(), 1000);
|
||||||
|
} else {
|
||||||
|
showNotification('❌ Error updating location: ' + result.error, 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
showNotification('❌ Error updating location: ' + error.message, 'error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle delete confirmation
|
||||||
|
function confirmDelete() {
|
||||||
|
const locationId = document.getElementById('delete-confirm-id').textContent;
|
||||||
|
|
||||||
|
// Send delete request
|
||||||
|
fetch('/delete_location', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ location_id: locationId })
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(result => {
|
||||||
|
if (result.success) {
|
||||||
|
showNotification('✅ Location deleted successfully!', 'success');
|
||||||
|
closeDeleteModal();
|
||||||
|
// Reload page to show changes
|
||||||
|
setTimeout(() => window.location.reload(), 1000);
|
||||||
|
} else {
|
||||||
|
showNotification('❌ Error deleting location: ' + result.error, 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
showNotification('❌ Error deleting location: ' + error.message, 'error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close modals when clicking outside
|
||||||
|
window.addEventListener('click', function(event) {
|
||||||
|
const editModal = document.getElementById('edit-modal');
|
||||||
|
const deleteModal = document.getElementById('delete-modal');
|
||||||
|
|
||||||
|
if (event.target === editModal) {
|
||||||
|
closeEditModal();
|
||||||
|
}
|
||||||
|
if (event.target === deleteModal) {
|
||||||
|
closeDeleteModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
{% block title %}Login{% endblock %}
|
{% block title %}Login{% endblock %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/login.css') }}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="login-page">
|
<div class="login-page">
|
||||||
<div class="logo-container">
|
<div class="logo-container">
|
||||||
|
|||||||
@@ -218,3 +218,59 @@ def generate_location_label_pdf():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error generating location label PDF: {e}")
|
print(f"Error generating location label PDF: {e}")
|
||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
def update_location(location_id, location_code, size, description):
|
||||||
|
"""Update an existing warehouse location"""
|
||||||
|
try:
|
||||||
|
conn = get_db_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Check if location exists
|
||||||
|
cursor.execute("SELECT id FROM warehouse_locations WHERE id = ?", (location_id,))
|
||||||
|
if not cursor.fetchone():
|
||||||
|
conn.close()
|
||||||
|
return {"success": False, "error": "Location not found"}
|
||||||
|
|
||||||
|
# Check if location code already exists for different location
|
||||||
|
cursor.execute("SELECT id FROM warehouse_locations WHERE location_code = ? AND id != ?", (location_code, location_id))
|
||||||
|
if cursor.fetchone():
|
||||||
|
conn.close()
|
||||||
|
return {"success": False, "error": "Location code already exists"}
|
||||||
|
|
||||||
|
# Update location
|
||||||
|
cursor.execute(
|
||||||
|
"UPDATE warehouse_locations SET location_code = ?, size = ?, description = ? WHERE id = ?",
|
||||||
|
(location_code, size if size else None, description, location_id)
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return {"success": True, "message": "Location updated successfully"}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error updating location: {e}")
|
||||||
|
return {"success": False, "error": str(e)}
|
||||||
|
|
||||||
|
def delete_location_by_id(location_id):
|
||||||
|
"""Delete a warehouse location by ID"""
|
||||||
|
try:
|
||||||
|
conn = get_db_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Check if location exists
|
||||||
|
cursor.execute("SELECT location_code FROM warehouse_locations WHERE id = ?", (location_id,))
|
||||||
|
location = cursor.fetchone()
|
||||||
|
if not location:
|
||||||
|
conn.close()
|
||||||
|
return {"success": False, "error": "Location not found"}
|
||||||
|
|
||||||
|
# Delete location
|
||||||
|
cursor.execute("DELETE FROM warehouse_locations WHERE id = ?", (location_id,))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return {"success": True, "message": f"Location '{location[0]}' deleted successfully"}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error deleting location: {e}")
|
||||||
|
return {"success": False, "error": str(e)}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
server_domain=localhost
|
server_domain=localhost
|
||||||
port=3602
|
port=3602
|
||||||
database_name=recticel
|
database_name=trasabilitate_database
|
||||||
username=sa
|
username=trasabilitate
|
||||||
password=12345678
|
password=Initial01!
|
||||||
|
|||||||
Reference in New Issue
Block a user