Compare commits
10 Commits
9baca0e6a2
...
e280cd5a34
| Author | SHA1 | Date | |
|---|---|---|---|
| e280cd5a34 | |||
| 8363509210 | |||
| ce697548c6 | |||
| 063e446983 | |||
| 8ef7fb9230 | |||
| 32672387a6 | |||
| 38970430a0 | |||
| d3ecfae5df | |||
| 5c11a289df | |||
| e0d822adc8 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -41,7 +41,10 @@ a.py
|
||||
*.txt
|
||||
*.dxf
|
||||
/public/*.dxf
|
||||
/public/uploads/*
|
||||
/pages/api/auth/*
|
||||
|
||||
/cypress
|
||||
cypress.json
|
||||
|
||||
/tool-templates
|
||||
465
ADDING_NEW_TOOLS_GUIDE.md
Normal file
465
ADDING_NEW_TOOLS_GUIDE.md
Normal file
@@ -0,0 +1,465 @@
|
||||
# Adding New Tools and Functionality Guide
|
||||
|
||||
This document provides a comprehensive guide for adding new tools and functionality to the Wastpol web application. It's designed to help developers (including AI agents) integrate new tools from separate projects into the main application.
|
||||
|
||||
## Project Overview
|
||||
|
||||
The Wastpol application is a Next.js-based web application with the following key characteristics:
|
||||
- **Framework**: Next.js 12 with React 17
|
||||
- **Styling**: Tailwind CSS with custom UI components
|
||||
- **Authentication**: NextAuth.js
|
||||
- **Architecture**: Pages-based routing with component-driven design
|
||||
- **UI Library**: Custom components + Evergreen UI + Heroicons
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
web-app/
|
||||
├── pages/ # Next.js pages (routes)
|
||||
│ ├── api/ # API endpoints
|
||||
│ ├── index.js # Main dashboard with tabs
|
||||
│ ├── cross.js # Grid tool
|
||||
│ ├── uziomy.js # Ground connections tool
|
||||
│ ├── tracking.js # Package tracking
|
||||
│ └── [other-tools].js # Additional tools
|
||||
├── components/
|
||||
│ ├── ui/ # Reusable UI components
|
||||
│ │ ├── Layout.js # Main layout with navigation
|
||||
│ │ └── components.js # UI component library
|
||||
│ └── templates/ # Tool-specific components
|
||||
├── styles/ # Global styles
|
||||
├── public/ # Static assets
|
||||
└── externals/ # External data files
|
||||
```
|
||||
|
||||
## Adding a New Tool - Step by Step
|
||||
|
||||
### 1. Planning Phase
|
||||
|
||||
Before adding a new tool, define:
|
||||
- **Tool Purpose**: What does the tool do?
|
||||
- **Tool Name**: Short, descriptive name
|
||||
- **Route**: URL path (e.g., `/new-tool`)
|
||||
- **Icon**: Choose from Heroicons
|
||||
- **Dependencies**: Any new packages needed
|
||||
- **API Requirements**: Backend processing needs
|
||||
|
||||
### 2. Tool Integration Checklist
|
||||
|
||||
#### A. Create the Main Page Component
|
||||
|
||||
Create a new file: `pages/[tool-name].js`
|
||||
|
||||
**Template Structure:**
|
||||
```javascript
|
||||
import { useState, useCallback } from "react";
|
||||
import { useSession, signIn } from "next-auth/react";
|
||||
import Layout from "../components/ui/Layout";
|
||||
import { Card, CardHeader, CardContent, CardTitle, CardDescription, Button, Alert } from "../components/ui/components";
|
||||
import { [ToolIcon] } from '@heroicons/react/24/outline';
|
||||
|
||||
export default function NewTool() {
|
||||
const { data: session } = useSession();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
// Tool-specific state variables
|
||||
const [toolData, setToolData] = useState(null);
|
||||
|
||||
// Authentication check
|
||||
if (!session) {
|
||||
return (
|
||||
<Layout title="Wastpol - New Tool">
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardContent className="text-center p-6">
|
||||
<h2 className="text-xl font-semibold mb-4">Wymagane logowanie</h2>
|
||||
<Button onClick={() => signIn()}>
|
||||
Zaloguj się
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout title="Wastpol - New Tool">
|
||||
<div className="p-6 max-w-7xl mx-auto">
|
||||
{/* Page Header */}
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">
|
||||
New Tool Name
|
||||
</h1>
|
||||
<p className="text-gray-600">
|
||||
Tool description here
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center">
|
||||
<[ToolIcon] className="w-6 h-6 mr-2" />
|
||||
Tool Interface
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Brief description of what this tool does
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{/* Tool-specific UI goes here */}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### B. Add Navigation Entry
|
||||
|
||||
Update `components/ui/Layout.js`:
|
||||
|
||||
1. **Import the icon:**
|
||||
```javascript
|
||||
import {
|
||||
// ...existing imports
|
||||
[NewToolIcon]
|
||||
} from '@heroicons/react/24/outline';
|
||||
```
|
||||
|
||||
2. **Add to navigationItems array:**
|
||||
```javascript
|
||||
const navigationItems = [
|
||||
// ...existing items
|
||||
{ name: 'New Tool Name', href: '/new-tool', icon: NewToolIcon },
|
||||
];
|
||||
```
|
||||
|
||||
#### C. Create API Endpoint (if needed)
|
||||
|
||||
Create `pages/api/[tool-name].js`:
|
||||
|
||||
```javascript
|
||||
import formidable from 'formidable';
|
||||
import { getSession } from 'next-auth/react';
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false,
|
||||
},
|
||||
};
|
||||
|
||||
export default async function handler(req, res) {
|
||||
// Authentication check
|
||||
const session = await getSession({ req });
|
||||
if (!session) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
if (req.method !== 'POST') {
|
||||
return res.status(405).json({ error: 'Method not allowed' });
|
||||
}
|
||||
|
||||
try {
|
||||
// Tool-specific processing logic
|
||||
const form = new formidable.IncomingForm();
|
||||
const [fields, files] = await form.parse(req);
|
||||
|
||||
// Process the request
|
||||
const result = await processToolData(fields, files);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: result
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Tool processing error:', error);
|
||||
res.status(500).json({
|
||||
error: 'Internal server error',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function processToolData(fields, files) {
|
||||
// Implement tool-specific logic here
|
||||
return {};
|
||||
}
|
||||
```
|
||||
|
||||
#### D. Add Tool-Specific Components (Optional)
|
||||
|
||||
Create reusable components in `components/templates/[tool-name]/`:
|
||||
|
||||
```javascript
|
||||
// components/templates/new-tool/ToolComponent.js
|
||||
import { useState } from 'react';
|
||||
import { Button, Alert } from '../../ui/components';
|
||||
|
||||
export default function ToolComponent({ onSubmit, isLoading }) {
|
||||
const [inputData, setInputData] = useState('');
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
onSubmit(inputData);
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{/* Tool-specific form elements */}
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className="w-full"
|
||||
>
|
||||
{isLoading ? 'Processing...' : 'Submit'}
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Integration from Separate Project
|
||||
|
||||
#### A. Identify Components to Port
|
||||
|
||||
When integrating from a separate project:
|
||||
|
||||
1. **Identify core functionality files**
|
||||
2. **Extract reusable components**
|
||||
3. **Map dependencies and APIs**
|
||||
4. **Identify data processing logic**
|
||||
|
||||
#### B. Dependency Management
|
||||
|
||||
1. **Check existing dependencies** in `package.json`
|
||||
2. **Add new dependencies** if needed:
|
||||
```bash
|
||||
npm install [new-package]
|
||||
```
|
||||
3. **Update package.json** if manual editing is needed
|
||||
|
||||
#### C. Asset Management
|
||||
|
||||
1. **Static files**: Place in `public/` directory
|
||||
2. **External data**: Place in `externals/` directory
|
||||
3. **Uploads**: Use `public/uploads/` for user uploads
|
||||
|
||||
### 4. Common Integration Patterns
|
||||
|
||||
#### File Upload Pattern
|
||||
```javascript
|
||||
import { FileUploader, FileCard } from "evergreen-ui";
|
||||
|
||||
const [files, setFiles] = useState([]);
|
||||
const handleChange = useCallback((files) => setFiles([files[0]]), []);
|
||||
```
|
||||
|
||||
#### API Call Pattern
|
||||
```javascript
|
||||
import axios from "axios";
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("data", JSON.stringify(data));
|
||||
|
||||
const response = await axios.post("/api/new-tool", formData);
|
||||
setResult(response.data);
|
||||
} catch (error) {
|
||||
setError(error.message);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### State Management Pattern
|
||||
```javascript
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [result, setResult] = useState(null);
|
||||
const [error, setError] = useState(null);
|
||||
```
|
||||
|
||||
### 5. Styling Guidelines
|
||||
|
||||
#### Use Existing UI Components
|
||||
```javascript
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardContent,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
Button,
|
||||
Alert
|
||||
} from "../components/ui/components";
|
||||
```
|
||||
|
||||
#### Consistent Layout Classes
|
||||
- **Container**: `p-6 max-w-7xl mx-auto`
|
||||
- **Page Header**: `mb-8`
|
||||
- **Title**: `text-3xl font-bold text-gray-900 mb-2`
|
||||
- **Description**: `text-gray-600`
|
||||
- **Cards**: Use existing Card components
|
||||
- **Spacing**: `space-y-4` for form elements
|
||||
|
||||
### 6. Error Handling Best Practices
|
||||
|
||||
#### Client-Side Error Handling
|
||||
```javascript
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
// Clear error on new action
|
||||
const clearError = () => setError(null);
|
||||
|
||||
// Display errors
|
||||
{error && (
|
||||
<Alert variant="destructive" className="mb-4">
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
```
|
||||
|
||||
#### Server-Side Error Handling
|
||||
```javascript
|
||||
try {
|
||||
// Process request
|
||||
} catch (error) {
|
||||
console.error('Tool error:', error);
|
||||
return res.status(500).json({
|
||||
error: 'Processing failed',
|
||||
details: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Testing Integration
|
||||
|
||||
#### Manual Testing Checklist
|
||||
- [ ] Tool loads without errors
|
||||
- [ ] Authentication works correctly
|
||||
- [ ] Navigation item appears and works
|
||||
- [ ] File uploads work (if applicable)
|
||||
- [ ] API endpoints respond correctly
|
||||
- [ ] Results display properly
|
||||
- [ ] Error handling works
|
||||
- [ ] Mobile responsiveness
|
||||
- [ ] Loading states display correctly
|
||||
|
||||
#### Console Testing
|
||||
1. Check browser console for errors
|
||||
2. Verify API responses in Network tab
|
||||
3. Test with different user roles
|
||||
|
||||
### 8. AI Agent Instructions
|
||||
|
||||
When an AI agent is adding a new tool:
|
||||
|
||||
#### Information to Gather
|
||||
1. **Tool description and purpose**
|
||||
2. **Required input/output formats**
|
||||
3. **Processing logic requirements**
|
||||
4. **UI/UX preferences**
|
||||
5. **Integration points with existing system**
|
||||
|
||||
#### Step-by-Step Process
|
||||
1. **Analyze the existing project structure**
|
||||
2. **Identify the closest existing tool for reference**
|
||||
3. **Create the main page component**
|
||||
4. **Add navigation entry**
|
||||
5. **Create API endpoint if needed**
|
||||
6. **Test the integration**
|
||||
7. **Handle any errors or conflicts**
|
||||
|
||||
#### Code Quality Standards
|
||||
- Follow existing naming conventions
|
||||
- Use consistent styling patterns
|
||||
- Implement proper error handling
|
||||
- Add loading states for async operations
|
||||
- Ensure responsive design
|
||||
- Maintain authentication requirements
|
||||
|
||||
### 9. Example Tool Addition
|
||||
|
||||
Here's how to add a hypothetical "Document Generator" tool:
|
||||
|
||||
#### Step 1: Create `pages/document-generator.js`
|
||||
```javascript
|
||||
import { useState } from "react";
|
||||
import { useSession, signIn } from "next-auth/react";
|
||||
import Layout from "../components/ui/Layout";
|
||||
import { Card, CardHeader, CardContent, CardTitle } from "../components/ui/components";
|
||||
import { DocumentTextIcon } from '@heroicons/react/24/outline';
|
||||
|
||||
export default function DocumentGenerator() {
|
||||
const { data: session } = useSession();
|
||||
// Component implementation...
|
||||
}
|
||||
```
|
||||
|
||||
#### Step 2: Update `components/ui/Layout.js`
|
||||
```javascript
|
||||
import { DocumentTextIcon } from '@heroicons/react/24/outline';
|
||||
|
||||
const navigationItems = [
|
||||
// ...existing items
|
||||
{ name: 'Generator dokumentów', href: '/document-generator', icon: DocumentTextIcon },
|
||||
];
|
||||
```
|
||||
|
||||
#### Step 3: Create `pages/api/document-generator.js`
|
||||
```javascript
|
||||
export default async function handler(req, res) {
|
||||
// API implementation...
|
||||
}
|
||||
```
|
||||
|
||||
### 10. Maintenance and Updates
|
||||
|
||||
#### Version Control
|
||||
- Commit changes with descriptive messages
|
||||
- Tag releases for major tool additions
|
||||
- Document breaking changes
|
||||
|
||||
#### Documentation Updates
|
||||
- Update this guide when patterns change
|
||||
- Document new dependencies
|
||||
- Keep API documentation current
|
||||
|
||||
#### Performance Considerations
|
||||
- Monitor bundle size with new dependencies
|
||||
- Optimize images and assets
|
||||
- Consider lazy loading for heavy components
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Commands
|
||||
|
||||
```bash
|
||||
# Install new dependency
|
||||
npm install [package-name]
|
||||
|
||||
# Run development server
|
||||
npm run dev
|
||||
|
||||
# Build for production
|
||||
npm run build
|
||||
|
||||
# Deploy changes
|
||||
npm run deploy
|
||||
```
|
||||
|
||||
## Common File Locations
|
||||
|
||||
- **New page**: `pages/[tool-name].js`
|
||||
- **API endpoint**: `pages/api/[tool-name].js`
|
||||
- **Components**: `components/templates/[tool-name]/`
|
||||
- **Navigation**: `components/ui/Layout.js`
|
||||
- **Styles**: `styles/globals.css`
|
||||
- **Assets**: `public/`
|
||||
|
||||
This guide should provide everything needed to successfully integrate new tools into the Wastpol application while maintaining consistency and code quality.
|
||||
21
Dockerfile
Normal file
21
Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
||||
# Build stage
|
||||
FROM node:16 AS builder
|
||||
WORKDIR /panel
|
||||
COPY . .
|
||||
RUN npm install && npm run build
|
||||
|
||||
# Final stage: Node.js + Python + ezdxf
|
||||
FROM node:16-bullseye
|
||||
|
||||
# Install Python and pip
|
||||
RUN apt-get update && apt-get install -y python3 python3-pip && \
|
||||
pip3 install --no-cache-dir ezdxf && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy Node app
|
||||
WORKDIR /panel
|
||||
COPY --from=builder /panel ./
|
||||
RUN npm install --omit=dev
|
||||
|
||||
EXPOSE 3000
|
||||
CMD ["npm", "run", "start"]
|
||||
153
SPADKI_INTEGRATION_SUMMARY.md
Normal file
153
SPADKI_INTEGRATION_SUMMARY.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# Power System Designer - Integration Summary
|
||||
|
||||
## ✅ **Fixes and Improvements Implemented**
|
||||
|
||||
### **1. Fixed Object Selection**
|
||||
- **Issue**: Selection tool didn't work properly
|
||||
- **Fix**:
|
||||
- Implemented proper `findObjectAt()` function with correct hit detection
|
||||
- Added visual feedback for selected objects (blue glow)
|
||||
- Improved click detection accuracy with 20px radius
|
||||
|
||||
### **2. Added Object Dragging**
|
||||
- **Issue**: Objects couldn't be moved
|
||||
- **Fix**:
|
||||
- Implemented `handleCanvasMouseDown`, `handleCanvasMouseMove`, and `handleCanvasMouseUp` event handlers
|
||||
- Added drag state management with `isDragging` and `dragOffset`
|
||||
- Objects snap to 20px grid while dragging
|
||||
- Visual feedback during dragging operations
|
||||
|
||||
### **3. Fixed Cable Connection Tool**
|
||||
- **Issue**: Cable connection tool didn't work at all
|
||||
- **Fix**:
|
||||
- Proper two-click connection workflow
|
||||
- Visual feedback showing selected connection source (orange highlight)
|
||||
- Connection status indicator overlay
|
||||
- Automatic cable length calculation based on object positions
|
||||
- Prevention of duplicate connections and self-connections
|
||||
- Error messages for invalid connections
|
||||
|
||||
### **4. Enhanced Object Data Structure**
|
||||
- **Issue**: Objects didn't have all necessary information
|
||||
- **Fix**:
|
||||
- Added comprehensive object properties:
|
||||
- `typeDescription` - Custom type descriptions
|
||||
- `load` - Current load vs rated power
|
||||
- `voltage` - Operating voltage
|
||||
- `current` - Current flow (for calculations)
|
||||
- Position tracking (`x`, `y`)
|
||||
- Proper default values for each object type
|
||||
|
||||
### **5. Improved Cable Data Structure**
|
||||
- **Enhanced Cable Properties**:
|
||||
- `resistance` - Electrical resistance based on cable type/size
|
||||
- Better cable type handling (underground vs overhead)
|
||||
- Automatic resistance calculation
|
||||
- Improved cable length calculation
|
||||
|
||||
### **6. Enhanced User Interface**
|
||||
|
||||
#### **Object Properties Panel**:
|
||||
- Object type indicator with ID
|
||||
- Position display (X, Y coordinates)
|
||||
- Type-specific property fields
|
||||
- Enhanced transformer properties (primary/secondary voltage, power rating)
|
||||
- Load vs power rating for distribution points
|
||||
- Custom type descriptions
|
||||
|
||||
#### **Cable Properties Panel**:
|
||||
- Connection source/destination display
|
||||
- Electrical parameters (resistance, installation type)
|
||||
- Cable type selection with proper underground/overhead distinction
|
||||
- Cross-section selection with standard sizes
|
||||
- Delete cable functionality
|
||||
|
||||
#### **Visual Improvements**:
|
||||
- Better object highlighting (selected vs connection source)
|
||||
- Improved cable rendering with proper colors:
|
||||
- Purple: Underground cables (YAKY, NA2XY-J)
|
||||
- Orange: Overhead cables (AL, AsXSn)
|
||||
- Dashed lines for overhead cables
|
||||
- Cable labels with background for better readability
|
||||
- Connection status indicator overlay
|
||||
|
||||
### **7. Added Interaction Features**
|
||||
|
||||
#### **Mouse Controls**:
|
||||
- **Left Click**: Select/place objects
|
||||
- **Drag**: Move selected objects (with grid snapping)
|
||||
- **Connect Mode**: Two-click cable connection workflow
|
||||
|
||||
#### **Keyboard Shortcuts**:
|
||||
- **Delete/Backspace**: Delete selected object or cable
|
||||
- **Escape**: Clear selection and return to select mode
|
||||
|
||||
#### **Object Management**:
|
||||
- Delete objects (with automatic cable cleanup)
|
||||
- Delete cables individually
|
||||
- Proper state management for selections
|
||||
|
||||
### **8. Error Handling and Validation**
|
||||
- Transformer limit enforcement (only one allowed)
|
||||
- Duplicate connection prevention
|
||||
- Self-connection prevention
|
||||
- Error messages with clear descriptions
|
||||
- Input validation for numeric fields
|
||||
|
||||
### **9. Technical Improvements**
|
||||
|
||||
#### **Event System**:
|
||||
- Proper mouse event handling with coordinate transformation
|
||||
- Grid snapping for precise placement
|
||||
- Drag offset calculation for smooth object movement
|
||||
|
||||
#### **State Management**:
|
||||
- Clean separation of object and cable selections
|
||||
- Proper state updates with React patterns
|
||||
- Automatic re-rendering on state changes
|
||||
|
||||
#### **Performance**:
|
||||
- Efficient hit detection algorithms
|
||||
- Optimized canvas rendering
|
||||
- Proper event listener cleanup
|
||||
|
||||
## **🎯 Key Features Now Working**
|
||||
|
||||
1. **✅ Object Selection**: Click objects to select and edit properties
|
||||
2. **✅ Object Movement**: Drag objects to new positions with grid snapping
|
||||
3. **✅ Cable Connections**: Two-click workflow to connect objects
|
||||
4. **✅ Property Editing**: Comprehensive property panels for all object types
|
||||
5. **✅ Visual Feedback**: Clear indication of selected items and connection states
|
||||
6. **✅ Delete Functions**: Remove objects and cables with keyboard shortcuts
|
||||
7. **✅ Data Persistence**: Export/import with complete object data
|
||||
8. **✅ Electrical Calculations**: Ready for voltage drop analysis
|
||||
|
||||
## **🔧 Usage Instructions**
|
||||
|
||||
### **Adding Objects**:
|
||||
1. Click object type button in toolbar (Transformer, Box, Pole, End Connection)
|
||||
2. Click on canvas to place object
|
||||
3. Object automatically snaps to grid
|
||||
|
||||
### **Selecting and Moving**:
|
||||
1. Click "Wybierz" (Select) tool
|
||||
2. Click object to select (blue highlight appears)
|
||||
3. Drag selected object to move (snaps to grid)
|
||||
|
||||
### **Connecting with Cables**:
|
||||
1. Click "Połącz kablem" (Connect with Cable) tool
|
||||
2. Click first object (orange highlight appears)
|
||||
3. Click second object to create cable connection
|
||||
4. Cable appears with automatic length calculation
|
||||
|
||||
### **Editing Properties**:
|
||||
1. Select object or cable
|
||||
2. Use properties panel on right to edit all parameters
|
||||
3. Changes are saved automatically
|
||||
|
||||
### **Deleting Items**:
|
||||
1. Select object or cable
|
||||
2. Press Delete/Backspace key, OR
|
||||
3. Use "Usuń" (Delete) button in properties panel
|
||||
|
||||
The power system designer is now fully functional with professional-grade features for electrical system design and analysis!
|
||||
132
UI_REDESIGN_PROPOSAL.md
Normal file
132
UI_REDESIGN_PROPOSAL.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Wastpol UI Redesign Proposal
|
||||
|
||||
## Overview
|
||||
This UI redesign modernizes the Wastpol electrical engineering application with a contemporary, professional interface while maintaining all existing functionality.
|
||||
|
||||
## Key Improvements
|
||||
|
||||
### 🎨 **Visual Design**
|
||||
- **Modern Color Palette**: Professional blue-based theme with proper contrast ratios
|
||||
- **Typography**: Inter font family for improved readability
|
||||
- **Consistent Spacing**: Systematic spacing scale using Tailwind CSS
|
||||
- **Enhanced Shadows**: Subtle depth with layered shadow system
|
||||
- **Better Visual Hierarchy**: Clear information architecture with proper heading levels
|
||||
|
||||
### 📱 **Responsive Layout**
|
||||
- **Mobile-First Design**: Optimized for all screen sizes
|
||||
- **Sidebar Navigation**: Collapsible sidebar for desktop, drawer for mobile
|
||||
- **Adaptive Grid**: Responsive card layouts that adjust to screen size
|
||||
- **Touch-Friendly**: Proper touch targets for mobile devices
|
||||
|
||||
### 🎯 **User Experience**
|
||||
- **Simplified Navigation**: Clear, icon-based navigation with active states
|
||||
- **Progress Indicators**: Loading states and progress feedback
|
||||
- **Better Form Design**: Improved input styling with proper validation states
|
||||
- **Interactive Elements**: Hover effects and smooth transitions
|
||||
- **Card-Based Layout**: Organized content in digestible sections
|
||||
|
||||
### ⚡ **Performance & Accessibility**
|
||||
- **Semantic HTML**: Proper heading structure and ARIA labels
|
||||
- **Keyboard Navigation**: Full keyboard accessibility
|
||||
- **Color Contrast**: WCAG 2.1 AA compliant color combinations
|
||||
- **Focus Management**: Clear focus indicators
|
||||
- **Screen Reader Support**: Proper labeling and descriptions
|
||||
|
||||
## New Components
|
||||
|
||||
### Layout System
|
||||
- **Layout.js**: Unified layout component with responsive sidebar
|
||||
- **components.js**: Reusable UI components (Card, Button, Input, etc.)
|
||||
|
||||
### Key Features
|
||||
|
||||
#### 1. Unified Navigation
|
||||
- Logo and branding in header
|
||||
- Clear section navigation (Przekrój, Siatka, Uziomy)
|
||||
- User authentication status
|
||||
- Responsive mobile menu
|
||||
|
||||
#### 2. Enhanced Cards
|
||||
- Clean card design with headers and content sections
|
||||
- Proper spacing and typography
|
||||
- Action buttons with loading states
|
||||
- Status indicators and badges
|
||||
|
||||
#### 3. Improved Forms
|
||||
- Better input styling with floating labels
|
||||
- Proper validation states
|
||||
- Radio groups and select inputs
|
||||
- Date picker integration
|
||||
|
||||
#### 4. Better Feedback
|
||||
- Loading spinners for async operations
|
||||
- Success/error alerts with proper styling
|
||||
- Progress indicators
|
||||
- Toast notifications
|
||||
|
||||
## Technical Implementation
|
||||
|
||||
### Dependencies Added
|
||||
- `@heroicons/react`: Modern icon system
|
||||
- `@tailwindcss/forms`: Enhanced form styling
|
||||
|
||||
### File Structure
|
||||
```
|
||||
components/
|
||||
ui/
|
||||
Layout.js # Main layout wrapper
|
||||
components.js # Reusable UI components
|
||||
templates/
|
||||
generator_new.js # Updated terrain profile generator
|
||||
manual_new.js # Updated manual input component
|
||||
pages/
|
||||
cross_new.js # Updated grid generator page
|
||||
uziomy_new.js # Updated grounding calculator page
|
||||
```
|
||||
|
||||
### Color System
|
||||
- **Primary**: Blue (#3B82F6) for main actions
|
||||
- **Secondary**: Gray tones for secondary elements
|
||||
- **Success**: Green for positive states
|
||||
- **Warning**: Yellow for attention states
|
||||
- **Error**: Red for error states
|
||||
|
||||
### Typography Scale
|
||||
- **Headings**: Inter font with proper weight hierarchy
|
||||
- **Body**: 14px base with good line height
|
||||
- **Code**: Monospace for technical data input
|
||||
|
||||
## Benefits
|
||||
|
||||
### For Users
|
||||
- **Easier Navigation**: Intuitive interface reduces learning curve
|
||||
- **Better Mobile Experience**: Responsive design works on all devices
|
||||
- **Faster Workflows**: Streamlined forms and clear action buttons
|
||||
- **Professional Appearance**: Modern design builds trust and credibility
|
||||
|
||||
### For Developers
|
||||
- **Component Reusability**: Consistent design system
|
||||
- **Maintainable Code**: Clean component architecture
|
||||
- **Modern Stack**: Latest design practices and tools
|
||||
- **Scalable System**: Easy to extend and modify
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### Backward Compatibility
|
||||
- Original files preserved (can switch back if needed)
|
||||
- New files use `_new.js` suffix for testing
|
||||
- API endpoints remain unchanged
|
||||
- All functionality preserved
|
||||
|
||||
### Migration Path
|
||||
1. Test new components in development
|
||||
2. Gradually replace old components
|
||||
3. Update remaining templates to match new design
|
||||
4. Remove old components once migration complete
|
||||
|
||||
### Browser Support
|
||||
- Modern browsers (Chrome, Firefox, Safari, Edge)
|
||||
- Mobile browsers (iOS Safari, Chrome Mobile)
|
||||
- Progressive enhancement for older browsers
|
||||
|
||||
This redesign transforms Wastpol into a modern, professional application that enhances user productivity while maintaining all existing engineering capabilities.
|
||||
39
archive/README.md
Normal file
39
archive/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Archive - Old UI Components
|
||||
|
||||
This folder contains the original versions of components and pages that were replaced during the UI redesign.
|
||||
|
||||
## Archived Files
|
||||
|
||||
### Pages
|
||||
- `cross_old.js` - Original cross-section generator page
|
||||
- `uziomy_old.js` - Original grounding calculator page
|
||||
|
||||
### Components
|
||||
- `templates/generator_old.js` - Original profile generator component
|
||||
- `templates/manual_old.js` - Original manual input component
|
||||
|
||||
## What Changed
|
||||
|
||||
The archived files used the old UI system with:
|
||||
- Evergreen UI components
|
||||
- Older styling approach
|
||||
- Less responsive design
|
||||
- Basic user interface
|
||||
|
||||
The new versions (now default) feature:
|
||||
- Modern Tailwind CSS design system
|
||||
- Responsive layout with sidebar navigation
|
||||
- Custom UI components (`components/ui/`)
|
||||
- Improved user experience
|
||||
- Heroicons v2 integration
|
||||
- Better mobile support
|
||||
|
||||
## Restoration
|
||||
|
||||
If you need to restore an old version, you can:
|
||||
1. Copy the desired file from this archive
|
||||
2. Rename it to remove the `_old` suffix
|
||||
3. Replace the current version in the main project
|
||||
|
||||
## Date Archived
|
||||
Archived on: July 1, 2025
|
||||
231
archive/components/templates/generator_old.js
Normal file
231
archive/components/templates/generator_old.js
Normal file
@@ -0,0 +1,231 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import {
|
||||
Pane,
|
||||
TextInputField,
|
||||
TextareaField,
|
||||
Button,
|
||||
BuildIcon,
|
||||
toaster,
|
||||
Alert,
|
||||
RadioGroup,
|
||||
} from "evergreen-ui";
|
||||
import axios from "axios";
|
||||
|
||||
import Footer from "./footer";
|
||||
|
||||
export default function Generator() {
|
||||
const [profil, setProfil] = useState();
|
||||
const [args, setArgs] = useState({
|
||||
scale: 200,
|
||||
elementOne: 0,
|
||||
elementTwo: 0,
|
||||
});
|
||||
const [ElementOneOptions] = useState([
|
||||
{ label: "Nic", value: "0" },
|
||||
{ label: "Słup", value: "1" },
|
||||
{ label: "Dom", value: "2" },
|
||||
]);
|
||||
const [ElementTwoOptions] = useState([
|
||||
{ label: "Nic", value: "0" },
|
||||
{ label: "Słup", value: "1" },
|
||||
{ label: "Dom", value: "2" },
|
||||
]);
|
||||
const { query } = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (query.external == "tru") {
|
||||
axios
|
||||
.post("/api/readtext", {
|
||||
id: query.id,
|
||||
})
|
||||
.then(function (response) {
|
||||
setProfil(response.data.data);
|
||||
document.getElementById("textarea-1").value = response.data.data;
|
||||
console.log(response.data.data);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getPath = (e, path) => {
|
||||
let newLines = [];
|
||||
|
||||
for (let line of path.split("\n")) {
|
||||
if (line[0] == "P") continue;
|
||||
if (line[0] == "S") continue;
|
||||
if (line[0] == "X") continue;
|
||||
|
||||
newLines.push(line);
|
||||
}
|
||||
let xs = [];
|
||||
let ys = [];
|
||||
let zs = [];
|
||||
let toti = 0;
|
||||
let al = 0;
|
||||
for (let line of newLines) {
|
||||
al += 1;
|
||||
let theLine = line.split(",");
|
||||
//console.log(theLine)
|
||||
if (
|
||||
xs &&
|
||||
parseFloat(theLine[0]) == xs[-1] &&
|
||||
parseFloat(theLine[1]) == ys[-1]
|
||||
)
|
||||
continue;
|
||||
|
||||
if (al > 2) {
|
||||
let dist =
|
||||
((xs[xs.length - 1] - xs[xs.length - 2]) ** 2 +
|
||||
(ys[ys.length - 1] - ys[ys.length - 2]) ** 2) **
|
||||
0.5;
|
||||
toti += Math.round(dist);
|
||||
}
|
||||
xs.push(parseFloat(theLine[0]));
|
||||
ys.push(parseFloat(theLine[1]));
|
||||
zs.push(parseFloat(theLine[2]));
|
||||
}
|
||||
let prevLine = ["0", "0", "0"];
|
||||
let scaleFactor = 100000 / 2000;
|
||||
const canvas = document.getElementById("canvas");
|
||||
|
||||
if (!canvas.getContext) {
|
||||
console.log("canvas err");
|
||||
toaster.warning("canvas err");
|
||||
return;
|
||||
}
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.clearRect(0, 0, 700, 700);
|
||||
|
||||
// set line stroke and line width
|
||||
ctx.strokeStyle = "red";
|
||||
ctx.lineWidth = 2;
|
||||
let totalH = Math.max(...zs);
|
||||
let minH = Math.min(...zs);
|
||||
for (let line = 0; line < xs.length - 1; line++) {
|
||||
let theLine = [xs[line], ys[line], zs[line]];
|
||||
let x = parseFloat(theLine[0]) - parseFloat(prevLine[0]);
|
||||
let y = parseFloat(theLine[1]) - parseFloat(prevLine[1]);
|
||||
let x1 = line * scaleFactor;
|
||||
let y1 = zs[line];
|
||||
let x2 = (line + 1) * scaleFactor;
|
||||
let y2 = zs[line + 1];
|
||||
//console.log(x1, y1, x2, y2);
|
||||
let b0 = 0;
|
||||
let b1 = 500;
|
||||
let z1 = b0 + (b1 - b0) * ((y1 - totalH) / (minH - totalH));
|
||||
let z2 = b0 + (b1 - b0) * ((y2 - totalH) / (minH - totalH));
|
||||
|
||||
//b0 + (b1 - b0) * ((x1-0)/(toti*scaleFactor-0));
|
||||
let x12 = b0 + (b1 - b0) * ((x1 - 0) / (toti * scaleFactor));
|
||||
let x22 = b0 + (b1 - b0) * ((x2 - 0) / (toti * scaleFactor));
|
||||
//console.log(x12);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x12, z1);
|
||||
ctx.lineTo(x22, z2);
|
||||
ctx.stroke();
|
||||
}
|
||||
};
|
||||
|
||||
const parsePreview = (e) => {
|
||||
// console.log(dxf);
|
||||
};
|
||||
|
||||
const py = (e, profil, args) => {
|
||||
e.preventDefault();
|
||||
|
||||
axios
|
||||
.post("/api/spawn", {
|
||||
profil: profil,
|
||||
arguments: args,
|
||||
})
|
||||
.then(function (response) {
|
||||
console.log(response);
|
||||
if (response.data.toString().startsWith("Py Error")) {
|
||||
toaster.danger(response.data);
|
||||
return;
|
||||
}
|
||||
toaster.warning(response.data.data);
|
||||
document.getElementById("down").download =
|
||||
response.data.filename.toString() + ".dxf";
|
||||
document.getElementById("down").click();
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div className="flex xl:flex-row flex-col">
|
||||
<div className="flex flex-col">
|
||||
<Pane width={480}>
|
||||
<TextareaField
|
||||
id="textarea-1"
|
||||
label="Dane z Geoportalu:"
|
||||
placeholder="Próbkowanie: 1 ..."
|
||||
onChange={(e) => {
|
||||
//console.log(e.target.value);
|
||||
setProfil(e.target.value);
|
||||
parsePreview(e);
|
||||
getPath(e, e.target.value);
|
||||
}}
|
||||
/>
|
||||
|
||||
<TextInputField
|
||||
label="Skala:"
|
||||
placeholder="200"
|
||||
width={100}
|
||||
onChange={(e) => {
|
||||
console.log(e.target.value);
|
||||
setArgs({ ...args, scale: e.target.value });
|
||||
}}
|
||||
/>
|
||||
<RadioGroup
|
||||
label="Lewa"
|
||||
value={args.elementOne}
|
||||
options={ElementOneOptions}
|
||||
onChange={(event) => {
|
||||
setArgs({ ...args, elementOne: event.target.value });
|
||||
}}
|
||||
/>
|
||||
<RadioGroup
|
||||
label="Prawa"
|
||||
value={args.elementTwo}
|
||||
options={ElementTwoOptions}
|
||||
onChange={(event) => {
|
||||
setArgs({ ...args, elementTwo: event.target.value });
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
marginY={8}
|
||||
marginRight={12}
|
||||
appearance="default"
|
||||
iconAfter={BuildIcon}
|
||||
onClick={(e) => {
|
||||
if (document.getElementById("textarea-1").value == "") {
|
||||
toaster.danger("Pole danych nie może być puste");
|
||||
} else if (
|
||||
!document
|
||||
.getElementById("textarea-1")
|
||||
.value.startsWith("Próbkowanie")
|
||||
) {
|
||||
toaster.danger("Błędne dane");
|
||||
} else {
|
||||
py(e, profil, args);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Generuj
|
||||
</Button>
|
||||
</Pane>
|
||||
|
||||
<a href="test.dxf" download="test.dxf" id="down">
|
||||
{" "}
|
||||
</a>
|
||||
</div>
|
||||
<Pane className="ml-8 shadow-md rounded-md">
|
||||
<canvas id="canvas" height="500" width="700"></canvas>
|
||||
</Pane>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
157
archive/components/templates/manual_old.js
Normal file
157
archive/components/templates/manual_old.js
Normal file
@@ -0,0 +1,157 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
Pane,
|
||||
TextInputField,
|
||||
TextareaField,
|
||||
Button,
|
||||
toaster,
|
||||
Alert,
|
||||
TrashIcon,
|
||||
Icon,
|
||||
} from "evergreen-ui";
|
||||
import axios from "axios";
|
||||
import Footer from "./footer";
|
||||
import Generator from "./generator";
|
||||
|
||||
export default function Manual() {
|
||||
const [points, setPoints] = useState([{ id: 0, len: 0, height: 0 }]);
|
||||
const [args, setArgs] = useState({ scale: 200 });
|
||||
|
||||
//useEffect(() => {
|
||||
//do something here
|
||||
// }, [points]);
|
||||
|
||||
const reIndex = () => {
|
||||
let newId = 0;
|
||||
let newPoints = [];
|
||||
for (let point of points) {
|
||||
point.id = newId;
|
||||
newPoints.push(point);
|
||||
newId += 1;
|
||||
}
|
||||
setPoints([...newPoints]);
|
||||
};
|
||||
|
||||
const generation = (e) => {
|
||||
let pointString = "Próbkowanie: 1\nSegment 0: w dół\nX: Y, Z";
|
||||
|
||||
for (let point of points) {
|
||||
pointString += "\n";
|
||||
pointString += Number(point.len) * 1.0;
|
||||
pointString += ", 1.0, ";
|
||||
pointString += point.height;
|
||||
}
|
||||
|
||||
console.log(pointString);
|
||||
py(e, pointString, args);
|
||||
};
|
||||
|
||||
const py = (e, profil, args) => {
|
||||
e.preventDefault();
|
||||
|
||||
axios
|
||||
.post("/api/spawn", {
|
||||
profil: profil,
|
||||
arguments: args,
|
||||
})
|
||||
.then(function (response) {
|
||||
console.log(response);
|
||||
if (response.data.toString().startsWith("Py Error")) {
|
||||
toaster.danger(response.data);
|
||||
return;
|
||||
}
|
||||
toaster.warning(response.data.data);
|
||||
document.getElementById("down").download =
|
||||
response.data.filename.toString() + ".dxf";
|
||||
document.getElementById("down").click();
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex xl:flex-row flex-col">
|
||||
<div className="flex flex-col">
|
||||
<Pane width={480}>
|
||||
{points.map((point, index) => (
|
||||
<Pane className="flex flex-row items-center">
|
||||
<TextInputField
|
||||
id={`len-${index}`}
|
||||
placeholder="0 m"
|
||||
onChange={(e) => {
|
||||
console.log(e.target.value);
|
||||
let newPoints = points;
|
||||
newPoints[index].len = e.target.value;
|
||||
setPoints([...newPoints]);
|
||||
}}
|
||||
value={point.len}
|
||||
></TextInputField>
|
||||
<TextInputField
|
||||
id={`h-${index}`}
|
||||
placeholder="0 m n.p.m."
|
||||
onChange={(e) => {
|
||||
console.log(e.target.value);
|
||||
let newPoints = points;
|
||||
newPoints[index].height = e.target.value;
|
||||
setPoints([...newPoints]);
|
||||
}}
|
||||
value={point.height}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key == "Enter") {
|
||||
setPoints([
|
||||
...points,
|
||||
{
|
||||
id: points.length,
|
||||
len: Number(points[points.length - 1].len) + 1,
|
||||
height: 0,
|
||||
},
|
||||
]);
|
||||
|
||||
let nextDiv = e.target.parentNode.parentNode.nextSibling;
|
||||
nextDiv.childNodes[1].childNodes[1].focus()
|
||||
nextDiv.childNodes[1].childNodes[1].setSelectionRange(0, 1)
|
||||
}
|
||||
}}
|
||||
></TextInputField>
|
||||
<Button
|
||||
className="mb-5"
|
||||
appearance="minimal"
|
||||
onClick={() => {
|
||||
let newPoints = points;
|
||||
newPoints.splice(index, 1);
|
||||
setPoints([...newPoints]);
|
||||
reIndex();
|
||||
}}
|
||||
>
|
||||
<Icon icon={TrashIcon} color="danger"></Icon>
|
||||
</Button>
|
||||
</Pane>
|
||||
))}
|
||||
</Pane>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setPoints([
|
||||
...points,
|
||||
{
|
||||
id: points.length,
|
||||
len: Number(points[points.length - 1].len) + 1,
|
||||
height: 0,
|
||||
},
|
||||
]);
|
||||
}}
|
||||
>
|
||||
Dodaj
|
||||
</Button>
|
||||
<Button
|
||||
appearance="primary"
|
||||
onClick={(e) => {
|
||||
generation(e);
|
||||
}}
|
||||
>
|
||||
Generuj
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
178
archive/pages/cross_old.js
Normal file
178
archive/pages/cross_old.js
Normal file
@@ -0,0 +1,178 @@
|
||||
import { useState, useCallback } from "react";
|
||||
import { useSession, signIn } from "next-auth/react";
|
||||
import Layout from "../components/ui/Layout";
|
||||
import { Card, CardHeader, CardContent, CardTitle, CardDescription, Button, Alert } from "../components/ui/components";
|
||||
import {
|
||||
FileUploader,
|
||||
FileCard,
|
||||
} from "evergreen-ui";
|
||||
import { CloudArrowUpIcon as CloudUploadIcon, ArrowDownTrayIcon as DownloadIcon, Squares2X2Icon as GridIcon } from '@heroicons/react/24/outline';
|
||||
import axios from "axios";
|
||||
|
||||
export default function Cross() {
|
||||
const { data: session } = useSession();
|
||||
|
||||
const [fileData, setFileData] = useState(null);
|
||||
const handleDownload = () => {
|
||||
console.log("down");
|
||||
if (fileData) {
|
||||
console.log("load");
|
||||
// const link = document.createElement("a");
|
||||
// link.href = `data:application/octet-stream;base64,${fileData}`;
|
||||
// link.download = "plik.dxf";
|
||||
// link.click();
|
||||
document.getElementById("down").download = fileData.filename;
|
||||
console.log(fileData.filename)
|
||||
|
||||
document.getElementById("down").href = "cross.dxf";
|
||||
console.log("cross.dxf")
|
||||
|
||||
document.getElementById("down").click();
|
||||
}
|
||||
};
|
||||
|
||||
const [files, setFiles] = useState([]);
|
||||
const [fileRejections, setFileRejections] = useState([]);
|
||||
const handleChange = useCallback((files) => setFiles([files[0]]), []);
|
||||
const handleRejected = useCallback(
|
||||
(fileRejections) => setFileRejections([fileRejections[0]]),
|
||||
[]
|
||||
);
|
||||
const handleRemove = useCallback(() => {
|
||||
setFiles([]);
|
||||
setFileRejections([]);
|
||||
}, []);
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
let file = files[0];
|
||||
|
||||
if (!file) {
|
||||
// Handle error if no file is selected
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
||||
axios
|
||||
.post("/api/upload", formData, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
console.log(response.data);
|
||||
if (response.data.toString().startsWith("Py Error")) {
|
||||
toaster.danger(response.data);
|
||||
return;
|
||||
}
|
||||
toaster.warning(response.statusText);
|
||||
console.log(response.data)
|
||||
setFileData(response.data);
|
||||
document.getElementById("download").disabled = false;
|
||||
|
||||
// document.getElementById("down").download =
|
||||
// response.data.filename.toString().split(".")[0].split("-")[1]+"_s.dxf";
|
||||
// document.getElementById("down").href =
|
||||
// "uploads/"+response.data.filename.toString().split(".")[0]+"_s.dxf";
|
||||
// document.getElementById("down").click();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
};
|
||||
|
||||
if (session) {
|
||||
return (
|
||||
<div className="">
|
||||
<Head>
|
||||
<title>Wastpol</title>
|
||||
</Head>
|
||||
|
||||
<div className="flex md:flex-row flex-col justify-between px-8 mt-2">
|
||||
<Nav />
|
||||
<UserTop session={session} />
|
||||
</div>
|
||||
|
||||
<main className="flex flex-1 flex-col items-center">
|
||||
<FileUploader
|
||||
label="Prześlij plik"
|
||||
description="Możesz dodać 1 plik, max 50 MB."
|
||||
maxSizeInBytes={50 * 1024 ** 2}
|
||||
maxFiles={1}
|
||||
onChange={handleChange}
|
||||
onRejected={handleRejected}
|
||||
renderFile={(file) => {
|
||||
const { name, size, type } = file;
|
||||
const fileRejection = fileRejections.find(
|
||||
(fileRejection) => fileRejection.file === file
|
||||
);
|
||||
const { message } = fileRejection || {};
|
||||
return (
|
||||
<FileCard
|
||||
key={name}
|
||||
isInvalid={fileRejection != null}
|
||||
name={name}
|
||||
onRemove={handleRemove}
|
||||
sizeInBytes={size}
|
||||
type={type}
|
||||
validationMessage={message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
values={files}
|
||||
/>
|
||||
|
||||
<Button
|
||||
marginY={8}
|
||||
marginRight={12}
|
||||
appearance="default"
|
||||
iconAfter={BuildIcon}
|
||||
onClick={(e) => {
|
||||
console.log("louding begins");
|
||||
console.log(files);
|
||||
document.getElementById("download").disabled = false;
|
||||
handleSubmit(e);
|
||||
}}
|
||||
>
|
||||
Dodaj siatkę
|
||||
</Button>
|
||||
<Button
|
||||
id="download"
|
||||
marginY={8}
|
||||
marginRight={12}
|
||||
appearance="default"
|
||||
iconAfter={BuildIcon}
|
||||
// disabled={true}
|
||||
onClick={handleDownload}
|
||||
>
|
||||
Pobierz
|
||||
</Button>
|
||||
<a href="the.dxf" download="the.dxf" id="down">
|
||||
{" "}
|
||||
</a>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="grid place-items-center h-screen">
|
||||
<Head>
|
||||
<title>Wastpol</title>
|
||||
</Head>
|
||||
<div className="flex flex-col justify-center">
|
||||
<h2 className="p-2">Nie zalogowano</h2>
|
||||
<br></br>
|
||||
<Button
|
||||
onClick={() => signIn()}
|
||||
appearance="primary"
|
||||
// className="p-2 bg-slate-200 rounded-md"
|
||||
>
|
||||
Zaloguj
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
551
archive/pages/uziomy_old.js
Normal file
551
archive/pages/uziomy_old.js
Normal file
@@ -0,0 +1,551 @@
|
||||
import Head from "next/head";
|
||||
import styles from "../styles/Home.module.css";
|
||||
import Nav from "../components/templates/nav";
|
||||
import UserTop from "../components/templates/userTop";
|
||||
import { useSession, signIn, signOut } from "next-auth/react";
|
||||
import { useState, useCallback } from "react";
|
||||
import {
|
||||
Pane,
|
||||
TextInputField,
|
||||
TextareaField,
|
||||
Button,
|
||||
BuildIcon,
|
||||
toaster,
|
||||
Alert,
|
||||
FileUploader,
|
||||
FilePicker,
|
||||
FileCard,
|
||||
RadioGroup,
|
||||
Autocomplete,
|
||||
} from "evergreen-ui";
|
||||
|
||||
import PizZip from "pizzip";
|
||||
import Docxtemplater from "docxtemplater";
|
||||
import DatePicker from "react-datepicker";
|
||||
import { registerLocale, setDefaultLocale } from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import pl from "date-fns/locale/pl";
|
||||
registerLocale("pl", pl);
|
||||
|
||||
export default function Rezy() {
|
||||
const { data: session } = useSession();
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
|
||||
const [ground, setGround] = useState({
|
||||
wet_coef: 0,
|
||||
resistivity: 0,
|
||||
resistance: 0,
|
||||
measure_dist: 0,
|
||||
rod_len: 0,
|
||||
rod_num: 0,
|
||||
rod_coef: 0,
|
||||
hor_len: 0,
|
||||
result_v: 0,
|
||||
result_h: 0,
|
||||
result: 0,
|
||||
wszrg_h: 0,
|
||||
wszrg_v: 0,
|
||||
wanted: 0,
|
||||
date: undefined,
|
||||
no: 0,
|
||||
pr_title: "Budowa przyłącza kablowego nN",
|
||||
in_city: undefined,
|
||||
commune: undefined,
|
||||
all_parcels: undefined,
|
||||
target_parcel: undefined,
|
||||
geo_data: undefined,
|
||||
object: "Przyłącz kablowy nN",
|
||||
objValue1: "proj.",
|
||||
objName: undefined,
|
||||
});
|
||||
|
||||
// Function to go to the next step
|
||||
const goToNextStep = () => {
|
||||
if (currentStep < 3) {
|
||||
setCurrentStep(currentStep + 1);
|
||||
}
|
||||
};
|
||||
|
||||
// Function to go to the previous step
|
||||
const goToPreviousStep = () => {
|
||||
if (currentStep > 1) {
|
||||
setCurrentStep(currentStep - 1);
|
||||
}
|
||||
};
|
||||
|
||||
const [date, setDate] = useState(null);
|
||||
const [calDate, setCalDate] = useState(null);
|
||||
|
||||
const [options] = useState([
|
||||
{ label: "5 Ω", value: "5" },
|
||||
{ label: "10 Ω", value: "10" },
|
||||
{ label: "15 Ω", value: "15" },
|
||||
{ label: "30 Ω", value: "30" },
|
||||
]);
|
||||
const [neededValue, setNeededValue] = useState("5");
|
||||
|
||||
const [resHValue, setResHValue] = useState("88");
|
||||
const [resVValue, setResVValue] = useState("89");
|
||||
|
||||
const [objOptions1] = useState([
|
||||
{ label: "proj.", value: "proj." },
|
||||
{ label: "istn.", value: "istn." },
|
||||
]);
|
||||
const [objValue1, setObjValue1] = useState("proj.");
|
||||
|
||||
function getRandomInt(min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
function parseDate(dateString) {
|
||||
console.log(dateString);
|
||||
const parts = dateString.split(".");
|
||||
const day = parseInt(parts[0], 10);
|
||||
const month = parseInt(parts[1], 10) - 1; // Months are 0-indexed in JavaScript Dates
|
||||
const year = parseInt(parts[2], 10);
|
||||
return new Date(year, month, day);
|
||||
}
|
||||
|
||||
function getGrounding(wanted, wszrg_h, wszrg_v, date) {
|
||||
const dateObject = parseDate(date);
|
||||
const month = dateObject.getMonth() + 1; // JavaScript months are 0-indexed
|
||||
|
||||
const wet_coef = month >= 6 && month <= 9 ? 1.2 : 1.6;
|
||||
const rod_len = wanted === 30 ? 2 : 3;
|
||||
|
||||
const resistivity_h = wszrg_h / wet_coef;
|
||||
const measure_dist_h = 1;
|
||||
const resistance_h = resistivity_h / (2 * Math.PI * measure_dist_h);
|
||||
|
||||
const resistivity_v = wszrg_v / wet_coef;
|
||||
const measure_dist_v = 1 + rod_len;
|
||||
const resistance_v = resistivity_v / (2 * Math.PI * measure_dist_v);
|
||||
|
||||
const result_v =
|
||||
(wszrg_v / (2 * Math.PI * rod_len)) *
|
||||
(Math.log((8 * rod_len) / 0.016) - 1);
|
||||
|
||||
let rod_num = 2; //minimum 2 rods
|
||||
|
||||
let hor_len = 1 + (rod_num - 1) * rod_len * 2;
|
||||
|
||||
let result_h =
|
||||
(wszrg_h / (2 * Math.PI * hor_len)) *
|
||||
Math.log((hor_len * hor_len) / (1 * 0.0191));
|
||||
|
||||
let rod_coef =
|
||||
Math.pow(rod_num, 4) * 0.00002 -
|
||||
Math.pow(rod_num, 3) * 0.0009 +
|
||||
Math.pow(rod_num, 2) * 0.0137 -
|
||||
rod_num * 0.0981 +
|
||||
1.0468;
|
||||
|
||||
let result =
|
||||
(result_v * result_h) /
|
||||
(result_v * rod_coef + rod_num * result_h * rod_coef);
|
||||
|
||||
while (result > wanted) {
|
||||
rod_num += 1;
|
||||
|
||||
hor_len = 1 + (rod_num - 1) * rod_len * 2;
|
||||
|
||||
result_h =
|
||||
(wszrg_h / (2 * Math.PI * hor_len)) *
|
||||
Math.log((hor_len * hor_len) / (1 * 0.0191));
|
||||
|
||||
rod_coef =
|
||||
Math.pow(rod_num, 4) * 0.00002 -
|
||||
Math.pow(rod_num, 3) * 0.0009 +
|
||||
Math.pow(rod_num, 2) * 0.0137 -
|
||||
rod_num * 0.0981 +
|
||||
1.0468;
|
||||
|
||||
result =
|
||||
(result_v * result_h) /
|
||||
(result_v * rod_coef + rod_num * result_h * rod_coef);
|
||||
|
||||
console.log(result, rod_num);
|
||||
}
|
||||
|
||||
console.log(result, rod_num);
|
||||
return {
|
||||
wet_coef: wet_coef,
|
||||
resistivity_h: resistivity_h.toFixed(2),
|
||||
resistance_h: resistance_h.toFixed(2),
|
||||
measure_dist_h: measure_dist_h,
|
||||
resistivity_v: resistivity_v.toFixed(2),
|
||||
resistance_v: resistance_v.toFixed(2),
|
||||
measure_dist_v: measure_dist_v,
|
||||
rod_len: rod_len,
|
||||
rod_num: rod_num,
|
||||
rod_coef: rod_coef.toFixed(2),
|
||||
hor_len: hor_len,
|
||||
result_v: result_v.toFixed(2),
|
||||
result_h: result_h.toFixed(2),
|
||||
result: result.toFixed(2),
|
||||
wszrg_h: wszrg_h,
|
||||
wszrg_v: wszrg_v,
|
||||
wanted: neededValue,
|
||||
};
|
||||
}
|
||||
|
||||
const generateDocument = async (ground) => {
|
||||
const data = {
|
||||
...ground,
|
||||
resisted_object: ground.objValue1 + " " + ground.objName,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/generateDocx", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error: ${response.status}`);
|
||||
}
|
||||
|
||||
// Convert the response to a blob and download it
|
||||
const blob = await response.blob();
|
||||
const downloadUrl = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = downloadUrl;
|
||||
link.download = "opis.docx";
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
} catch (error) {
|
||||
console.error("Failed to generate document:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const generateDxf = async () => {
|
||||
// Data that you want to send to the backend
|
||||
var dateParts = ground.date.split(".");
|
||||
|
||||
// Extract day, month, and year
|
||||
var day = parseInt(dateParts[0], 10);
|
||||
var month = parseInt(dateParts[1], 10);
|
||||
var year = parseInt(dateParts[2], 10);
|
||||
|
||||
// Format the result
|
||||
var formattedDate = month.toString().padStart(2, "0") + "." + year;
|
||||
const inputData = {
|
||||
args: [
|
||||
ground.objValue1 + ground.objName,
|
||||
ground.pr_title,
|
||||
ground.object,
|
||||
ground.in_city +
|
||||
", " +
|
||||
ground.commune +
|
||||
", dz. nr " +
|
||||
ground.all_parcels,
|
||||
formattedDate,
|
||||
ground.hor_len,
|
||||
ground.rod_len,
|
||||
],
|
||||
};
|
||||
// object1 = args[0] #ZK2a-1P
|
||||
// object2 = args[1] #Budowa przyłącza
|
||||
// object3 = args[2] #Przyłącze kablowe
|
||||
// adres = args[3]
|
||||
// date = args[4]
|
||||
// len_h = int(args[5])
|
||||
// len_v = int(args[6])
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/generateDxf", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(inputData),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Response was not ok.");
|
||||
}
|
||||
|
||||
// Download the response (the output file)
|
||||
const blob = await response.blob();
|
||||
const downloadUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = downloadUrl;
|
||||
a.download = "uziom.dxf";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
} catch (error) {
|
||||
console.error("There was an error:", error);
|
||||
}
|
||||
};
|
||||
|
||||
if (session) {
|
||||
return (
|
||||
<div className="">
|
||||
<Head>
|
||||
<title>Wastpol</title>
|
||||
</Head>
|
||||
|
||||
<div className="flex md:flex-row flex-col justify-between px-8 mt-2">
|
||||
<Nav />
|
||||
<UserTop session={session} />
|
||||
</div>
|
||||
|
||||
<main className="flex flex-1 flex-col items-center">
|
||||
<div className="flex flex-row m-24 space-x-24">
|
||||
<div>
|
||||
1. Data wykonania pomiaru
|
||||
<br />
|
||||
<br />
|
||||
<b className="m-4">
|
||||
<DatePicker
|
||||
locale="pl"
|
||||
selected={calDate}
|
||||
onChange={(date) => {
|
||||
console.log(date);
|
||||
console.log(typeof date);
|
||||
const day = date.getDate().toString().padStart(2, "0"); // Add leading zero if necessary
|
||||
const month = (date.getMonth() + 1)
|
||||
.toString()
|
||||
.padStart(2, "0"); // Month is 0-indexed, add 1 to get the correct month
|
||||
const year = date.getFullYear();
|
||||
const formattedDate = `${day}.${month}.${year}`;
|
||||
console.log(formattedDate);
|
||||
setGround((current) => ({
|
||||
...current,
|
||||
date: formattedDate,
|
||||
}));
|
||||
setCalDate(date);
|
||||
}}
|
||||
placeholderText="Wybierz datę"
|
||||
dateFormat="dd.MM.yyyy"
|
||||
/>
|
||||
</b>
|
||||
<br />
|
||||
<br />
|
||||
<RadioGroup
|
||||
label="2. Wymagane uziemienie"
|
||||
size={16}
|
||||
value={neededValue}
|
||||
options={options}
|
||||
onChange={(event) => {
|
||||
setNeededValue(event.target.value);
|
||||
}}
|
||||
/>
|
||||
<TextInputField
|
||||
label="3. Rezystywność gruntu (po korekcie wszrg)"
|
||||
description="typowo 80-150"
|
||||
placeholder="86"
|
||||
onChange={(e) => {
|
||||
setResHValue(e.target.value);
|
||||
setResVValue((ground.wanted==30)?
|
||||
getRandomInt(
|
||||
parseInt(e.target.value) - 10,
|
||||
parseInt(e.target.value) - 4
|
||||
):getRandomInt(
|
||||
parseInt(e.target.value) - 20,
|
||||
parseInt(e.target.value) - 10
|
||||
)
|
||||
|
||||
);
|
||||
}}
|
||||
value={resHValue}
|
||||
/>
|
||||
<TextInputField
|
||||
label="4. Numer protokołu rezystywności gruntu"
|
||||
// description=""
|
||||
placeholder="435"
|
||||
onChange={(e) =>
|
||||
setGround((current) => ({ ...current, no: e.target.value }))
|
||||
}
|
||||
value={ground.no}
|
||||
/>{" "}
|
||||
</div>
|
||||
<div>
|
||||
<TextInputField
|
||||
label="5. Tytuł projektu"
|
||||
description="Budowa/modernizacja przyłącza/sieci kablowej/napowietrznej"
|
||||
placeholder="Budowa przyłącza kablowego nN"
|
||||
width={440}
|
||||
onChange={(e) =>
|
||||
setGround((current) => ({
|
||||
...current,
|
||||
pr_title: e.target.value,
|
||||
}))
|
||||
}
|
||||
value={ground.pr_title}
|
||||
/>
|
||||
<TextInputField
|
||||
label="6. Obiekt"
|
||||
// description=""
|
||||
placeholder="Przyłącz kablowy nN"
|
||||
onChange={(e) =>
|
||||
setGround((current) => ({
|
||||
...current,
|
||||
object: e.target.value,
|
||||
}))
|
||||
}
|
||||
value={ground.object}
|
||||
/>
|
||||
<TextInputField
|
||||
label="7. Miejscowość"
|
||||
// description=""
|
||||
placeholder="Paszyn"
|
||||
onChange={(e) =>
|
||||
setGround((current) => ({
|
||||
...current,
|
||||
in_city: e.target.value,
|
||||
}))
|
||||
}
|
||||
value={ground.in_city}
|
||||
/>
|
||||
<TextInputField
|
||||
label="8. Gmina/Obręb"
|
||||
// description=""
|
||||
placeholder="gm. Chełmiec lub obr. 116"
|
||||
onChange={(e) =>
|
||||
setGround((current) => ({
|
||||
...current,
|
||||
commune: e.target.value,
|
||||
}))
|
||||
}
|
||||
value={ground.commune}
|
||||
/>
|
||||
<TextInputField
|
||||
label="9. Działki"
|
||||
// description=""
|
||||
placeholder="Wszystkie numery działek"
|
||||
onChange={(e) =>
|
||||
setGround((current) => ({
|
||||
...current,
|
||||
all_parcels: e.target.value,
|
||||
}))
|
||||
}
|
||||
value={ground.all_parcels}
|
||||
/>
|
||||
<TextInputField
|
||||
label="10. Działka przyłączana"
|
||||
// description=""
|
||||
placeholder="423"
|
||||
onChange={(e) =>
|
||||
setGround((current) => ({
|
||||
...current,
|
||||
target_parcel: e.target.value,
|
||||
}))
|
||||
}
|
||||
value={ground.target_parcel}
|
||||
/>
|
||||
<TextInputField
|
||||
label="11. Współrzędne układu uziomowego"
|
||||
// description=""
|
||||
placeholder={"49°42'54\"N 20°38'0\"E"}
|
||||
onChange={(e) =>
|
||||
setGround((current) => ({
|
||||
...current,
|
||||
geo_data: e.target.value,
|
||||
}))
|
||||
}
|
||||
value={ground.geo_data}
|
||||
/>{" "}
|
||||
</div>
|
||||
<div>
|
||||
<RadioGroup
|
||||
label="1. Projektowany/istniejący"
|
||||
size={16}
|
||||
value={objValue1}
|
||||
options={objOptions1}
|
||||
onChange={(event) => {
|
||||
setObjValue1(event.target.value);
|
||||
setGround((current) => ({
|
||||
...current,
|
||||
objValue1: event.target.value,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
<TextInputField
|
||||
label="2. Nazwa uziemianego obiektu"
|
||||
// description=""
|
||||
placeholder={"ZK2a-1P lub słup nr 54"}
|
||||
width={440}
|
||||
onChange={(e) =>
|
||||
setGround((current) => ({
|
||||
...current,
|
||||
objName: e.target.value,
|
||||
}))
|
||||
}
|
||||
value={ground.objName}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const res = getGrounding(
|
||||
parseInt(neededValue),
|
||||
parseFloat(resHValue),
|
||||
parseFloat(resVValue),
|
||||
ground.date
|
||||
);
|
||||
setGround((current) => ({ ...current, ...res }));
|
||||
}}
|
||||
appearance="primary"
|
||||
// className="p-2 bg-slate-200 rounded-md"
|
||||
>
|
||||
Oblicz Uziemienie
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={() => {
|
||||
const docx = generateDocument(ground);
|
||||
}}
|
||||
appearance="primary"
|
||||
// className="p-2 bg-slate-200 rounded-md"
|
||||
>
|
||||
Generuj Opis
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={generateDxf}
|
||||
appearance="primary"
|
||||
// className="p-2 bg-slate-200 rounded-md"
|
||||
>
|
||||
Generuj Rysunek
|
||||
</Button>
|
||||
<h2>
|
||||
Uziemienie poziome: {ground.result_h} Ω ({ground.wszrg_h})
|
||||
</h2>
|
||||
<h2>
|
||||
Uziemienie pionowe: {ground.result_v} Ω ({ground.wszrg_v})
|
||||
</h2>
|
||||
<h2>Uziemienie: {ground.result} Ω</h2>
|
||||
<h2>Szpile: {ground.rod_num} szt</h2>
|
||||
<h2>Bednarka: {ground.hor_len} m</h2>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid place-items-center h-screen">
|
||||
<Head>
|
||||
<title>Wastpol</title>
|
||||
</Head>
|
||||
<div className="flex flex-col justify-center">
|
||||
<h2 className="p-2">Nie zalogowano</h2>
|
||||
<br></br>
|
||||
<Button
|
||||
onClick={() => signIn()}
|
||||
appearance="primary"
|
||||
// className="p-2 bg-slate-200 rounded-md"
|
||||
>
|
||||
Zaloguj
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
68
components/MapComponent.js
Normal file
68
components/MapComponent.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const MapComponent = ({ latitude, longitude, name, description, officeType }) => {
|
||||
const [mapLoaded, setMapLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Load Leaflet on client-side only
|
||||
if (typeof window !== 'undefined' && latitude && longitude) {
|
||||
// Import Leaflet dynamically
|
||||
import('leaflet').then((L) => {
|
||||
// Safety check to see if map element exists
|
||||
const mapElement = document.getElementById('map');
|
||||
if (!mapElement) return;
|
||||
|
||||
// Clean up any existing map instance
|
||||
if (mapElement._leaflet_map) {
|
||||
mapElement._leaflet_map.remove();
|
||||
}
|
||||
|
||||
// Initialize map
|
||||
const map = L.map('map').setView([latitude, longitude], 15);
|
||||
|
||||
// Store the map instance on the DOM element to clean up later
|
||||
mapElement._leaflet_map = map;
|
||||
|
||||
// Add OpenStreetMap tiles
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors',
|
||||
maxZoom: 19
|
||||
}).addTo(map);
|
||||
|
||||
// Add marker for post office
|
||||
const marker = L.marker([latitude, longitude])
|
||||
.addTo(map)
|
||||
.bindPopup(`
|
||||
<div style="font-family: 'Segoe UI', sans-serif;">
|
||||
<h4 style="margin: 0 0 8px 0; color: #004d99;">${name}</h4>
|
||||
<p style="margin: 4px 0;"><strong>📍 Adres:</strong><br>${description.street} ${description.houseNumber}<br>${description.zipCode} ${description.city}</p>
|
||||
<p style="margin: 4px 0;"><strong>🏢 Typ:</strong> ${officeType === 'UP' ? 'Urząd Pocztowy' : officeType}</p>
|
||||
</div>
|
||||
`, {
|
||||
maxWidth: 300
|
||||
});
|
||||
|
||||
// Open popup by default
|
||||
marker.openPopup();
|
||||
|
||||
// Make sure map renders correctly
|
||||
setTimeout(() => {
|
||||
map.invalidateSize();
|
||||
setMapLoaded(true);
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
|
||||
// Clean up function
|
||||
return () => {
|
||||
const mapElement = document.getElementById('map');
|
||||
if (mapElement && mapElement._leaflet_map) {
|
||||
mapElement._leaflet_map.remove();
|
||||
}
|
||||
};
|
||||
}, [latitude, longitude, name, description, officeType]);
|
||||
|
||||
return <div id="map" style={{ height: '400px', borderRadius: '8px' }}></div>;
|
||||
};
|
||||
|
||||
export default MapComponent;
|
||||
@@ -1,107 +1,239 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Pane,
|
||||
TextInputField,
|
||||
TextareaField,
|
||||
Button,
|
||||
BuildIcon,
|
||||
toaster,
|
||||
Alert,
|
||||
} from "evergreen-ui";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { Card, CardHeader, CardContent, CardTitle, CardDescription, Button, Input, Textarea, Alert } from "../ui/components";
|
||||
import { ChartBarIcon, ArrowDownTrayIcon as DownloadIcon, EyeIcon } from '@heroicons/react/24/outline';
|
||||
import axios from "axios";
|
||||
import Footer from "./footer";
|
||||
|
||||
export default function Generator() {
|
||||
const [profil, setProfil] = useState();
|
||||
const [args, setArgs] = useState({ scale: 200 });
|
||||
const [args, setArgs] = useState({
|
||||
scale: 200,
|
||||
elementOne: 0,
|
||||
elementTwo: 0,
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [previewVisible, setPreviewVisible] = useState(false);
|
||||
const { query } = useRouter();
|
||||
|
||||
const ElementOneOptions = [
|
||||
{ label: "Nic", value: "0" },
|
||||
{ label: "Słup", value: "1" },
|
||||
{ label: "Dom", value: "2" },
|
||||
];
|
||||
|
||||
const ElementTwoOptions = [
|
||||
{ label: "Nic", value: "0" },
|
||||
{ label: "Słup", value: "1" },
|
||||
{ label: "Dom", value: "2" },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (query.external == "tru") {
|
||||
axios
|
||||
.post("/api/readtext", {
|
||||
id: query.id,
|
||||
})
|
||||
.then(function (response) {
|
||||
setProfil(response.data.data);
|
||||
document.getElementById("textarea-1").value = response.data.data;
|
||||
console.log(response.data.data);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getPath = (e, path) => {
|
||||
let newLines = [];
|
||||
const lines = path.split("\n");
|
||||
|
||||
for (let line of path.split("\n")) {
|
||||
if (line[0] == "P") continue;
|
||||
if (line[0] == "S") continue;
|
||||
if (line[0] == "X") continue;
|
||||
|
||||
// Parse the data more accurately
|
||||
for (let line of lines) {
|
||||
line = line.trim();
|
||||
// Skip header lines, segment lines, and empty lines
|
||||
if (line.startsWith("Próbkowanie") ||
|
||||
line.startsWith("Segment") ||
|
||||
line.startsWith("X:") ||
|
||||
line === "") {
|
||||
continue;
|
||||
}
|
||||
newLines.push(line);
|
||||
}
|
||||
let xs = [];
|
||||
let ys = [];
|
||||
let zs = [];
|
||||
let toti = 0;
|
||||
let al = 0;
|
||||
|
||||
if (newLines.length === 0) {
|
||||
console.log("No valid coordinate data found");
|
||||
return;
|
||||
}
|
||||
|
||||
let points = [];
|
||||
|
||||
// Parse coordinates more reliably
|
||||
for (let line of newLines) {
|
||||
al += 1;
|
||||
let theLine = line.split(",");
|
||||
//console.log(theLine)
|
||||
if (
|
||||
xs &&
|
||||
parseFloat(theLine[0]) == xs[-1] &&
|
||||
parseFloat(theLine[1]) == ys[-1]
|
||||
)
|
||||
continue;
|
||||
const coords = line.split(/[,\t]+/).map(s => s.trim());
|
||||
if (coords.length >= 3) {
|
||||
const x = parseFloat(coords[0]);
|
||||
const y = parseFloat(coords[1]);
|
||||
const z = parseFloat(coords[2]);
|
||||
|
||||
if (al > 2) {
|
||||
let dist =
|
||||
((xs[xs.length - 1] - xs[xs.length - 2]) ** 2 +
|
||||
(ys[ys.length - 1] - ys[ys.length - 2]) ** 2) **
|
||||
0.5;
|
||||
toti += Math.round(dist);
|
||||
if (!isNaN(x) && !isNaN(y) && !isNaN(z)) {
|
||||
points.push({ x, y, z });
|
||||
}
|
||||
xs.push(parseFloat(theLine[0]));
|
||||
ys.push(parseFloat(theLine[1]));
|
||||
zs.push(parseFloat(theLine[2]));
|
||||
}
|
||||
let prevLine = ["0", "0", "0"];
|
||||
let scaleFactor = 100000 / 2000;
|
||||
}
|
||||
|
||||
if (points.length === 0) {
|
||||
console.log("No valid points parsed");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Parsed ${points.length} points`);
|
||||
|
||||
const canvas = document.getElementById("canvas");
|
||||
|
||||
if (!canvas.getContext) {
|
||||
console.log("canvas err");
|
||||
toaster.warning("canvas err");
|
||||
console.log("Canvas element:", canvas);
|
||||
if (!canvas || !canvas.getContext) {
|
||||
console.log("canvas not found or no context available");
|
||||
console.log("canvas exists:", !!canvas);
|
||||
console.log("getContext available:", canvas ? !!canvas.getContext : false);
|
||||
return;
|
||||
}
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.clearRect(0, 0, 700, 700);
|
||||
const canvasWidth = 800; // Match the canvas width
|
||||
const canvasHeight = 400; // Match the canvas height
|
||||
const padding = 50;
|
||||
|
||||
// set line stroke and line width
|
||||
ctx.strokeStyle = "red";
|
||||
ctx.lineWidth = 2;
|
||||
let totalH = Math.max(...zs);
|
||||
let minH = Math.min(...zs);
|
||||
for (let line = 0; line < xs.length - 1; line++) {
|
||||
let theLine = [xs[line], ys[line], zs[line]];
|
||||
let x = parseFloat(theLine[0]) - parseFloat(prevLine[0]);
|
||||
let y = parseFloat(theLine[1]) - parseFloat(prevLine[1]);
|
||||
let x1 = line * scaleFactor;
|
||||
let y1 = zs[line];
|
||||
let x2 = (line + 1) * scaleFactor;
|
||||
let y2 = zs[line + 1];
|
||||
//console.log(x1, y1, x2, y2);
|
||||
let b0 = 0;
|
||||
let b1 = 500;
|
||||
let z1 = b0 + (b1 - b0) * ((y1 - totalH) / (minH - totalH));
|
||||
let z2 = b0 + (b1 - b0) * ((y2 - totalH) / (minH - totalH));
|
||||
// Clear canvas
|
||||
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
||||
|
||||
//b0 + (b1 - b0) * ((x1-0)/(toti*scaleFactor-0));
|
||||
let x12 = b0 + (b1 - b0) * ((x1 - 0) / (toti * scaleFactor));
|
||||
let x22 = b0 + (b1 - b0) * ((x2 - 0) / (toti * scaleFactor));
|
||||
//console.log(x12);
|
||||
// Calculate bounds
|
||||
const xCoords = points.map(p => p.x);
|
||||
const yCoords = points.map(p => p.y);
|
||||
const zCoords = points.map(p => p.z);
|
||||
|
||||
const minX = Math.min(...xCoords);
|
||||
const maxX = Math.max(...xCoords);
|
||||
const minY = Math.min(...yCoords);
|
||||
const maxY = Math.max(...yCoords);
|
||||
const minZ = Math.min(...zCoords);
|
||||
const maxZ = Math.max(...zCoords);
|
||||
|
||||
// Calculate distances along the path
|
||||
let distances = [0];
|
||||
let totalDistance = 0;
|
||||
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
const dx = points[i].x - points[i-1].x;
|
||||
const dy = points[i].y - points[i-1].y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
totalDistance += distance;
|
||||
distances.push(totalDistance);
|
||||
}
|
||||
|
||||
console.log(`Total distance: ${totalDistance.toFixed(2)}m, Height range: ${minZ.toFixed(2)}m - ${maxZ.toFixed(2)}m`);
|
||||
|
||||
// Set up drawing area
|
||||
const drawWidth = canvasWidth - 2 * padding;
|
||||
const drawHeight = canvasHeight - 2 * padding;
|
||||
|
||||
// Draw coordinate system
|
||||
ctx.strokeStyle = "#E5E7EB";
|
||||
ctx.lineWidth = 1;
|
||||
|
||||
// Draw grid
|
||||
const gridLines = 10;
|
||||
for (let i = 0; i <= gridLines; i++) {
|
||||
const x = padding + (i / gridLines) * drawWidth;
|
||||
const y = padding + (i / gridLines) * drawHeight;
|
||||
|
||||
// Vertical grid lines
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x12, z1);
|
||||
ctx.lineTo(x22, z2);
|
||||
ctx.moveTo(x, padding);
|
||||
ctx.lineTo(x, canvasHeight - padding);
|
||||
ctx.stroke();
|
||||
|
||||
// Horizontal grid lines
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(padding, y);
|
||||
ctx.lineTo(canvasWidth - padding, y);
|
||||
ctx.stroke();
|
||||
}
|
||||
};
|
||||
|
||||
const parsePreview = (e) => {
|
||||
// console.log(dxf);
|
||||
// Draw axes
|
||||
ctx.strokeStyle = "#6B7280";
|
||||
ctx.lineWidth = 2;
|
||||
|
||||
// X-axis (bottom)
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(padding, canvasHeight - padding);
|
||||
ctx.lineTo(canvasWidth - padding, canvasHeight - padding);
|
||||
ctx.stroke();
|
||||
|
||||
// Y-axis (left)
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(padding, padding);
|
||||
ctx.lineTo(padding, canvasHeight - padding);
|
||||
ctx.stroke();
|
||||
|
||||
// Draw labels
|
||||
ctx.fillStyle = "#374151";
|
||||
ctx.font = "12px sans-serif";
|
||||
ctx.textAlign = "center";
|
||||
|
||||
// Distance labels (bottom)
|
||||
for (let i = 0; i <= 5; i++) {
|
||||
const distance = (totalDistance * i) / 5;
|
||||
const x = padding + (i / 5) * drawWidth;
|
||||
ctx.fillText(`${distance.toFixed(0)}m`, x, canvasHeight - padding + 20);
|
||||
}
|
||||
|
||||
// Height labels (left)
|
||||
ctx.textAlign = "right";
|
||||
for (let i = 0; i <= 5; i++) {
|
||||
const height = minZ + ((maxZ - minZ) * i) / 5;
|
||||
const y = canvasHeight - padding - (i / 5) * drawHeight;
|
||||
ctx.fillText(`${height.toFixed(1)}m`, padding - 10, y + 4);
|
||||
}
|
||||
|
||||
// Draw profile line
|
||||
ctx.strokeStyle = "#3B82F6";
|
||||
ctx.lineWidth = 3;
|
||||
ctx.lineCap = "round";
|
||||
ctx.lineJoin = "round";
|
||||
|
||||
ctx.beginPath();
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
const x = padding + (distances[i] / totalDistance) * drawWidth;
|
||||
const y = canvasHeight - padding - ((points[i].z - minZ) / (maxZ - minZ)) * drawHeight;
|
||||
|
||||
if (i === 0) {
|
||||
ctx.moveTo(x, y);
|
||||
} else {
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
}
|
||||
ctx.stroke();
|
||||
|
||||
// Draw elevation points
|
||||
ctx.fillStyle = "#3B82F6";
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
const x = padding + (distances[i] / totalDistance) * drawWidth;
|
||||
const y = canvasHeight - padding - ((points[i].z - minZ) / (maxZ - minZ)) * drawHeight;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, 4, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// Add title
|
||||
ctx.fillStyle = "#1F2937";
|
||||
ctx.font = "16px sans-serif";
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText("Profil terenu", canvasWidth / 2, 30);
|
||||
|
||||
setPreviewVisible(true);
|
||||
};
|
||||
|
||||
const py = (e, profil, args) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
|
||||
axios
|
||||
.post("/api/spawn", {
|
||||
@@ -109,75 +241,205 @@ export default function Generator() {
|
||||
arguments: args,
|
||||
})
|
||||
.then(function (response) {
|
||||
setIsLoading(false);
|
||||
console.log(response);
|
||||
if (response.data.toString().startsWith("Py Error")) {
|
||||
toaster.danger(response.data);
|
||||
alert("Błąd: " + response.data);
|
||||
return;
|
||||
}
|
||||
toaster.warning(response.data.data);
|
||||
document.getElementById("down").download =
|
||||
response.data.filename.toString() + ".dxf";
|
||||
document.getElementById("down").download = response.data.filename.toString() + ".dxf";
|
||||
document.getElementById("down").click();
|
||||
})
|
||||
.catch(function (error) {
|
||||
setIsLoading(false);
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex xl:flex-row flex-col">
|
||||
<div className="flex flex-col">
|
||||
<Pane width={480}>
|
||||
<TextareaField
|
||||
<div className="grid grid-cols-1 xl:grid-cols-2 gap-8">
|
||||
{/* Input Section */}
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<ChartBarIcon className="w-5 h-5 text-blue-600" />
|
||||
<span>Dane wejściowe</span>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Wklej dane z Geoportalu lub wprowadź własne współrzędne
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<Textarea
|
||||
id="textarea-1"
|
||||
label="Dane z Geoportalu:"
|
||||
label="Dane z Geoportalu"
|
||||
placeholder="Próbkowanie: 1 ..."
|
||||
rows={8}
|
||||
onChange={(e) => {
|
||||
//console.log(e.target.value);
|
||||
setProfil(e.target.value);
|
||||
parsePreview(e);
|
||||
getPath(e, e.target.value);
|
||||
}}
|
||||
className="font-mono text-sm"
|
||||
/>
|
||||
|
||||
<TextInputField
|
||||
label="Skala:"
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<Input
|
||||
label="Skala"
|
||||
placeholder="200"
|
||||
width={100}
|
||||
onChange={(e) => {
|
||||
console.log(e.target.value);
|
||||
setArgs({ ...args, scale: e.target.value });
|
||||
}}
|
||||
type="number"
|
||||
value={args.scale}
|
||||
onChange={(e) => setArgs({ ...args, scale: e.target.value })}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Element lewy
|
||||
</label>
|
||||
<select
|
||||
className="block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
|
||||
value={args.elementOne}
|
||||
onChange={(e) => setArgs({ ...args, elementOne: e.target.value })}
|
||||
>
|
||||
{ElementOneOptions.map(option => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Element prawy
|
||||
</label>
|
||||
<select
|
||||
className="block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
|
||||
value={args.elementTwo}
|
||||
onChange={(e) => setArgs({ ...args, elementTwo: e.target.value })}
|
||||
>
|
||||
{ElementTwoOptions.map(option => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex space-x-3">
|
||||
<Button
|
||||
onClick={() => {
|
||||
const textareaValue = document.getElementById("textarea-1").value;
|
||||
if (textareaValue && textareaValue.startsWith("Próbkowanie")) {
|
||||
// First set preview visible, then draw after a small delay
|
||||
setPreviewVisible(true);
|
||||
setTimeout(() => {
|
||||
getPath(null, textareaValue);
|
||||
}, 50);
|
||||
} else {
|
||||
alert("Wprowadź poprawne dane z Geoportalu");
|
||||
}
|
||||
}}
|
||||
variant="outline"
|
||||
className="flex-1"
|
||||
>
|
||||
<EyeIcon className="w-5 h-5 mr-2" />
|
||||
Podgląd
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
marginY={8}
|
||||
marginRight={12}
|
||||
appearance="default"
|
||||
iconAfter={BuildIcon}
|
||||
onClick={(e) => {
|
||||
if (document.getElementById("textarea-1").value == "") {
|
||||
toaster.danger("Pole danych nie może być puste");
|
||||
} else if (
|
||||
!document
|
||||
.getElementById("textarea-1")
|
||||
.value.startsWith("Próbkowanie")
|
||||
) {
|
||||
toaster.danger("Błędne dane");
|
||||
const textareaValue = document.getElementById("textarea-1").value;
|
||||
if (textareaValue === "") {
|
||||
alert("Pole danych nie może być puste");
|
||||
} else if (!textareaValue.startsWith("Próbkowanie")) {
|
||||
alert("Błędne dane");
|
||||
} else {
|
||||
py(e, profil, args);
|
||||
}
|
||||
}}
|
||||
disabled={isLoading}
|
||||
loading={isLoading}
|
||||
className="flex-1"
|
||||
>
|
||||
Generuj
|
||||
<DownloadIcon className="w-5 h-5 mr-2" />
|
||||
{isLoading ? 'Generowanie...' : 'Generuj profil'}
|
||||
</Button>
|
||||
</Pane>
|
||||
|
||||
<a href="test.dxf" download="test.dxf" id="down">
|
||||
{" "}
|
||||
</a>
|
||||
</div>
|
||||
<Pane className="ml-8 shadow-md rounded-md">
|
||||
<canvas id="canvas" height="500" width="700"></canvas>
|
||||
</Pane>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Preview Section */}
|
||||
<div>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<EyeIcon className="w-5 h-5 text-green-600" />
|
||||
<span>Podgląd profilu</span>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Wizualizacja profilu terenu na podstawie wprowadzonych danych
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="bg-gray-50 rounded-lg p-4">
|
||||
<canvas
|
||||
id="canvas"
|
||||
height="400"
|
||||
width="800"
|
||||
className="border border-gray-200 rounded bg-white w-full max-w-full"
|
||||
style={{ display: previewVisible ? 'block' : 'none' }}
|
||||
/>
|
||||
{!previewVisible && (
|
||||
<div className="bg-gray-50 rounded-lg p-8 text-center">
|
||||
<div className="mx-auto w-16 h-16 bg-gray-200 rounded-full flex items-center justify-center mb-4">
|
||||
<ChartBarIcon className="w-8 h-8 text-gray-400" />
|
||||
</div>
|
||||
<p className="text-gray-500">Wprowadź dane i kliknij "Podgląd" aby zobaczyć profil</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Instructions */}
|
||||
<Card className="mt-6">
|
||||
<CardHeader>
|
||||
<CardTitle>Instrukcje</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3 text-sm">
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-xs font-semibold text-blue-600">1</span>
|
||||
</div>
|
||||
<p className="text-gray-600">Skopiuj dane z Geoportalu w formacie "Próbkowanie: 1"</p>
|
||||
</div>
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-xs font-semibold text-blue-600">2</span>
|
||||
</div>
|
||||
<p className="text-gray-600">Ustaw skalę i wybierz elementy dodatkowe</p>
|
||||
</div>
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-xs font-semibold text-blue-600">3</span>
|
||||
</div>
|
||||
<p className="text-gray-600">Kliknij "Podgląd" aby zobaczyć wizualizację profilu</p>
|
||||
</div>
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-xs font-semibold text-blue-600">4</span>
|
||||
</div>
|
||||
<p className="text-gray-600">Kliknij "Generuj profil" aby pobrać plik DXF</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<a href="test.dxf" download="test.dxf" id="down" className="hidden" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
294
components/templates/generator_new.js
Normal file
294
components/templates/generator_new.js
Normal file
@@ -0,0 +1,294 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { Card, CardHeader, CardContent, CardTitle, CardDescription, Button, Input, Textarea, Alert } from "../ui/components";
|
||||
import { ChartBarIcon, ArrowDownTrayIcon as DownloadIcon, EyeIcon } from '@heroicons/react/24/outline';
|
||||
import axios from "axios";
|
||||
|
||||
export default function Generator() {
|
||||
const [profil, setProfil] = useState();
|
||||
const [args, setArgs] = useState({
|
||||
scale: 200,
|
||||
elementOne: 0,
|
||||
elementTwo: 0,
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [previewVisible, setPreviewVisible] = useState(false);
|
||||
const { query } = useRouter();
|
||||
|
||||
const ElementOneOptions = [
|
||||
{ label: "Nic", value: "0" },
|
||||
{ label: "Słup", value: "1" },
|
||||
{ label: "Dom", value: "2" },
|
||||
];
|
||||
|
||||
const ElementTwoOptions = [
|
||||
{ label: "Nic", value: "0" },
|
||||
{ label: "Słup", value: "1" },
|
||||
{ label: "Dom", value: "2" },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (query.external == "tru") {
|
||||
axios
|
||||
.post("/api/readtext", {
|
||||
id: query.id,
|
||||
})
|
||||
.then(function (response) {
|
||||
setProfil(response.data.data);
|
||||
document.getElementById("textarea-1").value = response.data.data;
|
||||
console.log(response.data.data);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getPath = (e, path) => {
|
||||
let newLines = [];
|
||||
|
||||
for (let line of path.split("\n")) {
|
||||
if (line[0] == "P") continue;
|
||||
if (line[0] == "S") continue;
|
||||
if (line[0] == "X") continue;
|
||||
newLines.push(line);
|
||||
}
|
||||
|
||||
let xs = [], ys = [], zs = [];
|
||||
let toti = 0, al = 0;
|
||||
|
||||
for (let line of newLines) {
|
||||
al += 1;
|
||||
let theLine = line.split(",");
|
||||
|
||||
if (xs && parseFloat(theLine[0]) == xs[-1] && parseFloat(theLine[1]) == ys[-1])
|
||||
continue;
|
||||
|
||||
if (al > 2) {
|
||||
let dist = ((xs[xs.length - 1] - xs[xs.length - 2]) ** 2 +
|
||||
(ys[ys.length - 1] - ys[ys.length - 2]) ** 2) ** 0.5;
|
||||
toti += Math.round(dist);
|
||||
}
|
||||
xs.push(parseFloat(theLine[0]));
|
||||
ys.push(parseFloat(theLine[1]));
|
||||
zs.push(parseFloat(theLine[2]));
|
||||
}
|
||||
|
||||
const canvas = document.getElementById("canvas");
|
||||
if (!canvas.getContext) {
|
||||
console.log("canvas err");
|
||||
return;
|
||||
}
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.clearRect(0, 0, 700, 500);
|
||||
ctx.strokeStyle = "#3B82F6";
|
||||
ctx.lineWidth = 2;
|
||||
|
||||
let scaleFactor = 100000 / 2000;
|
||||
let totalH = Math.max(...zs);
|
||||
let minH = Math.min(...zs);
|
||||
|
||||
for (let line = 0; line < xs.length - 1; line++) {
|
||||
let x1 = line * scaleFactor;
|
||||
let y1 = zs[line];
|
||||
let x2 = (line + 1) * scaleFactor;
|
||||
let y2 = zs[line + 1];
|
||||
|
||||
let b0 = 50, b1 = 450;
|
||||
let z1 = b0 + (b1 - b0) * ((y1 - totalH) / (minH - totalH));
|
||||
let z2 = b0 + (b1 - b0) * ((y2 - totalH) / (minH - totalH));
|
||||
|
||||
let x12 = b0 + (b1 - b0) * ((x1 - 0) / (toti * scaleFactor));
|
||||
let x22 = b0 + (b1 - b0) * ((x2 - 0) / (toti * scaleFactor));
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x12, z1);
|
||||
ctx.lineTo(x22, z2);
|
||||
ctx.stroke();
|
||||
}
|
||||
setPreviewVisible(true);
|
||||
};
|
||||
|
||||
const py = (e, profil, args) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
|
||||
axios
|
||||
.post("/api/spawn", {
|
||||
profil: profil,
|
||||
arguments: args,
|
||||
})
|
||||
.then(function (response) {
|
||||
setIsLoading(false);
|
||||
console.log(response);
|
||||
if (response.data.toString().startsWith("Py Error")) {
|
||||
alert("Błąd: " + response.data);
|
||||
return;
|
||||
}
|
||||
document.getElementById("down").download = response.data.filename.toString() + ".dxf";
|
||||
document.getElementById("down").click();
|
||||
})
|
||||
.catch(function (error) {
|
||||
setIsLoading(false);
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 xl:grid-cols-2 gap-8">
|
||||
{/* Input Section */}
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<ChartBarIcon className="w-5 h-5 text-blue-600" />
|
||||
<span>Dane wejściowe</span>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Wklej dane z Geoportalu lub wprowadź własne współrzędne
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<Textarea
|
||||
id="textarea-1"
|
||||
label="Dane z Geoportalu"
|
||||
placeholder="Próbkowanie: 1 ..."
|
||||
rows={8}
|
||||
onChange={(e) => {
|
||||
setProfil(e.target.value);
|
||||
getPath(e, e.target.value);
|
||||
}}
|
||||
className="font-mono text-sm"
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<Input
|
||||
label="Skala"
|
||||
placeholder="200"
|
||||
type="number"
|
||||
value={args.scale}
|
||||
onChange={(e) => setArgs({ ...args, scale: e.target.value })}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Element lewy
|
||||
</label>
|
||||
<select
|
||||
className="block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
|
||||
value={args.elementOne}
|
||||
onChange={(e) => setArgs({ ...args, elementOne: e.target.value })}
|
||||
>
|
||||
{ElementOneOptions.map(option => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Element prawy
|
||||
</label>
|
||||
<select
|
||||
className="block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
|
||||
value={args.elementTwo}
|
||||
onChange={(e) => setArgs({ ...args, elementTwo: e.target.value })}
|
||||
>
|
||||
{ElementTwoOptions.map(option => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
const textareaValue = document.getElementById("textarea-1").value;
|
||||
if (textareaValue === "") {
|
||||
alert("Pole danych nie może być puste");
|
||||
} else if (!textareaValue.startsWith("Próbkowanie")) {
|
||||
alert("Błędne dane");
|
||||
} else {
|
||||
py(e, profil, args);
|
||||
}
|
||||
}}
|
||||
disabled={isLoading}
|
||||
loading={isLoading}
|
||||
className="w-full"
|
||||
>
|
||||
<DownloadIcon className="w-5 h-5 mr-2" />
|
||||
{isLoading ? 'Generowanie...' : 'Generuj profil'}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Preview Section */}
|
||||
<div>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<EyeIcon className="w-5 h-5 text-green-600" />
|
||||
<span>Podgląd profilu</span>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Wizualizacja profilu terenu na podstawie wprowadzonych danych
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{previewVisible ? (
|
||||
<div className="bg-gray-50 rounded-lg p-4">
|
||||
<canvas
|
||||
id="canvas"
|
||||
height="500"
|
||||
width="700"
|
||||
className="border border-gray-200 rounded bg-white"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-gray-50 rounded-lg p-8 text-center">
|
||||
<div className="mx-auto w-16 h-16 bg-gray-200 rounded-full flex items-center justify-center mb-4">
|
||||
<ChartBarIcon className="w-8 h-8 text-gray-400" />
|
||||
</div>
|
||||
<p className="text-gray-500">Wprowadź dane aby zobaczyć podgląd profilu</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Instructions */}
|
||||
<Card className="mt-6">
|
||||
<CardHeader>
|
||||
<CardTitle>Instrukcje</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3 text-sm">
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-xs font-semibold text-blue-600">1</span>
|
||||
</div>
|
||||
<p className="text-gray-600">Skopiuj dane z Geoportalu w formacie "Próbkowanie: 1"</p>
|
||||
</div>
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-xs font-semibold text-blue-600">2</span>
|
||||
</div>
|
||||
<p className="text-gray-600">Ustaw skalę i wybierz elementy dodatkowe</p>
|
||||
</div>
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-xs font-semibold text-blue-600">3</span>
|
||||
</div>
|
||||
<p className="text-gray-600">Kliknij "Generuj profil" aby pobrać plik DXF</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<a href="test.dxf" download="test.dxf" id="down" className="hidden" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,25 +1,12 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
Pane,
|
||||
TextInputField,
|
||||
TextareaField,
|
||||
Button,
|
||||
toaster,
|
||||
Alert,
|
||||
TrashIcon,
|
||||
Icon,
|
||||
} from "evergreen-ui";
|
||||
import { useState } from "react";
|
||||
import { Card, CardHeader, CardContent, CardTitle, CardDescription, Button, Input } from "../ui/components";
|
||||
import { PencilIcon, PlusIcon, TrashIcon, ArrowDownTrayIcon as DownloadIcon } from '@heroicons/react/24/outline';
|
||||
import axios from "axios";
|
||||
import Footer from "./footer";
|
||||
import Generator from "./generator";
|
||||
|
||||
export default function Manual() {
|
||||
const [points, setPoints] = useState([{ id: 0, len: 0, height: 0 }]);
|
||||
const [args, setArgs] = useState({ scale: 200 });
|
||||
|
||||
//useEffect(() => {
|
||||
//do something here
|
||||
// }, [points]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const reIndex = () => {
|
||||
let newId = 0;
|
||||
@@ -32,7 +19,32 @@ export default function Manual() {
|
||||
setPoints([...newPoints]);
|
||||
};
|
||||
|
||||
const addPoint = () => {
|
||||
setPoints([
|
||||
...points,
|
||||
{
|
||||
id: points.length,
|
||||
len: Number(points[points.length - 1].len) + 1,
|
||||
height: 0,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const removePoint = (index) => {
|
||||
let newPoints = points;
|
||||
newPoints.splice(index, 1);
|
||||
setPoints([...newPoints]);
|
||||
reIndex();
|
||||
};
|
||||
|
||||
const updatePoint = (index, field, value) => {
|
||||
let newPoints = points;
|
||||
newPoints[index][field] = value;
|
||||
setPoints([...newPoints]);
|
||||
};
|
||||
|
||||
const generation = (e) => {
|
||||
setIsLoading(true);
|
||||
let pointString = "Próbkowanie: 1\nSegment 0: w dół\nX: Y, Z";
|
||||
|
||||
for (let point of points) {
|
||||
@@ -42,7 +54,6 @@ export default function Manual() {
|
||||
pointString += point.height;
|
||||
}
|
||||
|
||||
console.log(pointString);
|
||||
py(e, pointString, args);
|
||||
};
|
||||
|
||||
@@ -55,103 +66,176 @@ export default function Manual() {
|
||||
arguments: args,
|
||||
})
|
||||
.then(function (response) {
|
||||
setIsLoading(false);
|
||||
console.log(response);
|
||||
if (response.data.toString().startsWith("Py Error")) {
|
||||
toaster.danger(response.data);
|
||||
alert("Błąd: " + response.data);
|
||||
return;
|
||||
}
|
||||
toaster.warning(response.data.data);
|
||||
document.getElementById("down").download =
|
||||
response.data.filename.toString() + ".dxf";
|
||||
document.getElementById("down").download = response.data.filename.toString() + ".dxf";
|
||||
document.getElementById("down").click();
|
||||
})
|
||||
.catch(function (error) {
|
||||
setIsLoading(false);
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex xl:flex-row flex-col">
|
||||
<div className="flex flex-col">
|
||||
<Pane width={480}>
|
||||
{points.map((point, index) => (
|
||||
<Pane className="flex flex-row items-center">
|
||||
<TextInputField
|
||||
id={`len-${index}`}
|
||||
placeholder="0 m"
|
||||
onChange={(e) => {
|
||||
console.log(e.target.value);
|
||||
let newPoints = points;
|
||||
newPoints[index].len = e.target.value;
|
||||
setPoints([...newPoints]);
|
||||
}}
|
||||
value={point.len}
|
||||
></TextInputField>
|
||||
<TextInputField
|
||||
id={`h-${index}`}
|
||||
placeholder="0 m n.p.m."
|
||||
onChange={(e) => {
|
||||
console.log(e.target.value);
|
||||
let newPoints = points;
|
||||
newPoints[index].height = e.target.value;
|
||||
setPoints([...newPoints]);
|
||||
}}
|
||||
value={point.height}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key == "Enter") {
|
||||
setPoints([
|
||||
...points,
|
||||
{
|
||||
id: points.length,
|
||||
len: Number(points[points.length - 1].len) + 1,
|
||||
height: 0,
|
||||
},
|
||||
]);
|
||||
<div className="grid grid-cols-1 xl:grid-cols-2 gap-8">
|
||||
{/* Input Section */}
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<PencilIcon className="w-5 h-5 text-blue-600" />
|
||||
<span>Punkty profilu</span>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Wprowadź punkty profilu ręcznie - długość i wysokość nad poziomem morza
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{/* Scale Input */}
|
||||
<Input
|
||||
label="Skala"
|
||||
type="number"
|
||||
placeholder="200"
|
||||
value={args.scale}
|
||||
onChange={(e) => setArgs({ ...args, scale: e.target.value })}
|
||||
/>
|
||||
|
||||
let nextDiv = e.target.parentNode.parentNode.nextSibling;
|
||||
nextDiv.childNodes[1].childNodes[1].focus()
|
||||
nextDiv.childNodes[1].childNodes[1].setSelectionRange(0, 1)
|
||||
{/* Points List */}
|
||||
<div className="space-y-3 max-h-96 overflow-y-auto">
|
||||
{points.map((point, index) => (
|
||||
<div key={point.id} className="grid grid-cols-2 gap-3 p-3 bg-gray-50 rounded-lg">
|
||||
<Input
|
||||
label={`Punkt ${index + 1} - Długość (m)`}
|
||||
type="number"
|
||||
placeholder="0"
|
||||
value={point.len}
|
||||
onChange={(e) => updatePoint(index, 'len', e.target.value)}
|
||||
/>
|
||||
<div className="flex space-x-2">
|
||||
<Input
|
||||
label="Wysokość (m n.p.m.)"
|
||||
type="number"
|
||||
placeholder="0"
|
||||
value={point.height}
|
||||
onChange={(e) => updatePoint(index, 'height', e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
addPoint();
|
||||
// Focus next input after render
|
||||
setTimeout(() => {
|
||||
const nextInput = e.target.parentNode.parentNode.parentNode.nextSibling?.querySelector('input');
|
||||
if (nextInput) nextInput.focus();
|
||||
}, 100);
|
||||
}
|
||||
}}
|
||||
></TextInputField>
|
||||
<Button
|
||||
className="mb-5"
|
||||
appearance="minimal"
|
||||
onClick={() => {
|
||||
let newPoints = points;
|
||||
newPoints.splice(index, 1);
|
||||
setPoints([...newPoints]);
|
||||
reIndex();
|
||||
}}
|
||||
/>
|
||||
{points.length > 1 && (
|
||||
<button
|
||||
onClick={() => removePoint(index)}
|
||||
className="self-end p-2 text-red-600 hover:bg-red-50 rounded-lg transition-colors"
|
||||
title="Usuń punkt"
|
||||
>
|
||||
<Icon icon={TrashIcon} color="danger"></Icon>
|
||||
</Button>
|
||||
</Pane>
|
||||
<TrashIcon className="w-5 h-5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</Pane>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex space-x-3">
|
||||
<Button
|
||||
onClick={() => {
|
||||
setPoints([
|
||||
...points,
|
||||
{
|
||||
id: points.length,
|
||||
len: Number(points[points.length - 1].len) + 1,
|
||||
height: 0,
|
||||
},
|
||||
]);
|
||||
}}
|
||||
onClick={addPoint}
|
||||
variant="outline"
|
||||
className="flex-1"
|
||||
>
|
||||
Dodaj
|
||||
<PlusIcon className="w-5 h-5 mr-2" />
|
||||
Dodaj punkt
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
appearance="primary"
|
||||
onClick={(e) => {
|
||||
generation(e);
|
||||
}}
|
||||
onClick={generation}
|
||||
disabled={isLoading}
|
||||
loading={isLoading}
|
||||
className="flex-1"
|
||||
>
|
||||
Generuj
|
||||
<DownloadIcon className="w-5 h-5 mr-2" />
|
||||
{isLoading ? 'Generowanie...' : 'Generuj profil'}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Preview/Instructions Section */}
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Podgląd punktów</CardTitle>
|
||||
<CardDescription>
|
||||
Lista wprowadzonych punktów profilu
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{points.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{points.map((point, index) => (
|
||||
<div key={point.id} className="flex justify-between items-center p-3 bg-gray-50 rounded-lg">
|
||||
<span className="font-medium text-gray-900">Punkt {index + 1}</span>
|
||||
<div className="text-sm text-gray-600">
|
||||
<span className="mr-4">Długość: {point.len}m</span>
|
||||
<span>Wysokość: {point.height}m</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<div className="mx-auto w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mb-4">
|
||||
<PencilIcon className="w-8 h-8 text-gray-400" />
|
||||
</div>
|
||||
<p className="text-gray-500">Brak punktów do wyświetlenia</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Instrukcje</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3 text-sm">
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-xs font-semibold text-blue-600">1</span>
|
||||
</div>
|
||||
<p className="text-gray-600">Wprowadź kolejne punkty profilu z długością i wysokością</p>
|
||||
</div>
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-xs font-semibold text-blue-600">2</span>
|
||||
</div>
|
||||
<p className="text-gray-600">Użyj klawisza Enter aby szybko dodać następny punkt</p>
|
||||
</div>
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-xs font-semibold text-blue-600">3</span>
|
||||
</div>
|
||||
<p className="text-gray-600">Ustaw skalę i kliknij "Generuj profil"</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<a href="test.dxf" download="test.dxf" id="down" className="hidden" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
241
components/templates/manual_new.js
Normal file
241
components/templates/manual_new.js
Normal file
@@ -0,0 +1,241 @@
|
||||
import { useState } from "react";
|
||||
import { Card, CardHeader, CardContent, CardTitle, CardDescription, Button, Input } from "../ui/components";
|
||||
import { PencilIcon, PlusIcon, TrashIcon, ArrowDownTrayIcon as DownloadIcon } from '@heroicons/react/24/outline';
|
||||
import axios from "axios";
|
||||
|
||||
export default function Manual() {
|
||||
const [points, setPoints] = useState([{ id: 0, len: 0, height: 0 }]);
|
||||
const [args, setArgs] = useState({ scale: 200 });
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const reIndex = () => {
|
||||
let newId = 0;
|
||||
let newPoints = [];
|
||||
for (let point of points) {
|
||||
point.id = newId;
|
||||
newPoints.push(point);
|
||||
newId += 1;
|
||||
}
|
||||
setPoints([...newPoints]);
|
||||
};
|
||||
|
||||
const addPoint = () => {
|
||||
setPoints([
|
||||
...points,
|
||||
{
|
||||
id: points.length,
|
||||
len: Number(points[points.length - 1].len) + 1,
|
||||
height: 0,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const removePoint = (index) => {
|
||||
let newPoints = points;
|
||||
newPoints.splice(index, 1);
|
||||
setPoints([...newPoints]);
|
||||
reIndex();
|
||||
};
|
||||
|
||||
const updatePoint = (index, field, value) => {
|
||||
let newPoints = points;
|
||||
newPoints[index][field] = value;
|
||||
setPoints([...newPoints]);
|
||||
};
|
||||
|
||||
const generation = (e) => {
|
||||
setIsLoading(true);
|
||||
let pointString = "Próbkowanie: 1\nSegment 0: w dół\nX: Y, Z";
|
||||
|
||||
for (let point of points) {
|
||||
pointString += "\n";
|
||||
pointString += Number(point.len) * 1.0;
|
||||
pointString += ", 1.0, ";
|
||||
pointString += point.height;
|
||||
}
|
||||
|
||||
py(e, pointString, args);
|
||||
};
|
||||
|
||||
const py = (e, profil, args) => {
|
||||
e.preventDefault();
|
||||
|
||||
axios
|
||||
.post("/api/spawn", {
|
||||
profil: profil,
|
||||
arguments: args,
|
||||
})
|
||||
.then(function (response) {
|
||||
setIsLoading(false);
|
||||
console.log(response);
|
||||
if (response.data.toString().startsWith("Py Error")) {
|
||||
alert("Błąd: " + response.data);
|
||||
return;
|
||||
}
|
||||
document.getElementById("down").download = response.data.filename.toString() + ".dxf";
|
||||
document.getElementById("down").click();
|
||||
})
|
||||
.catch(function (error) {
|
||||
setIsLoading(false);
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 xl:grid-cols-2 gap-8">
|
||||
{/* Input Section */}
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<PencilIcon className="w-5 h-5 text-blue-600" />
|
||||
<span>Punkty profilu</span>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Wprowadź punkty profilu ręcznie - długość i wysokość nad poziomem morza
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{/* Scale Input */}
|
||||
<Input
|
||||
label="Skala"
|
||||
type="number"
|
||||
placeholder="200"
|
||||
value={args.scale}
|
||||
onChange={(e) => setArgs({ ...args, scale: e.target.value })}
|
||||
/>
|
||||
|
||||
{/* Points List */}
|
||||
<div className="space-y-3 max-h-96 overflow-y-auto">
|
||||
{points.map((point, index) => (
|
||||
<div key={point.id} className="grid grid-cols-2 gap-3 p-3 bg-gray-50 rounded-lg">
|
||||
<Input
|
||||
label={`Punkt ${index + 1} - Długość (m)`}
|
||||
type="number"
|
||||
placeholder="0"
|
||||
value={point.len}
|
||||
onChange={(e) => updatePoint(index, 'len', e.target.value)}
|
||||
/>
|
||||
<div className="flex space-x-2">
|
||||
<Input
|
||||
label="Wysokość (m n.p.m.)"
|
||||
type="number"
|
||||
placeholder="0"
|
||||
value={point.height}
|
||||
onChange={(e) => updatePoint(index, 'height', e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
addPoint();
|
||||
// Focus next input after render
|
||||
setTimeout(() => {
|
||||
const nextInput = e.target.parentNode.parentNode.parentNode.nextSibling?.querySelector('input');
|
||||
if (nextInput) nextInput.focus();
|
||||
}, 100);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{points.length > 1 && (
|
||||
<button
|
||||
onClick={() => removePoint(index)}
|
||||
className="self-end p-2 text-red-600 hover:bg-red-50 rounded-lg transition-colors"
|
||||
title="Usuń punkt"
|
||||
>
|
||||
<TrashIcon className="w-5 h-5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex space-x-3">
|
||||
<Button
|
||||
onClick={addPoint}
|
||||
variant="outline"
|
||||
className="flex-1"
|
||||
>
|
||||
<PlusIcon className="w-5 h-5 mr-2" />
|
||||
Dodaj punkt
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={generation}
|
||||
disabled={isLoading}
|
||||
loading={isLoading}
|
||||
className="flex-1"
|
||||
>
|
||||
<DownloadIcon className="w-5 h-5 mr-2" />
|
||||
{isLoading ? 'Generowanie...' : 'Generuj profil'}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Preview/Instructions Section */}
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Podgląd punktów</CardTitle>
|
||||
<CardDescription>
|
||||
Lista wprowadzonych punktów profilu
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{points.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{points.map((point, index) => (
|
||||
<div key={point.id} className="flex justify-between items-center p-3 bg-gray-50 rounded-lg">
|
||||
<span className="font-medium text-gray-900">Punkt {index + 1}</span>
|
||||
<div className="text-sm text-gray-600">
|
||||
<span className="mr-4">Długość: {point.len}m</span>
|
||||
<span>Wysokość: {point.height}m</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<div className="mx-auto w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mb-4">
|
||||
<PencilIcon className="w-8 h-8 text-gray-400" />
|
||||
</div>
|
||||
<p className="text-gray-500">Brak punktów do wyświetlenia</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Instrukcje</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3 text-sm">
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-xs font-semibold text-blue-600">1</span>
|
||||
</div>
|
||||
<p className="text-gray-600">Wprowadź kolejne punkty profilu z długością i wysokością</p>
|
||||
</div>
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-xs font-semibold text-blue-600">2</span>
|
||||
</div>
|
||||
<p className="text-gray-600">Użyj klawisza Enter aby szybko dodać następny punkt</p>
|
||||
</div>
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-xs font-semibold text-blue-600">3</span>
|
||||
</div>
|
||||
<p className="text-gray-600">Ustaw skalę i kliknij "Generuj profil"</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<a href="test.dxf" download="test.dxf" id="down" className="hidden" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,13 @@
|
||||
import Link from "next/link";
|
||||
import { useRouter } from 'next/router';
|
||||
import { Pane, Heading } from "evergreen-ui";
|
||||
|
||||
export default function Nav() {
|
||||
const router = useRouter();
|
||||
|
||||
const isActive = (href) => {
|
||||
return router.pathname === href ? `active font-bold bg-slate-200 rounded-md` : '';
|
||||
};
|
||||
return (
|
||||
<Pane display="flex">
|
||||
<Link href="/">
|
||||
@@ -9,12 +15,18 @@ export default function Nav() {
|
||||
<img src="logo.png" className="h-12" />
|
||||
</h2>
|
||||
</Link>
|
||||
{/*<Link href="/onas">
|
||||
<h3 className="px-3 py-2 place-self-end">O nas</h3>
|
||||
<Link href="/">
|
||||
<h3 className={"px-3 py-2 place-self-end "+isActive('/')} style={{cursor: 'pointer'}}>Przekrój</h3>
|
||||
</Link>
|
||||
<Link href="/kontakt">
|
||||
<Link href="/cross">
|
||||
<h3 className={"px-3 py-2 place-self-end "+isActive('/cross')} style={{cursor: 'pointer'}}>Siatka</h3>
|
||||
</Link>
|
||||
<Link href="/uziomy">
|
||||
<h3 className={"px-3 py-2 place-self-end "+isActive('/uziomy')} style={{cursor: 'pointer'}}>Uziomy</h3>
|
||||
</Link>
|
||||
{/* <Link href="/kontakt">
|
||||
<h3 className="px-3 py-2 place-self-end">Kontakt</h3>
|
||||
</Link>*/}
|
||||
</Link> */}
|
||||
</Pane>
|
||||
);
|
||||
}
|
||||
|
||||
158
components/ui/Layout.js
Normal file
158
components/ui/Layout.js
Normal file
@@ -0,0 +1,158 @@
|
||||
import { useState } from 'react';
|
||||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useSession, signOut } from 'next-auth/react';
|
||||
import {
|
||||
HomeIcon,
|
||||
Squares2X2Icon as GridIcon,
|
||||
BoltIcon as LightningBoltIcon,
|
||||
Bars3Icon as MenuIcon,
|
||||
XMarkIcon as XIcon,
|
||||
UserIcon,
|
||||
ArrowRightOnRectangleIcon as LogoutIcon,
|
||||
TruckIcon,
|
||||
CpuChipIcon
|
||||
} from '@heroicons/react/24/outline';
|
||||
|
||||
const navigationItems = [
|
||||
{ name: 'Przekrój terenu', href: '/', icon: HomeIcon },
|
||||
{ name: 'Siatka', href: '/cross', icon: GridIcon },
|
||||
{ name: 'Uziomy', href: '/uziomy', icon: LightningBoltIcon },
|
||||
{ name: 'Spadki napięcia', href: '/spadki', icon: CpuChipIcon },
|
||||
{ name: 'Śledzenie przesyłek', href: '/tracking', icon: TruckIcon },
|
||||
];
|
||||
|
||||
export default function Layout({ children, title = 'Wastpol' }) {
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const { data: session } = useSession();
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-blue-50">
|
||||
<Head>
|
||||
<title>{title}</title>
|
||||
<meta name="description" content="Professional electrical engineering tools" />
|
||||
<link rel="icon" href="/icon.png" />
|
||||
</Head>
|
||||
|
||||
{/* Mobile sidebar */}
|
||||
<div className={`fixed inset-0 z-50 lg:hidden ${sidebarOpen ? 'block' : 'hidden'}`}>
|
||||
<div className="fixed inset-0 bg-gray-600 bg-opacity-75" onClick={() => setSidebarOpen(false)} />
|
||||
<div className="fixed inset-y-0 left-0 flex w-64 flex-col bg-white shadow-xl">
|
||||
<div className="flex h-16 items-center justify-between px-4 border-b border-gray-200">
|
||||
<img className="h-8 w-auto" src="/logo.png" alt="Wastpol" />
|
||||
<button
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
className="text-gray-500 hover:text-gray-700"
|
||||
>
|
||||
<XIcon className="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
<nav className="flex-1 px-2 py-4 space-y-1">
|
||||
{navigationItems.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const isActive = router.pathname === item.href;
|
||||
return (
|
||||
<Link key={item.name} href={item.href}>
|
||||
<a
|
||||
className={`group flex items-center px-2 py-2 text-sm font-medium rounded-md transition-colors ${
|
||||
isActive
|
||||
? 'bg-blue-100 text-blue-900 border-r-2 border-blue-500'
|
||||
: 'text-gray-700 hover:bg-gray-100 hover:text-gray-900'
|
||||
}`}
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
>
|
||||
<Icon className="mr-3 h-5 w-5 flex-shrink-0" />
|
||||
{item.name}
|
||||
</a>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Desktop layout */}
|
||||
<div className="flex">
|
||||
{/* Desktop sidebar */}
|
||||
<div className="hidden lg:flex lg:flex-shrink-0">
|
||||
<div className="flex w-64 flex-col">
|
||||
<div className="flex min-h-0 flex-1 flex-col bg-white border-r border-gray-200 shadow-sm">
|
||||
<div className="flex h-16 items-center justify-center px-4 border-b border-gray-200 bg-gradient-to-r from-blue-600 to-blue-700">
|
||||
<img className="h-8 w-auto filter brightness-0 invert" src="/logo.png" alt="Wastpol" />
|
||||
{/* <span className="ml-2 text-lg font-semibold text-white">Wastpol</span> */}
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col overflow-y-auto">
|
||||
<nav className="flex-1 px-2 py-4 space-y-1">
|
||||
{navigationItems.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const isActive = router.pathname === item.href;
|
||||
return (
|
||||
<Link key={item.name} href={item.href}>
|
||||
<a
|
||||
className={`group flex items-center px-2 py-2 text-sm font-medium rounded-md transition-all duration-200 ${
|
||||
isActive
|
||||
? 'bg-blue-100 text-blue-900 border-r-2 border-blue-500 shadow-sm'
|
||||
: 'text-gray-700 hover:bg-gray-100 hover:text-gray-900 hover:shadow-sm'
|
||||
}`}
|
||||
>
|
||||
<Icon className={`mr-3 h-5 w-5 flex-shrink-0 transition-colors ${
|
||||
isActive ? 'text-blue-600' : 'text-gray-400 group-hover:text-gray-500'
|
||||
}`} />
|
||||
{item.name}
|
||||
</a>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main content */}
|
||||
<div className="flex flex-1 flex-col">
|
||||
{/* Top navbar */}
|
||||
<div className="relative z-10 flex h-16 flex-shrink-0 bg-white shadow-sm border-b border-gray-200">
|
||||
<button
|
||||
onClick={() => setSidebarOpen(true)}
|
||||
className="border-r border-gray-200 px-4 text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500 lg:hidden"
|
||||
>
|
||||
<MenuIcon className="h-6 w-6" />
|
||||
</button>
|
||||
|
||||
<div className="flex flex-1 justify-between px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex flex-1">
|
||||
{/* You can add search or breadcrumbs here */}
|
||||
</div>
|
||||
|
||||
{session && (
|
||||
<div className="ml-4 flex items-center md:ml-6">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex items-center space-x-2 text-sm text-gray-700">
|
||||
<UserIcon className="h-5 w-5 text-gray-400" />
|
||||
<span className="hidden sm:block">{session.user.email}</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => signOut()}
|
||||
className="inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors"
|
||||
>
|
||||
<LogoutIcon className="h-4 w-4 mr-2" />
|
||||
Wyloguj
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Page content */}
|
||||
<main className="flex-1 overflow-y-auto">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
179
components/ui/components.js
Normal file
179
components/ui/components.js
Normal file
@@ -0,0 +1,179 @@
|
||||
import React from 'react';
|
||||
|
||||
export const Card = ({ children, className = '', ...props }) => (
|
||||
<div
|
||||
className={`bg-white overflow-hidden shadow-sm border border-gray-200 rounded-xl transition-all duration-200 hover:shadow-md ${className}`}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const CardHeader = ({ children, className = '', ...props }) => (
|
||||
<div className={`px-6 py-4 border-b border-gray-200 ${className}`} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const CardContent = ({ children, className = '', ...props }) => (
|
||||
<div className={`px-6 py-4 ${className}`} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const CardTitle = ({ children, className = '', ...props }) => (
|
||||
<h3 className={`text-lg font-semibold text-gray-900 ${className}`} {...props}>
|
||||
{children}
|
||||
</h3>
|
||||
);
|
||||
|
||||
export const CardDescription = ({ children, className = '', ...props }) => (
|
||||
<p className={`text-sm text-gray-600 mt-1 ${className}`} {...props}>
|
||||
{children}
|
||||
</p>
|
||||
);
|
||||
|
||||
export const Button = ({
|
||||
children,
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
className = '',
|
||||
disabled = false,
|
||||
loading = false,
|
||||
...props
|
||||
}) => {
|
||||
const baseStyles = 'inline-flex items-center justify-center rounded-lg font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed';
|
||||
|
||||
const variants = {
|
||||
primary: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500 shadow-sm hover:shadow-md',
|
||||
secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200 focus:ring-gray-500 border border-gray-300',
|
||||
outline: 'border border-gray-300 text-gray-700 bg-white hover:bg-gray-50 focus:ring-blue-500',
|
||||
ghost: 'text-gray-700 hover:bg-gray-100 focus:ring-gray-500',
|
||||
danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500',
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
sm: 'px-3 py-2 text-sm',
|
||||
md: 'px-4 py-2 text-sm',
|
||||
lg: 'px-6 py-3 text-base',
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
className={`${baseStyles} ${variants[variant]} ${sizes[size]} ${className}`}
|
||||
disabled={disabled || loading}
|
||||
{...props}
|
||||
>
|
||||
{loading && (
|
||||
<svg className="animate-spin -ml-1 mr-2 h-4 w-4" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
|
||||
</svg>
|
||||
)}
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export const Input = ({ label, error, className = '', ...props }) => (
|
||||
<div className="space-y-1">
|
||||
{label && (
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<input
|
||||
className={`block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 transition-colors ${
|
||||
error ? 'border-red-300 focus:border-red-500 focus:ring-red-500' : ''
|
||||
} ${className}`}
|
||||
{...props}
|
||||
/>
|
||||
{error && (
|
||||
<p className="text-sm text-red-600">{error}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Textarea = ({ label, error, className = '', ...props }) => (
|
||||
<div className="space-y-1">
|
||||
{label && (
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<textarea
|
||||
className={`block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 transition-colors ${
|
||||
error ? 'border-red-300 focus:border-red-500 focus:ring-red-500' : ''
|
||||
} ${className}`}
|
||||
{...props}
|
||||
/>
|
||||
{error && (
|
||||
<p className="text-sm text-red-600">{error}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Badge = ({ children, variant = 'default', className = '' }) => {
|
||||
const variants = {
|
||||
default: 'bg-gray-100 text-gray-800',
|
||||
success: 'bg-green-100 text-green-800',
|
||||
warning: 'bg-yellow-100 text-yellow-800',
|
||||
error: 'bg-red-100 text-red-800',
|
||||
info: 'bg-blue-100 text-blue-800',
|
||||
};
|
||||
|
||||
return (
|
||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${variants[variant]} ${className}`}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export const Alert = ({ children, variant = 'info', className = '' }) => {
|
||||
const variants = {
|
||||
info: 'bg-blue-50 border-blue-200 text-blue-800',
|
||||
success: 'bg-green-50 border-green-200 text-green-800',
|
||||
warning: 'bg-yellow-50 border-yellow-200 text-yellow-800',
|
||||
error: 'bg-red-50 border-red-200 text-red-800',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`border-l-4 p-4 rounded-lg ${variants[variant]} ${className}`}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Tabs = ({ children, value, onValueChange }) => (
|
||||
<div className="w-full">
|
||||
<div className="border-b border-gray-200">
|
||||
<nav className="-mb-px flex space-x-8">
|
||||
{React.Children.map(children, (child, index) =>
|
||||
React.cloneElement(child, {
|
||||
isActive: value === index,
|
||||
onClick: () => onValueChange(index)
|
||||
})
|
||||
)}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const TabsTrigger = ({ children, isActive, onClick }) => (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm transition-colors ${
|
||||
isActive
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
|
||||
export const TabsContent = ({ children, isActive }) => (
|
||||
<div className={`pt-6 ${isActive ? 'block' : 'hidden'}`}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
92
cross.py
Normal file
92
cross.py
Normal file
@@ -0,0 +1,92 @@
|
||||
import sys
|
||||
import ezdxf
|
||||
import math
|
||||
|
||||
minx = 10000000000000
|
||||
miny = 10000000000000
|
||||
maxx = 0
|
||||
maxy = 0
|
||||
|
||||
if len(sys.argv) > 0:
|
||||
skala = int(sys.argv[1])
|
||||
path = (sys.argv[2])
|
||||
else:
|
||||
skala = 50
|
||||
|
||||
alt = 0
|
||||
|
||||
fileName = path
|
||||
|
||||
def drawCross(x, y):
|
||||
msp.add_text(
|
||||
"X:"+str(x), dxfattribs={
|
||||
"layer": "wsp", "height": 1, "style": "Arial"
|
||||
}).set_pos((x+0.5, y+0.5))
|
||||
msp.add_text(
|
||||
"Y:"+str(y), dxfattribs={
|
||||
"layer": "wsp", "height": 1, "style": "Arial"
|
||||
}).set_pos((x+0.5, y-1.5))
|
||||
msp.add_line((x-2.5, y), (x+2.5, y), dxfattribs={"layer": "siatka"})
|
||||
msp.add_line((x, y-2.5), (x, y+2.5), dxfattribs={"layer": "siatka"})
|
||||
|
||||
try:
|
||||
doc = ezdxf.readfile(fileName)
|
||||
# iterate over all entities in modelspace
|
||||
msp = doc.modelspace()
|
||||
#doc.layers.add(name="siatka", color=0, linetype="SOLID")
|
||||
for e in msp:
|
||||
if e.dxftype() == "LINE":
|
||||
if e.dxf.start[0] < minx:
|
||||
minx = e.dxf.start[0]
|
||||
if e.dxf.end[0] < minx:
|
||||
minx = e.dxf.end[0]
|
||||
if e.dxf.start[1] < miny:
|
||||
miny = e.dxf.start[1]
|
||||
if e.dxf.end[1] < miny:
|
||||
miny = e.dxf.end[1]
|
||||
|
||||
if e.dxf.start[0] > maxx:
|
||||
maxx = e.dxf.start[0]
|
||||
if e.dxf.end[0] > maxx:
|
||||
maxx = e.dxf.end[0]
|
||||
if e.dxf.start[1] > maxy:
|
||||
maxy = e.dxf.start[1]
|
||||
if e.dxf.end[1] > maxy:
|
||||
maxy = e.dxf.end[1]
|
||||
elif e.dxftype() == "POLYLINE":
|
||||
for point in e.points():
|
||||
if point[0] < minx:
|
||||
minx = point[0]
|
||||
if point[1] < miny:
|
||||
miny = point[1]
|
||||
if point[0] > maxx:
|
||||
maxx = point[0]
|
||||
if point[1] > maxy:
|
||||
maxy = point[1]
|
||||
|
||||
|
||||
ys = (math.floor(miny/100)*100) - (50 * alt)
|
||||
ye = (math.ceil(maxy/100)*100) - (50 * alt)
|
||||
xs = (math.floor((minx)/100)*100) - (50 * alt)
|
||||
xe = (math.ceil((maxx)/100)*100) - (50 * alt)
|
||||
|
||||
lenX = (xe - xs) + 100
|
||||
lenY = (ye - ys) + 100
|
||||
|
||||
for x in range(0, int(lenX/skala)):
|
||||
for y in range(0, int(lenY/skala)):
|
||||
drawCross(xs+(x*skala),ys+(y*skala))
|
||||
|
||||
|
||||
print(fileName)
|
||||
doc.saveas(fileName[:-4]+"_s.dxf")
|
||||
doc.saveas("public/cross.dxf")
|
||||
|
||||
|
||||
except IOError:
|
||||
print(f"Not a DXF file or a generic I/O error.")
|
||||
sys.exit(1)
|
||||
except ezdxf.DXFStructureError:
|
||||
print(f"Invalid or corrupted DXF file.")
|
||||
sys.exit(2)
|
||||
|
||||
BIN
dry_input_15.docx
Normal file
BIN
dry_input_15.docx
Normal file
Binary file not shown.
BIN
dry_input_3.docx
Normal file
BIN
dry_input_3.docx
Normal file
Binary file not shown.
@@ -2,7 +2,7 @@ module.exports = {
|
||||
apps : [{
|
||||
name:'kroj',
|
||||
script: 'npm',
|
||||
args:'start',
|
||||
args:'run start',
|
||||
watch: ['pages'],
|
||||
ignore_watch : ['public', 'P.txt']
|
||||
}]
|
||||
|
||||
9620
package-lock.json
generated
9620
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@@ -7,23 +7,31 @@
|
||||
"export": "next build && next export",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"deploy": "git pull && npm i && next build && pm2 restart kroj"
|
||||
"deploy": "git pull && npm i && next build && pm2 restart kroj",
|
||||
"buildRun": "next build && pm2 restart kroj"
|
||||
},
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"@socialgouv/matomo-next": "^1.3.0",
|
||||
"@supercharge/request-ip": "^1.1.2",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"axios": "^0.26.0",
|
||||
"child_process": "^1.0.2",
|
||||
"cors": "^2.8.5",
|
||||
"date-fns": "^2.30.0",
|
||||
"docxtemplater": "^3.40.3",
|
||||
"evergreen-ui": "^6.8.2",
|
||||
"formidable": "^2.1.1",
|
||||
"next": "^12.0.10",
|
||||
"next-auth": "^4.2.1",
|
||||
"nodemailer": "^6.7.2",
|
||||
"pizzip": "^3.1.4",
|
||||
"react": "17.0.2",
|
||||
"react-datepicker": "^4.21.0",
|
||||
"react-dom": "17.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.4.0",
|
||||
"cypress": "^9.5.0",
|
||||
"eslint": "8.4.1",
|
||||
"eslint-config-next": "12.0.7",
|
||||
"postcss": "^8.4.5",
|
||||
|
||||
56
pages/api/external.js
Normal file
56
pages/api/external.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import Cors from "cors";
|
||||
|
||||
import fs from "fs";
|
||||
|
||||
// Initializing the cors middleware
|
||||
// You can read more about the available options here: https://github.com/expressjs/cors#configuration-options
|
||||
const cors = Cors({
|
||||
methods: ["POST"],
|
||||
});
|
||||
|
||||
// Helper method to wait for a middleware to execute before continuing
|
||||
// And to throw an error when an error happens in a middleware
|
||||
function runMiddleware(req, res, fn) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fn(req, res, (result) => {
|
||||
if (result instanceof Error) {
|
||||
return reject(result);
|
||||
}
|
||||
|
||||
return resolve(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default async function handler(req, res) {
|
||||
// Run the middleware
|
||||
await runMiddleware(req, res, cors);
|
||||
|
||||
const id = Math.floor(Math.random() * 90000) + 10000;
|
||||
const textData = req.body;
|
||||
|
||||
let data = textData.split(",");
|
||||
|
||||
let text = "Próbkowanie: 1\nSegment 0: w dół\nX: Y, Z\n";
|
||||
let i = 1;
|
||||
for (let snippet of data) {
|
||||
if (i % 3 != 0) {
|
||||
text += snippet + ",";
|
||||
} else {
|
||||
text += snippet + "\n";
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// console.log(text);
|
||||
|
||||
fs.writeFile(`externals/${id}.txt`, text, (err) => {
|
||||
if (err) {
|
||||
res.status(418);
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
// console.log(textData);
|
||||
res.status(200).send({ url: `https://test.wastpol.pl/?external=tru&id=${id}` });
|
||||
}
|
||||
86
pages/api/generateDocx.js
Normal file
86
pages/api/generateDocx.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import PizZip from "pizzip";
|
||||
import Docxtemplater from "docxtemplater";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
export default async (req, res) => {
|
||||
if (req.method === "POST") {
|
||||
// Get the data from the request body
|
||||
const data = req.body;
|
||||
|
||||
// log stats
|
||||
let ip = req.body.ip;
|
||||
if (ip === undefined)
|
||||
ip = req.headers["x-forwarded-for"] + " on " + req.headers["user-agent"];
|
||||
let date_time = new Date();
|
||||
let string =
|
||||
fs.readFileSync("ground_log.txt", "utf8") +
|
||||
"\n" +
|
||||
"[" +
|
||||
date_time +
|
||||
"]" +
|
||||
ip +
|
||||
" : " +
|
||||
data.geo_data +
|
||||
" : " +
|
||||
data.in_city;
|
||||
fs.writeFile("ground_log.txt", string, function (err) {
|
||||
if (err) {
|
||||
return console.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
//get wet coefficient
|
||||
let input_docx;
|
||||
if (data.wet_coef == 1.6) {
|
||||
if (data.rod_len == 2) input_docx = "wet_input_15.docx";
|
||||
else if (data.rod_len == 3) input_docx = "wet_input_3.docx";
|
||||
} else {
|
||||
if (data.rod_len == 2) input_docx = "dry_input_15.docx";
|
||||
else if (data.rod_len == 3) input_docx = "dry_input_3.docx";
|
||||
}
|
||||
|
||||
// Load the docx file as binary content
|
||||
const content = fs.readFileSync(
|
||||
path.resolve(process.cwd(), input_docx),
|
||||
"binary"
|
||||
);
|
||||
|
||||
const zip = new PizZip(content);
|
||||
|
||||
const doc = new Docxtemplater(zip, {
|
||||
paragraphLoop: true,
|
||||
linebreaks: true,
|
||||
});
|
||||
|
||||
try {
|
||||
// Render the document with data
|
||||
console.log(data);
|
||||
doc.render(data);
|
||||
|
||||
// Generate the document as a buffer
|
||||
const buf = doc.getZip().generate({
|
||||
type: "nodebuffer",
|
||||
compression: "DEFLATE",
|
||||
});
|
||||
|
||||
// Set headers for downloading the file
|
||||
res.setHeader("Content-Disposition", "attachment; filename=opis.docx");
|
||||
res.setHeader(
|
||||
"Content-Type",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||
);
|
||||
|
||||
// Send the buffer as a response
|
||||
res.send(buf);
|
||||
} catch (error) {
|
||||
// Handle errors
|
||||
console.error(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
} else {
|
||||
// Handle non-POST requests
|
||||
res.setHeader("Allow", ["POST"]);
|
||||
res.status(405).end(`Method ${req.method} Not Allowed`);
|
||||
}
|
||||
};
|
||||
39
pages/api/generateDxf.js
Normal file
39
pages/api/generateDxf.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { exec } from 'child_process';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export default function handler(req, res) {
|
||||
if (req.method === 'POST') {
|
||||
// Get the input data from the request body
|
||||
const data = req.body;
|
||||
|
||||
// Prepare arguments to pass to the Python script if needed
|
||||
const scriptArgs = data.args || [];
|
||||
console.log(scriptArgs)
|
||||
const Arguments = "\"" + scriptArgs.join('" "') + "\"";
|
||||
|
||||
// Run the Python script and pass the arguments
|
||||
exec(`python3 uziom.py ${Arguments}`, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
// Handle execution errors
|
||||
console.error(`exec error: ${error}`);
|
||||
return res.status(500).json({ message: 'Internal server error' });
|
||||
}
|
||||
|
||||
// The output file needs to have been created by the Python script
|
||||
const filePath = path.resolve('./public/uziom.dxf');
|
||||
const fileName = 'uziom.dxf';
|
||||
|
||||
// Set headers for file download
|
||||
res.setHeader('Content-Disposition', `attachment; filename=${fileName}`);
|
||||
res.setHeader('Content-Type', 'application/dxf');
|
||||
|
||||
// Read and send the file data
|
||||
const fileStream = fs.createReadStream(filePath);
|
||||
fileStream.pipe(res);
|
||||
});
|
||||
} else {
|
||||
// Handle any other HTTP methods if necessary
|
||||
res.status(405).json({ message: 'Method not allowed' });
|
||||
}
|
||||
}
|
||||
11
pages/api/readtext.js
Normal file
11
pages/api/readtext.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import fs from "fs";
|
||||
|
||||
export default function (req, res) {
|
||||
fs.readFile(`externals/${req.body.id}.txt`, (err, data) => {
|
||||
if (err) throw err;
|
||||
|
||||
res
|
||||
.status(200)
|
||||
.send({ data: data.toString().slice(0, data.toString().length - 1) });
|
||||
});
|
||||
}
|
||||
191
pages/api/spadki.js
Normal file
191
pages/api/spadki.js
Normal file
@@ -0,0 +1,191 @@
|
||||
import { getSession } from 'next-auth/react';
|
||||
|
||||
export default async function handler(req, res) {
|
||||
// Authentication check
|
||||
const session = await getSession({ req });
|
||||
if (!session) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
if (req.method !== 'POST') {
|
||||
return res.status(405).json({ error: 'Method not allowed' });
|
||||
}
|
||||
|
||||
try {
|
||||
const { objects, cables } = req.body;
|
||||
|
||||
// Validate input data
|
||||
if (!objects || !cables) {
|
||||
return res.status(400).json({ error: 'Missing objects or cables data' });
|
||||
}
|
||||
|
||||
// Find transformer (source)
|
||||
const transformer = objects.find(obj => obj.type === 'transformer');
|
||||
if (!transformer) {
|
||||
return res.status(400).json({ error: 'No transformer found in the system' });
|
||||
}
|
||||
|
||||
// Calculate voltage drops for each path
|
||||
const results = calculateVoltageDrops(transformer, objects, cables);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: results,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Voltage drop calculation error:', error);
|
||||
res.status(500).json({
|
||||
error: 'Internal server error',
|
||||
details: process.env.NODE_ENV === 'development' ? error.message : undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function calculateVoltageDrops(transformer, objects, cables) {
|
||||
const results = {
|
||||
transformer: transformer,
|
||||
paths: [],
|
||||
summary: {
|
||||
totalPower: 0,
|
||||
maxVoltageDrop: 0,
|
||||
minVoltage: transformer.bottomVoltage
|
||||
}
|
||||
};
|
||||
|
||||
// Cable resistance values (Ω/km) - simplified values for common cable types
|
||||
const cableResistance = {
|
||||
'YAKY': {
|
||||
16: 1.15, 25: 0.727, 35: 0.524, 50: 0.387, 70: 0.268, 95: 0.193, 120: 0.153, 150: 0.124, 185: 0.099, 240: 0.075
|
||||
},
|
||||
'NA2XY-J': {
|
||||
16: 1.15, 25: 0.727, 35: 0.524, 50: 0.387, 70: 0.268, 95: 0.193, 120: 0.153, 150: 0.124, 185: 0.099, 240: 0.075
|
||||
},
|
||||
'AL': {
|
||||
16: 1.91, 25: 1.20, 35: 0.868, 50: 0.641, 70: 0.443, 95: 0.320, 120: 0.253, 150: 0.206, 185: 0.164, 240: 0.125
|
||||
},
|
||||
'AsXSn': {
|
||||
16: 1.91, 25: 1.20, 35: 0.868, 50: 0.641, 70: 0.443, 95: 0.320, 120: 0.253, 150: 0.206, 185: 0.164, 240: 0.125
|
||||
}
|
||||
};
|
||||
|
||||
// Find all paths from transformer to end connections
|
||||
const paths = findAllPaths(transformer, objects, cables);
|
||||
|
||||
paths.forEach((path, index) => {
|
||||
let totalVoltageDrop = 0;
|
||||
let pathPower = 0;
|
||||
const pathDetails = [];
|
||||
|
||||
// Calculate for each segment in the path
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
const fromNode = path[i];
|
||||
const toNode = path[i + 1];
|
||||
|
||||
// Find the cable connecting these nodes
|
||||
const cable = cables.find(c =>
|
||||
(c.from === fromNode.id && c.to === toNode.id) ||
|
||||
(c.from === toNode.id && c.to === fromNode.id)
|
||||
);
|
||||
|
||||
if (cable) {
|
||||
// Get cable resistance
|
||||
const resistance = getCableResistance(cable, cableResistance);
|
||||
|
||||
// Calculate current (simplified - assuming balanced load)
|
||||
const nodePower = toNode.powerRating || 0;
|
||||
const current = nodePower > 0 ? (nodePower * 1000) / (Math.sqrt(3) * transformer.bottomVoltage * 0.9) : 0; // cos φ = 0.9
|
||||
|
||||
// Calculate voltage drop for this segment
|
||||
const segmentVoltageDrop = Math.sqrt(3) * current * resistance * (cable.length / 1000); // length in km
|
||||
|
||||
totalVoltageDrop += segmentVoltageDrop;
|
||||
pathPower += nodePower;
|
||||
|
||||
pathDetails.push({
|
||||
from: fromNode.name,
|
||||
to: toNode.name,
|
||||
cable: {
|
||||
label: cable.label,
|
||||
type: cable.cableType,
|
||||
crossSection: cable.crossSection,
|
||||
length: cable.length
|
||||
},
|
||||
current: current.toFixed(2),
|
||||
resistance: resistance.toFixed(4),
|
||||
voltageDrop: segmentVoltageDrop.toFixed(2),
|
||||
power: nodePower
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const finalVoltage = transformer.bottomVoltage - totalVoltageDrop;
|
||||
const voltageDropPercentage = (totalVoltageDrop / transformer.bottomVoltage) * 100;
|
||||
|
||||
results.paths.push({
|
||||
id: index + 1,
|
||||
endNode: path[path.length - 1].name,
|
||||
totalVoltageDrop: totalVoltageDrop.toFixed(2),
|
||||
finalVoltage: finalVoltage.toFixed(2),
|
||||
voltageDropPercentage: voltageDropPercentage.toFixed(2),
|
||||
pathPower: pathPower,
|
||||
segments: pathDetails,
|
||||
isValid: voltageDropPercentage <= 5 // 5% is typical limit
|
||||
});
|
||||
|
||||
results.summary.totalPower += pathPower;
|
||||
if (totalVoltageDrop > results.summary.maxVoltageDrop) {
|
||||
results.summary.maxVoltageDrop = totalVoltageDrop;
|
||||
results.summary.minVoltage = finalVoltage;
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function findAllPaths(transformer, objects, cables) {
|
||||
const paths = [];
|
||||
const visited = new Set();
|
||||
|
||||
function dfs(currentNode, currentPath) {
|
||||
visited.add(currentNode.id);
|
||||
currentPath.push(currentNode);
|
||||
|
||||
// If this is an end connection, save the path
|
||||
if (currentNode.type === 'end') {
|
||||
paths.push([...currentPath]);
|
||||
} else {
|
||||
// Find all connected nodes
|
||||
const connectedCables = cables.filter(cable =>
|
||||
cable.from === currentNode.id || cable.to === currentNode.id
|
||||
);
|
||||
|
||||
connectedCables.forEach(cable => {
|
||||
const nextNodeId = cable.from === currentNode.id ? cable.to : cable.from;
|
||||
const nextNode = objects.find(obj => obj.id === nextNodeId);
|
||||
|
||||
if (nextNode && !visited.has(nextNodeId)) {
|
||||
dfs(nextNode, currentPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
currentPath.pop();
|
||||
visited.delete(currentNode.id);
|
||||
}
|
||||
|
||||
dfs(transformer, []);
|
||||
return paths;
|
||||
}
|
||||
|
||||
function getCableResistance(cable, resistanceTable) {
|
||||
const cableType = cable.cableType || 'YAKY';
|
||||
const crossSection = cable.crossSection || 25;
|
||||
|
||||
if (resistanceTable[cableType] && resistanceTable[cableType][crossSection]) {
|
||||
return resistanceTable[cableType][crossSection];
|
||||
}
|
||||
|
||||
// Default resistance if not found
|
||||
return 1.0;
|
||||
}
|
||||
@@ -4,7 +4,7 @@ export default function (req, res) {
|
||||
//console.log(req.body);
|
||||
//file
|
||||
let textData = req.body.profil;
|
||||
let replacedData = textData.replace("<EFBFBD>", "o").replace("<EFBFBD>", "e");
|
||||
let replacedData = textData.replace(" ", "o").replace(" ", "e");
|
||||
console.log(replacedData);
|
||||
|
||||
var fs = require("fs");
|
||||
@@ -14,10 +14,32 @@ export default function (req, res) {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// log stats
|
||||
let ip = req.body.ip;
|
||||
if (ip === undefined)
|
||||
ip = req.headers["x-forwarded-for"] + " on " + req.headers["user-agent"];
|
||||
let date_time = new Date();
|
||||
let string =
|
||||
fs.readFileSync("profile_log.txt", "utf8") +
|
||||
"\n" +
|
||||
"[" +
|
||||
date_time +
|
||||
"]" +
|
||||
ip +
|
||||
" : " +
|
||||
replacedData
|
||||
fs.writeFile("profile_log.txt", string, function (err) {
|
||||
if (err) {
|
||||
return console.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//py
|
||||
let fileName = Math.floor(Math.random() * 9999) + 1000;
|
||||
|
||||
const python = spawn("python3", ["a.py", req.body.arguments.scale, fileName]);
|
||||
const python = spawn("python3", ["a.py", req.body.arguments.scale, req.body.arguments.elementOne, req.body.arguments.elementTwo]);
|
||||
|
||||
let dataToSend;
|
||||
python.stdout.on("data", function (data) {
|
||||
|
||||
105
pages/api/upload.js
Normal file
105
pages/api/upload.js
Normal file
@@ -0,0 +1,105 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
// import nextConnect from 'next-connect';
|
||||
// import { sendFile } from 'next/dist/server/send-file';
|
||||
|
||||
import formidable from "formidable";
|
||||
import { spawn } from "child_process";
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false,
|
||||
},
|
||||
};
|
||||
|
||||
export default async function (req, res) {
|
||||
const form = new formidable.IncomingForm();
|
||||
try {
|
||||
form.parse(req, async (err, fields, files) => {
|
||||
if (err) {
|
||||
return res.status(400).json({ message: err.message });
|
||||
}
|
||||
|
||||
console.log('doing the saving')
|
||||
|
||||
|
||||
const { filepath, originalFilename } = files.file;
|
||||
const dateNow = Date.now()
|
||||
const newFilename = `${Date.now()}-${originalFilename}`;
|
||||
const newPath = path.join(
|
||||
process.cwd(),
|
||||
"public",
|
||||
"uploads",
|
||||
newFilename
|
||||
);
|
||||
|
||||
console.log('i tried so hart')
|
||||
|
||||
const readStream = fs.createReadStream(filepath);
|
||||
const writeStream = fs.createWriteStream(newPath);
|
||||
|
||||
readStream.on('error', function(err) {
|
||||
console.error('Error while reading file.', err);
|
||||
res.status(500).send('Error while uploading file.');
|
||||
});
|
||||
|
||||
writeStream.on('error', function(err) {
|
||||
console.error('Error while writing file.', err);
|
||||
res.status(500).send('Error while uploading file.');
|
||||
});
|
||||
|
||||
writeStream.on('finish', function() {
|
||||
console.log('File copied successfully');
|
||||
let dataToSend;
|
||||
python.stdout.on("data", function (data) {
|
||||
console.log("Pipe data from python script ...");
|
||||
dataToSend = data.toString();
|
||||
console.log(dataToSend);
|
||||
});
|
||||
python.stderr.on("data", (data) => {
|
||||
console.error(`stderr: ${data}`);
|
||||
res.send("Py Error: " + data);
|
||||
});
|
||||
// in close event we are sure that stream from child process is closed
|
||||
python.on("close", (code) => {
|
||||
console.log(`child process close all stdio with code ${code}`);
|
||||
// send data to browser
|
||||
console.log(dataToSend);
|
||||
console.log("done");
|
||||
try {
|
||||
// const handler = nextConnect();
|
||||
|
||||
// handler.get(async (req, res) => {
|
||||
const filename = newFilename; // get the filename from the query string
|
||||
const filePath = path.join(process.cwd(), 'public', 'uploads', filename);
|
||||
|
||||
console.log(newFilename, filename, filePath, dataToSend)
|
||||
|
||||
res.status(200).send({ filename: newFilename, filepath:filePath, data: dataToSend });
|
||||
// sendFile(req, filePath); // send the file as a response
|
||||
// });
|
||||
} catch (e) {
|
||||
console.log("child process end");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
readStream.pipe(writeStream);
|
||||
console.log('and got so far')
|
||||
|
||||
let fileName = Math.floor(Math.random() * 9999) + 1000;
|
||||
|
||||
console.log('starting the engines', newPath)
|
||||
|
||||
const python = spawn("python3", ["cross.py", 50, newPath]);
|
||||
console.log('oui')
|
||||
|
||||
|
||||
|
||||
// return res.status(200).json({ message: "File uploaded successfully" });
|
||||
});
|
||||
} catch (err) {
|
||||
console.log("err: ", err)
|
||||
return res.status(500).json({ message: err.message });
|
||||
}
|
||||
}
|
||||
247
pages/cross.js
Normal file
247
pages/cross.js
Normal file
@@ -0,0 +1,247 @@
|
||||
import { useState, useCallback } from "react";
|
||||
import { useSession, signIn } from "next-auth/react";
|
||||
import Layout from "../components/ui/Layout";
|
||||
import { Card, CardHeader, CardContent, CardTitle, CardDescription, Button, Alert } from "../components/ui/components";
|
||||
import {
|
||||
FileUploader,
|
||||
FileCard,
|
||||
} from "evergreen-ui";
|
||||
import { CloudArrowUpIcon as CloudUploadIcon, ArrowDownTrayIcon as DownloadIcon, Squares2X2Icon as GridIcon } from '@heroicons/react/24/outline';
|
||||
import axios from "axios";
|
||||
|
||||
export default function Cross() {
|
||||
const { data: session } = useSession();
|
||||
const [files, setFiles] = useState([]);
|
||||
const [fileRejections, setFileRejections] = useState([]);
|
||||
const [fileData, setFileData] = useState(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleChange = useCallback((files) => setFiles([files[0]]), []);
|
||||
const handleRejected = useCallback(
|
||||
(fileRejections) => setFileRejections([fileRejections[0]]),
|
||||
[]
|
||||
);
|
||||
const handleRemove = useCallback(() => {
|
||||
setFiles([]);
|
||||
setFileRejections([]);
|
||||
}, []);
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
|
||||
console.log("Files:", files);
|
||||
if (files.length === 0) {
|
||||
console.log("No files selected");
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("files", files[0]);
|
||||
|
||||
axios
|
||||
.post("/api/cross", formData, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
console.log(response.data);
|
||||
setFileData(response.data);
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const handleDownload = () => {
|
||||
if (fileData) {
|
||||
document.getElementById("down").download = fileData.filename;
|
||||
document.getElementById("down").href = "cross.dxf";
|
||||
document.getElementById("down").click();
|
||||
}
|
||||
};
|
||||
|
||||
if (session) {
|
||||
return (
|
||||
<Layout title="Wastpol - Generator siatki">
|
||||
<div className="p-6 max-w-4xl mx-auto">
|
||||
{/* Page Header */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center space-x-3 mb-4">
|
||||
<div className="p-3 bg-blue-100 rounded-lg">
|
||||
<GridIcon className="w-8 h-8 text-blue-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900">Generator siatki</h1>
|
||||
<p className="text-gray-600">Dodaj siatkę do mapy w pliku DXF</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
{/* Upload Section */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<CloudUploadIcon className="w-5 h-5 text-blue-600" />
|
||||
<span>Prześlij plik</span>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Wybierz plik DXF do przetworzenia (maksymalnie 50 MB)
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
<FileUploader
|
||||
label="Przeciągnij i upuść plik lub kliknij aby wybrać"
|
||||
description="Obsługiwane formaty: DXF (max 50 MB)"
|
||||
maxSizeInBytes={50 * 1024 ** 2}
|
||||
maxFiles={1}
|
||||
onChange={handleChange}
|
||||
onRejected={handleRejected}
|
||||
renderFile={(file) => {
|
||||
const { name, size, type } = file;
|
||||
const fileRejection = fileRejections.find(
|
||||
(fileRejection) => fileRejection.file === file
|
||||
);
|
||||
const { message } = fileRejection || {};
|
||||
return (
|
||||
<FileCard
|
||||
key={name}
|
||||
isInvalid={fileRejection != null}
|
||||
name={name}
|
||||
onRemove={handleRemove}
|
||||
sizeInBytes={size}
|
||||
type={type}
|
||||
validationMessage={message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
values={files}
|
||||
/>
|
||||
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
disabled={files.length === 0 || isLoading}
|
||||
loading={isLoading}
|
||||
className="w-full"
|
||||
>
|
||||
{isLoading ? 'Przetwarzanie...' : 'Dodaj siatkę'}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Results Section */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<DownloadIcon className="w-5 h-5 text-green-600" />
|
||||
<span>Wynik</span>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Pobierz wygenerowany plik siatki
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{fileData ? (
|
||||
<div className="space-y-4">
|
||||
<Alert variant="success">
|
||||
<div className="flex items-center space-x-2">
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
<span>Plik został pomyślnie przetworzony!</span>
|
||||
</div>
|
||||
</Alert>
|
||||
|
||||
<Button
|
||||
onClick={handleDownload}
|
||||
variant="primary"
|
||||
className="w-full"
|
||||
>
|
||||
<DownloadIcon className="w-5 h-5 mr-2" />
|
||||
Pobierz siatką DXF
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<div className="mx-auto w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mb-4">
|
||||
<GridIcon className="w-8 h-8 text-gray-400" />
|
||||
</div>
|
||||
<p className="text-gray-500">Prześlij plik aby rozpocząć przetwarzanie</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Instructions */}
|
||||
<Card className="mt-8">
|
||||
<CardHeader>
|
||||
<CardTitle>Instrukcje</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-sm font-semibold text-blue-600">1</span>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium text-gray-900">Prześlij plik</h4>
|
||||
<p className="text-sm text-gray-600">Wybierz plik DXF z projektem</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-sm font-semibold text-blue-600">2</span>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium text-gray-900">Przetwarzaj</h4>
|
||||
<p className="text-sm text-gray-600">Kliknij "Dodaj siatkę"</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-sm font-semibold text-blue-600">3</span>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium text-gray-900">Pobierz</h4>
|
||||
<p className="text-sm text-gray-600">Zapisz gotową siatkę DXF</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<a href="the.dxf" download="the.dxf" id="down" className="hidden" />
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center p-4">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader className="text-center">
|
||||
<div className="mx-auto w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mb-4">
|
||||
<img src="/logo.png" alt="Wastpol" className="w-10 h-10" />
|
||||
</div>
|
||||
<CardTitle className="text-2xl">Zaloguj się</CardTitle>
|
||||
<p className="text-gray-600 mt-2">Uzyskaj dostęp do generatora siatki</p>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button onClick={() => signIn()} className="w-full" size="lg">
|
||||
Zaloguj się
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
247
pages/cross_new.js
Normal file
247
pages/cross_new.js
Normal file
@@ -0,0 +1,247 @@
|
||||
import { useState, useCallback } from "react";
|
||||
import { useSession, signIn } from "next-auth/react";
|
||||
import Layout from "../components/ui/Layout";
|
||||
import { Card, CardHeader, CardContent, CardTitle, CardDescription, Button, Alert } from "../components/ui/components";
|
||||
import {
|
||||
FileUploader,
|
||||
FileCard,
|
||||
} from "evergreen-ui";
|
||||
import { CloudArrowUpIcon as CloudUploadIcon, ArrowDownTrayIcon as DownloadIcon, Squares2X2Icon as GridIcon } from '@heroicons/react/24/outline';
|
||||
import axios from "axios";
|
||||
|
||||
export default function Cross() {
|
||||
const { data: session } = useSession();
|
||||
const [files, setFiles] = useState([]);
|
||||
const [fileRejections, setFileRejections] = useState([]);
|
||||
const [fileData, setFileData] = useState(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleChange = useCallback((files) => setFiles([files[0]]), []);
|
||||
const handleRejected = useCallback(
|
||||
(fileRejections) => setFileRejections([fileRejections[0]]),
|
||||
[]
|
||||
);
|
||||
const handleRemove = useCallback(() => {
|
||||
setFiles([]);
|
||||
setFileRejections([]);
|
||||
}, []);
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
|
||||
console.log("Files:", files);
|
||||
if (files.length === 0) {
|
||||
console.log("No files selected");
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("files", files[0]);
|
||||
|
||||
axios
|
||||
.post("/api/cross", formData, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
console.log(response.data);
|
||||
setFileData(response.data);
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const handleDownload = () => {
|
||||
if (fileData) {
|
||||
document.getElementById("down").download = fileData.filename;
|
||||
document.getElementById("down").href = "cross.dxf";
|
||||
document.getElementById("down").click();
|
||||
}
|
||||
};
|
||||
|
||||
if (session) {
|
||||
return (
|
||||
<Layout title="Wastpol - Generator siatki">
|
||||
<div className="p-6 max-w-4xl mx-auto">
|
||||
{/* Page Header */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center space-x-3 mb-4">
|
||||
<div className="p-3 bg-blue-100 rounded-lg">
|
||||
<GridIcon className="w-8 h-8 text-blue-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900">Generator siatki</h1>
|
||||
<p className="text-gray-600">Przekształć pliki DXF na siatki instalacyjne</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
{/* Upload Section */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<CloudUploadIcon className="w-5 h-5 text-blue-600" />
|
||||
<span>Prześlij plik</span>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Wybierz plik DXF do przetworzenia (maksymalnie 50 MB)
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
<FileUploader
|
||||
label="Przeciągnij i upuść plik lub kliknij aby wybrać"
|
||||
description="Obsługiwane formaty: DXF (max 50 MB)"
|
||||
maxSizeInBytes={50 * 1024 ** 2}
|
||||
maxFiles={1}
|
||||
onChange={handleChange}
|
||||
onRejected={handleRejected}
|
||||
renderFile={(file) => {
|
||||
const { name, size, type } = file;
|
||||
const fileRejection = fileRejections.find(
|
||||
(fileRejection) => fileRejection.file === file
|
||||
);
|
||||
const { message } = fileRejection || {};
|
||||
return (
|
||||
<FileCard
|
||||
key={name}
|
||||
isInvalid={fileRejection != null}
|
||||
name={name}
|
||||
onRemove={handleRemove}
|
||||
sizeInBytes={size}
|
||||
type={type}
|
||||
validationMessage={message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
values={files}
|
||||
/>
|
||||
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
disabled={files.length === 0 || isLoading}
|
||||
loading={isLoading}
|
||||
className="w-full"
|
||||
>
|
||||
{isLoading ? 'Przetwarzanie...' : 'Dodaj siatkę'}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Results Section */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<DownloadIcon className="w-5 h-5 text-green-600" />
|
||||
<span>Wynik</span>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Pobierz wygenerowany plik siatki
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{fileData ? (
|
||||
<div className="space-y-4">
|
||||
<Alert variant="success">
|
||||
<div className="flex items-center space-x-2">
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
<span>Plik został pomyślnie przetworzony!</span>
|
||||
</div>
|
||||
</Alert>
|
||||
|
||||
<Button
|
||||
onClick={handleDownload}
|
||||
variant="primary"
|
||||
className="w-full"
|
||||
>
|
||||
<DownloadIcon className="w-5 h-5 mr-2" />
|
||||
Pobierz siatką DXF
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<div className="mx-auto w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mb-4">
|
||||
<GridIcon className="w-8 h-8 text-gray-400" />
|
||||
</div>
|
||||
<p className="text-gray-500">Prześlij plik aby rozpocząć przetwarzanie</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Instructions */}
|
||||
<Card className="mt-8">
|
||||
<CardHeader>
|
||||
<CardTitle>Instrukcje</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-sm font-semibold text-blue-600">1</span>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium text-gray-900">Prześlij plik</h4>
|
||||
<p className="text-sm text-gray-600">Wybierz plik DXF z projektem</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-sm font-semibold text-blue-600">2</span>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium text-gray-900">Przetwarzaj</h4>
|
||||
<p className="text-sm text-gray-600">Kliknij "Dodaj siatkę"</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-sm font-semibold text-blue-600">3</span>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium text-gray-900">Pobierz</h4>
|
||||
<p className="text-sm text-gray-600">Zapisz gotową siatkę DXF</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<a href="the.dxf" download="the.dxf" id="down" className="hidden" />
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center p-4">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader className="text-center">
|
||||
<div className="mx-auto w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mb-4">
|
||||
<img src="/logo.png" alt="Wastpol" className="w-10 h-10" />
|
||||
</div>
|
||||
<CardTitle className="text-2xl">Zaloguj się</CardTitle>
|
||||
<p className="text-gray-600 mt-2">Uzyskaj dostęp do generatora siatki</p>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button onClick={() => signIn()} className="w-full" size="lg">
|
||||
Zaloguj się
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
174
pages/index.js
174
pages/index.js
@@ -1,93 +1,129 @@
|
||||
import Head from "next/head";
|
||||
import styles from "../styles/Home.module.css";
|
||||
import Header from "../components/templates/header";
|
||||
import { useState } from "react";
|
||||
import { useSession, signIn } from "next-auth/react";
|
||||
import Layout from "../components/ui/Layout";
|
||||
import { Card, CardHeader, CardContent, CardTitle, Button, Tabs, TabsTrigger, TabsContent } from "../components/ui/components";
|
||||
import Generator from "../components/templates/generator";
|
||||
import Manual from "../components/templates/manual";
|
||||
import Nav from "../components/templates/nav";
|
||||
import UserTop from "../components/templates/userTop";
|
||||
import Footer from "../components/templates/footer";
|
||||
import { useState } from "react";
|
||||
import { useSession, signIn, signOut } from "next-auth/react";
|
||||
import { Pane, Button, Tab, Tablist } from "evergreen-ui";
|
||||
import { ChartBarIcon, PencilIcon, ArrowRightOnRectangleIcon as LoginIcon } from '@heroicons/react/24/outline';
|
||||
|
||||
export default function Home() {
|
||||
const { data: session } = useSession();
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
const [tabs] = useState(["auto", "manual"]);
|
||||
const [realTabs] = useState([Generator, Manual]);
|
||||
const [selectedTab, setSelectedTab] = useState(0);
|
||||
|
||||
if (session) {
|
||||
return (
|
||||
<div className="">
|
||||
<Head>
|
||||
<title>Wastpol</title>
|
||||
</Head>
|
||||
|
||||
<div className="flex md:flex-row flex-col justify-between px-8 mt-2">
|
||||
<Nav />
|
||||
<UserTop session={session} />
|
||||
<Layout title="Wastpol - Profil przekroju terenu">
|
||||
<div className="p-6 max-w-7xl mx-auto">
|
||||
{/* Page Header */}
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">
|
||||
Generator profilu przekroju terenu
|
||||
</h1>
|
||||
<p className="text-gray-600">
|
||||
Stwórz profil terenu
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<main className="flex flex-1 flex-col items-center">
|
||||
<div className="flex flex-col items-center p-8 mt-12 rounded-md shadow-md transition-all duration-500 hover:shadow-xl">
|
||||
<Header />
|
||||
<Tablist>
|
||||
{tabs.map((tab, index) => (
|
||||
<Tab
|
||||
key={tab}
|
||||
id={tab}
|
||||
onSelect={() => setSelectedIndex(index)}
|
||||
isSelected={index === selectedIndex}
|
||||
>
|
||||
{tab}
|
||||
</Tab>
|
||||
))}
|
||||
</Tablist>
|
||||
<Pane>
|
||||
{realTabs.map((tab, index) =>
|
||||
index == 1 ? (
|
||||
<Pane
|
||||
key={tab}
|
||||
id={`panel-${tab}`}
|
||||
role="tabpanel"
|
||||
display={index === selectedIndex ? "block" : "none"}
|
||||
>
|
||||
<Manual />
|
||||
</Pane>
|
||||
) : (
|
||||
<Pane
|
||||
key={tab}
|
||||
id={`panel-${tab}`}
|
||||
role="tabpanel"
|
||||
display={index === selectedIndex ? "block" : "none"}
|
||||
>
|
||||
{/* Main Content Card */}
|
||||
<Card className="mb-6">
|
||||
<CardHeader className="border-b bg-gray-50">
|
||||
<Tabs value={selectedTab} onValueChange={setSelectedTab}>
|
||||
<TabsTrigger>
|
||||
<ChartBarIcon className="w-5 h-5 mr-2" />
|
||||
Automatyczny
|
||||
</TabsTrigger>
|
||||
<TabsTrigger>
|
||||
<PencilIcon className="w-5 h-5 mr-2" />
|
||||
Ręczny
|
||||
</TabsTrigger>
|
||||
</Tabs>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="p-0">
|
||||
<TabsContent isActive={selectedTab === 0}>
|
||||
<div className="p-6">
|
||||
<Generator />
|
||||
</Pane>
|
||||
)
|
||||
)}
|
||||
</Pane>
|
||||
</div>
|
||||
</main>
|
||||
</TabsContent>
|
||||
<TabsContent isActive={selectedTab === 1}>
|
||||
<div className="p-6">
|
||||
<Manual />
|
||||
</div>
|
||||
</TabsContent>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Info Cards */}
|
||||
{/* <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<Card>
|
||||
<CardContent>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="p-2 bg-blue-100 rounded-lg">
|
||||
<ChartBarIcon className="w-6 h-6 text-blue-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900">Automatyczny</h3>
|
||||
<p className="text-sm text-gray-600">Import danych z Geoportalu</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="p-2 bg-green-100 rounded-lg">
|
||||
<PencilIcon className="w-6 h-6 text-green-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900">Ręczny</h3>
|
||||
<p className="text-sm text-gray-600">Wprowadź punkty manualnie</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent>
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="p-2 bg-purple-100 rounded-lg">
|
||||
<svg className="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900">Export DXF</h3>
|
||||
<p className="text-sm text-gray-600">Pobierz gotowy rysunek</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div> */}
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid place-items-center h-screen">
|
||||
<Head>
|
||||
<title>Wastpol</title>
|
||||
</Head>
|
||||
<div className="flex flex-col justify-center">
|
||||
<h2 className="p-2">Nie zalogowano</h2>
|
||||
<br></br>
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center p-4">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader className="text-center">
|
||||
<div className="mx-auto w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mb-4">
|
||||
<img src="/logo.png" alt="Wastpol" className="w-10 h-10" />
|
||||
</div>
|
||||
<CardTitle className="text-2xl">Zaloguj się</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button
|
||||
onClick={() => signIn()}
|
||||
appearance="primary"
|
||||
// className="p-2 bg-slate-200 rounded-md"
|
||||
className="w-full"
|
||||
size="lg"
|
||||
>
|
||||
Zaloguj
|
||||
<LoginIcon className="w-5 h-5 mr-2" />
|
||||
Zaloguj się
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
157
pages/pdf.js
Normal file
157
pages/pdf.js
Normal file
@@ -0,0 +1,157 @@
|
||||
import Head from "next/head";
|
||||
import { useState, useCallback } from "react";
|
||||
import styles from "../styles/Home.module.css";
|
||||
import Header from "../components/templates/header";
|
||||
import Nav from "../components/templates/nav";
|
||||
import UserTop from "../components/templates/userTop";
|
||||
import Content from "../components/templates/content";
|
||||
import Footer from "../components/templates/footer";
|
||||
import { useSession, signIn, signOut } from "next-auth/react";
|
||||
import {
|
||||
Pane,
|
||||
TextInputField,
|
||||
TextareaField,
|
||||
Button,
|
||||
BuildIcon,
|
||||
toaster,
|
||||
Alert,
|
||||
FileUploader,
|
||||
FileCard,
|
||||
} from "evergreen-ui";
|
||||
|
||||
export default function Pdf() {
|
||||
const { data: session } = useSession();
|
||||
const [files, setFiles] = useState([]);
|
||||
const [fileRejections, setFileRejections] = useState([]);
|
||||
const handleChange = useCallback((files) => setFiles([files[0]]), []);
|
||||
const handleRejected = useCallback(
|
||||
(fileRejections) => setFileRejections([fileRejections[0]]),
|
||||
[]
|
||||
);
|
||||
const handleRemove = useCallback(() => {
|
||||
setFiles([]);
|
||||
setFileRejections([]);
|
||||
}, []);
|
||||
|
||||
if (session) {
|
||||
return (
|
||||
<div className="">
|
||||
<Head>
|
||||
<title>Wastpol</title>
|
||||
</Head>
|
||||
|
||||
<div className="flex md:flex-row flex-col justify-between px-8 mt-2">
|
||||
<Nav />
|
||||
<UserTop session={session} />
|
||||
</div>
|
||||
|
||||
<main className="flex flex-1 flex-col items-center">
|
||||
<Pane maxWidth={654}>
|
||||
<FileUploader
|
||||
label="Upload File"
|
||||
description="You can upload 1 file. File can be up to 50 MB."
|
||||
maxSizeInBytes={50 * 1024 ** 2}
|
||||
maxFiles={1}
|
||||
onChange={handleChange}
|
||||
onRejected={handleRejected}
|
||||
renderFile={(file) => {
|
||||
const { name, size, type } = file;
|
||||
const fileRejection = fileRejections.find(
|
||||
(fileRejection) => fileRejection.file === file
|
||||
);
|
||||
const { message } = fileRejection || {};
|
||||
return (
|
||||
<FileCard
|
||||
key={name}
|
||||
isInvalid={fileRejection != null}
|
||||
name={name}
|
||||
onRemove={handleRemove}
|
||||
sizeInBytes={size}
|
||||
type={type}
|
||||
validationMessage={message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
values={files}
|
||||
/>
|
||||
</Pane>
|
||||
|
||||
<Button
|
||||
marginY={8}
|
||||
marginRight={12}
|
||||
appearance="default"
|
||||
iconAfter={BuildIcon}
|
||||
onClick={(e) => {
|
||||
console.log("louding begins");
|
||||
}}
|
||||
>
|
||||
Przycisk
|
||||
</Button>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="grid place-items-center h-screen">
|
||||
<Head>
|
||||
<title>Wastpol</title>
|
||||
</Head>
|
||||
<div className="flex flex-col justify-center">
|
||||
<h2 className="p-2">Nie zalogowano</h2>
|
||||
<br></br>
|
||||
<Button
|
||||
onClick={() => signIn()}
|
||||
appearance="primary"
|
||||
// className="p-2 bg-slate-200 rounded-md"
|
||||
>
|
||||
Zaloguj
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FileUploaderSingleUploadDemo() {
|
||||
const [files, setFiles] = useState([]);
|
||||
const [fileRejections, setFileRejections] = useState([]);
|
||||
const handleChange = useCallback((files) => setFiles([files[0]]), []);
|
||||
const handleRejected = useCallback(
|
||||
(fileRejections) => setFileRejections([fileRejections[0]]),
|
||||
[]
|
||||
);
|
||||
const handleRemove = useCallback(() => {
|
||||
setFiles([]);
|
||||
setFileRejections([]);
|
||||
}, []);
|
||||
return (
|
||||
<Pane maxWidth={654}>
|
||||
<FileUploader
|
||||
label="Upload File"
|
||||
description="You can upload 1 file. File can be up to 50 MB."
|
||||
maxSizeInBytes={50 * 1024 ** 2}
|
||||
maxFiles={1}
|
||||
onChange={handleChange}
|
||||
onRejected={handleRejected}
|
||||
renderFile={(file) => {
|
||||
const { name, size, type } = file;
|
||||
const fileRejection = fileRejections.find(
|
||||
(fileRejection) => fileRejection.file === file
|
||||
);
|
||||
const { message } = fileRejection || {};
|
||||
return (
|
||||
<FileCard
|
||||
key={name}
|
||||
isInvalid={fileRejection != null}
|
||||
name={name}
|
||||
onRemove={handleRemove}
|
||||
sizeInBytes={size}
|
||||
type={type}
|
||||
validationMessage={message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
values={files}
|
||||
/>
|
||||
</Pane>
|
||||
);
|
||||
}
|
||||
1877
pages/spadki.js
Normal file
1877
pages/spadki.js
Normal file
File diff suppressed because it is too large
Load Diff
367
pages/tracking.js
Normal file
367
pages/tracking.js
Normal file
@@ -0,0 +1,367 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import Head from 'next/head';
|
||||
import { useRouter } from 'next/router';
|
||||
import Layout from "../components/ui/Layout";
|
||||
import { Card, CardHeader, CardContent, CardTitle } from "../components/ui/components";
|
||||
|
||||
export default function TrackingPage() {
|
||||
const router = useRouter();
|
||||
const [trackingNumber, setTrackingNumber] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [trackingData, setTrackingData] = useState(null);
|
||||
|
||||
// Use ref to keep track of if the component is mounted
|
||||
const isMounted = useRef(true);
|
||||
|
||||
// Handle tracking number from URL query parameter
|
||||
useEffect(() => {
|
||||
if (router.query.number) {
|
||||
setTrackingNumber(router.query.number);
|
||||
fetchTrackingData(router.query.number);
|
||||
}
|
||||
|
||||
return () => {
|
||||
isMounted.current = false;
|
||||
};
|
||||
}, [router.query.number]);
|
||||
|
||||
// Copy text to clipboard function
|
||||
const copyToClipboard = (text) => {
|
||||
navigator.clipboard.writeText(text).catch(err => {
|
||||
console.error('Failed to copy text: ', err);
|
||||
});
|
||||
};
|
||||
|
||||
// Format date to Polish locale
|
||||
const formatDate = (dateString) => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString('pl-PL', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
};
|
||||
|
||||
// Get country flag emoji
|
||||
const getCountryFlag = (countryCode) => {
|
||||
const flags = {
|
||||
'PL': '🇵🇱',
|
||||
'DE': '🇩🇪',
|
||||
'FR': '🇫🇷',
|
||||
'GB': '🇬🇧',
|
||||
'US': '🇺🇸',
|
||||
'IT': '🇮🇹',
|
||||
'ES': '🇪🇸',
|
||||
'NL': '🇳🇱',
|
||||
'BE': '🇧🇪',
|
||||
'CZ': '🇨🇿',
|
||||
'SK': '🇸🇰',
|
||||
'AT': '🇦🇹',
|
||||
'CH': '🇨🇭'
|
||||
};
|
||||
return flags[countryCode] || '🌍';
|
||||
};
|
||||
|
||||
// Validate tracking number format
|
||||
const isValidTrackingNumber = (number) => {
|
||||
return number && number.length >= 13 && /^[0-9A-Z]+$/.test(number.replace(/\s/g, ''));
|
||||
};
|
||||
|
||||
// No longer needed - removed map-related function
|
||||
|
||||
// Handle form submission
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
if (trackingNumber) {
|
||||
fetchTrackingData(trackingNumber);
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch tracking data from API
|
||||
const fetchTrackingData = async (number) => {
|
||||
if (!number) {
|
||||
setError('Proszę wprowadzić numer przesyłki.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isValidTrackingNumber(number)) {
|
||||
setError('Nieprawidłowy format numeru przesyłki. Numer powinien zawierać tylko cyfry i litery.');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError('');
|
||||
setTrackingData(null);
|
||||
|
||||
const apiKey =
|
||||
"GI42fpjWMcTGI2oY0DAN4IulxR3QZy8+XhA2oPE17HB6P5JA+BTvuY5gIH0imCa2ZmH/vieaE66A/bRnSlsh3RIWVPHPtR5qVflsHx2mBtDAnzWFBmzZeDg8zVIPIPRN7TyM/SzqHDkt5IdfnRDRXKO5p/Bc/1Jpj0JpNKs+NTk=.RjcxMEEwRjI3NjA1QURGMjNCNzc4NTVGOTY0NkQ0RTA5NDQ4NTgxNzE2RTNEREFGMTkwNkU2QjlFRTlEMzEzRg==.443a0acd4c7d4417b55e1ae9e755d91a";
|
||||
const url = `https://uss.poczta-polska.pl/uss/v2.0/tracking/checkmailex?language=PL&number=${number}&addPostOfficeInfo=true&events=true&states=true`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
api_key: apiKey,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
throw new Error('Nie znaleziono przesyłki o podanym numerze.');
|
||||
} else if (response.status === 403) {
|
||||
throw new Error('Brak autoryzacji do API. Skontaktuj się z administratorem.');
|
||||
} else {
|
||||
throw new Error(`Błąd serwera: ${response.status}. Spróbuj ponownie później.`);
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.mailInfo) {
|
||||
throw new Error('Brak danych o przesyłce. Sprawdź numer i spróbuj ponownie.');
|
||||
}
|
||||
|
||||
if (isMounted.current) {
|
||||
setTrackingData(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching tracking data:", error);
|
||||
if (isMounted.current) {
|
||||
setError(error.message || 'Wystąpił błąd podczas pobierania danych. Sprawdź połączenie internetowe i spróbuj ponownie.');
|
||||
}
|
||||
} finally {
|
||||
if (isMounted.current) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a comprehensive timeline combining states and events
|
||||
const getTimeline = (mailInfo) => {
|
||||
if (!mailInfo) return [];
|
||||
|
||||
const timeline = [];
|
||||
|
||||
// Add states to timeline
|
||||
if (mailInfo.states) {
|
||||
mailInfo.states.forEach(state => {
|
||||
timeline.push({
|
||||
type: 'state',
|
||||
data: state,
|
||||
time: new Date(state.time)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Add events to timeline
|
||||
if (mailInfo.events) {
|
||||
mailInfo.events.forEach(event => {
|
||||
timeline.push({
|
||||
type: 'event',
|
||||
data: event,
|
||||
time: new Date(event.time)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Sort timeline by time (oldest first)
|
||||
return timeline.sort((a, b) => a.time - b.time);
|
||||
};
|
||||
|
||||
// Handle button click for copying tracking number
|
||||
const handleCopy = () => {
|
||||
if (trackingData?.mailInfo?.number) {
|
||||
copyToClipboard(trackingData.mailInfo.number);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout title="Śledzenie przesyłek">
|
||||
<div className="p-6 max-w-7xl mx-auto">
|
||||
{/* Page Header */}
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">
|
||||
Śledzenie przesyłek
|
||||
</h1>
|
||||
<p className="text-gray-600">
|
||||
Sprawdź status przesyłki
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Input Card */}
|
||||
<Card className="mb-6">
|
||||
<CardHeader className="border-b bg-gray-50">
|
||||
<CardTitle>Wyszukiwanie przesyłki</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-6">
|
||||
<form onSubmit={handleSubmit} className="flex flex-wrap gap-4">
|
||||
<input
|
||||
type="text"
|
||||
value={trackingNumber}
|
||||
onChange={(e) => setTrackingNumber(e.target.value)}
|
||||
placeholder="Wprowadź numer przesyłki (np. 00659007734210841438)"
|
||||
maxLength="50"
|
||||
className="flex-1 min-w-[250px] block rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 transition-colors p-2.5 border"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2.5 px-5 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:bg-gray-400 disabled:cursor-not-allowed"
|
||||
>
|
||||
{loading ? 'Wyszukiwanie...' : 'Szukaj'}
|
||||
</button>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{loading && (
|
||||
<div className="flex items-center justify-center p-4 mb-6">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||
<span className="ml-3 text-blue-600 font-medium">Pobieranie danych...</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-6 rounded-lg">
|
||||
<p>{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{trackingData && trackingData.mailInfo && (
|
||||
<>
|
||||
{/* Package Information Card */}
|
||||
<Card className="mb-6">
|
||||
<CardHeader className="border-b bg-gray-50">
|
||||
<CardTitle>Dane przesyłki</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-6">
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="font-semibold text-gray-700 min-w-[140px]">Numer przesyłki:</span>
|
||||
<span className="text-gray-900">{trackingData.mailInfo.number}</span>
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className="ml-2 text-xs border border-blue-600 text-blue-600 hover:bg-blue-600 hover:text-white px-2 py-1 rounded transition-colors"
|
||||
>
|
||||
Kopiuj
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center">
|
||||
<span className="font-semibold text-gray-700 min-w-[140px]">Typ przesyłki:</span>
|
||||
<span className="text-gray-900">{trackingData.mailInfo.typeOfMailName}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center">
|
||||
<span className="font-semibold text-gray-700 min-w-[140px]">Doręczono:</span>
|
||||
<span className={`px-3 py-1 rounded-full text-sm font-medium ${
|
||||
trackingData.mailInfo.finished
|
||||
? 'bg-green-100 text-green-800'
|
||||
: 'bg-yellow-100 text-yellow-800'
|
||||
}`}>
|
||||
{trackingData.mailInfo.finished ? "✓ Tak" : "⏳ Nie"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4 mt-4 pt-4 border-t border-gray-200">
|
||||
<div className="flex items-center gap-2 bg-gray-50 px-4 py-3 rounded-lg">
|
||||
<span className="text-gray-700">🏁 Nadanie:</span>
|
||||
<span className="font-medium">
|
||||
{getCountryFlag(trackingData.mailInfo.dispatchCountryCode)} {trackingData.mailInfo.dispatchCountryName}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 bg-gray-50 px-4 py-3 rounded-lg">
|
||||
<span className="text-gray-700">🎯 Doręczenie:</span>
|
||||
<span className="font-medium">
|
||||
{getCountryFlag(trackingData.mailInfo.recipientCountryCode)} {trackingData.mailInfo.recipientCountryName}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Package History Card */}
|
||||
<Card>
|
||||
<CardHeader className="border-b bg-gray-50">
|
||||
<CardTitle>Historia przesyłki</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-6">
|
||||
{getTimeline(trackingData.mailInfo).length > 0 ? (
|
||||
<div className="relative pl-8 border-l-2 border-blue-500 ml-4">
|
||||
{getTimeline(trackingData.mailInfo).map((item, index) => {
|
||||
if (item.type === 'state') {
|
||||
// Find related events for this state
|
||||
const relatedEvents = trackingData.mailInfo.events ?
|
||||
trackingData.mailInfo.events.filter(event =>
|
||||
event.state && event.state.code === item.data.code
|
||||
) : [];
|
||||
|
||||
return (
|
||||
<div key={`state-${index}`} className="mb-8 relative">
|
||||
<div className="absolute -left-[29px] w-6 h-6 bg-blue-600 rounded-full border-4 border-white"></div>
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4 mb-3">
|
||||
<div className="font-medium text-blue-700">📦 {item.data.name}</div>
|
||||
<div className="text-sm text-gray-600 mt-1">🕒 {formatDate(item.data.time)}</div>
|
||||
</div>
|
||||
{relatedEvents.map((event, eventIndex) => (
|
||||
<div key={`related-event-${eventIndex}`} className="ml-4 bg-blue-50 rounded-lg p-4 mb-3 border-l-4 border-blue-400">
|
||||
<div className="font-medium text-blue-800">📋 {event.name}</div>
|
||||
<div className="text-sm text-gray-600 mt-1">🕒 {formatDate(event.time)}</div>
|
||||
{event.postOffice && event.postOffice.description && (
|
||||
<div className="mt-2 text-sm bg-white p-2 rounded border border-gray-200">
|
||||
📍 {event.postOffice.description.city}, {event.postOffice.description.street} {event.postOffice.description.houseNumber}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
} else if (item.type === 'event') {
|
||||
// Only show standalone events (not linked to states)
|
||||
const isLinkedToState = trackingData.mailInfo.states &&
|
||||
trackingData.mailInfo.states.some(state =>
|
||||
item.data.state && item.data.state.code === state.code
|
||||
);
|
||||
|
||||
if (!isLinkedToState) {
|
||||
return (
|
||||
<div key={`event-${index}`} className="mb-8 relative">
|
||||
<div className="absolute -left-[29px] w-6 h-6 bg-gray-400 rounded-full border-4 border-white"></div>
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
|
||||
<div className="font-medium text-gray-700">📋 {item.data.name}</div>
|
||||
<div className="text-sm text-gray-600 mt-1">🕒 {formatDate(item.data.time)}</div>
|
||||
{item.data.postOffice && item.data.postOffice.description && (
|
||||
<div className="mt-2 text-sm bg-gray-50 p-2 rounded border border-gray-200">
|
||||
📍 {item.data.postOffice.description.city}, {item.data.postOffice.description.street} {item.data.postOffice.description.houseNumber}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8 bg-gray-50 rounded-lg">
|
||||
<p className="text-gray-600">Brak dostępnych informacji o historii przesyłki</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
545
pages/uziomy.js
Normal file
545
pages/uziomy.js
Normal file
@@ -0,0 +1,545 @@
|
||||
import { useState } from "react";
|
||||
import { useSession, signIn } from "next-auth/react";
|
||||
import Layout from "../components/ui/Layout";
|
||||
import { Card, CardHeader, CardContent, CardTitle, CardDescription, Button, Input, Badge, Alert } from "../components/ui/components";
|
||||
import { BoltIcon as LightningBoltIcon, CalculatorIcon, DocumentArrowDownIcon as DocumentDownloadIcon, ClipboardDocumentListIcon as ClipboardListIcon } from '@heroicons/react/24/outline';
|
||||
import DatePicker from "react-datepicker";
|
||||
import { registerLocale } from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import pl from "date-fns/locale/pl";
|
||||
import PizZip from "pizzip";
|
||||
import Docxtemplater from "docxtemplater";
|
||||
registerLocale("pl", pl);
|
||||
|
||||
export default function Uziomy() {
|
||||
const { data: session } = useSession();
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const [isCalculating, setIsCalculating] = useState(false);
|
||||
const [isGeneratingDoc, setIsGeneratingDoc] = useState(false);
|
||||
const [isGeneratingDxf, setIsGeneratingDxf] = useState(false);
|
||||
const [calDate, setCalDate] = useState(null);
|
||||
|
||||
const [ground, setGround] = useState({
|
||||
wet_coef: 0,
|
||||
resistivity: 0,
|
||||
resistance: 0,
|
||||
measure_dist: 0,
|
||||
rod_len: 0,
|
||||
rod_num: 0,
|
||||
rod_coef: 0,
|
||||
hor_len: 0,
|
||||
result_v: 0,
|
||||
result_h: 0,
|
||||
result: 0,
|
||||
wszrg_h: 0,
|
||||
wszrg_v: 0,
|
||||
wanted: 0,
|
||||
date: undefined,
|
||||
no: 0,
|
||||
pr_title: "Budowa przyłącza kablowego nN",
|
||||
in_city: undefined,
|
||||
commune: undefined,
|
||||
all_parcels: undefined,
|
||||
target_parcel: undefined,
|
||||
geo_data: undefined,
|
||||
object: "Przyłącz kablowy nN",
|
||||
objValue1: "proj.",
|
||||
objName: undefined,
|
||||
});
|
||||
|
||||
const [options] = useState([
|
||||
{ label: "5 Ω", value: "5" },
|
||||
{ label: "10 Ω", value: "10" },
|
||||
{ label: "15 Ω", value: "15" },
|
||||
{ label: "30 Ω", value: "30" },
|
||||
]);
|
||||
const [neededValue, setNeededValue] = useState("5");
|
||||
const [resHValue, setResHValue] = useState("88");
|
||||
const [resVValue, setResVValue] = useState("89");
|
||||
|
||||
const [objOptions1] = useState([
|
||||
{ label: "proj.", value: "proj." },
|
||||
{ label: "istn.", value: "istn." },
|
||||
]);
|
||||
const [objValue1, setObjValue1] = useState("proj.");
|
||||
|
||||
function getRandomInt(min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
function parseDate(dateString) {
|
||||
const parts = dateString.split(".");
|
||||
const day = parseInt(parts[0], 10);
|
||||
const month = parseInt(parts[1], 10) - 1;
|
||||
const year = parseInt(parts[2], 10);
|
||||
return new Date(year, month, day);
|
||||
}
|
||||
|
||||
function getGrounding(wanted, wszrg_h, wszrg_v, date) {
|
||||
const dateObject = parseDate(date);
|
||||
const month = dateObject.getMonth() + 1;
|
||||
|
||||
const wet_coef = month >= 6 && month <= 9 ? 1.2 : 1.6;
|
||||
const rod_len = wanted === 30 ? 2 : 3;
|
||||
|
||||
const resistivity_h = wszrg_h / wet_coef;
|
||||
const measure_dist_h = 1;
|
||||
const resistance_h = resistivity_h / (2 * Math.PI * measure_dist_h);
|
||||
|
||||
const resistivity_v = wszrg_v / wet_coef;
|
||||
const measure_dist_v = 1 + rod_len;
|
||||
const resistance_v = resistivity_v / (2 * Math.PI * measure_dist_v);
|
||||
|
||||
const result_v = (wszrg_v / (2 * Math.PI * rod_len)) * (Math.log((8 * rod_len) / 0.016) - 1);
|
||||
|
||||
let rod_num = 2;
|
||||
let hor_len = 1 + (rod_num - 1) * rod_len * 2;
|
||||
let result_h = (wszrg_h / (2 * Math.PI * hor_len)) * Math.log((hor_len * hor_len) / (1 * 0.0191));
|
||||
|
||||
let rod_coef = Math.pow(rod_num, 4) * 0.00002 - Math.pow(rod_num, 3) * 0.0009 +
|
||||
Math.pow(rod_num, 2) * 0.0137 - rod_num * 0.0981 + 1.0468;
|
||||
|
||||
let result = (result_v * result_h) / (result_v * rod_coef + rod_num * result_h * rod_coef);
|
||||
|
||||
while (result > wanted) {
|
||||
rod_num += 1;
|
||||
hor_len = 1 + (rod_num - 1) * rod_len * 2;
|
||||
result_h = (wszrg_h / (2 * Math.PI * hor_len)) * Math.log((hor_len * hor_len) / (1 * 0.0191));
|
||||
rod_coef = Math.pow(rod_num, 4) * 0.00002 - Math.pow(rod_num, 3) * 0.0009 +
|
||||
Math.pow(rod_num, 2) * 0.0137 - rod_num * 0.0981 + 1.0468;
|
||||
result = (result_v * result_h) / (result_v * rod_coef + rod_num * result_h * rod_coef);
|
||||
}
|
||||
|
||||
return {
|
||||
wet_coef: wet_coef,
|
||||
resistivity_h: resistivity_h.toFixed(2),
|
||||
resistance_h: resistance_h.toFixed(2),
|
||||
measure_dist_h: measure_dist_h,
|
||||
resistivity_v: resistivity_v.toFixed(2),
|
||||
resistance_v: resistance_v.toFixed(2),
|
||||
measure_dist_v: measure_dist_v,
|
||||
rod_len: rod_len,
|
||||
rod_num: rod_num,
|
||||
rod_coef: rod_coef.toFixed(2),
|
||||
hor_len: hor_len,
|
||||
result_v: result_v.toFixed(2),
|
||||
result_h: result_h.toFixed(2),
|
||||
result: result.toFixed(2),
|
||||
wszrg_h: wszrg_h,
|
||||
wszrg_v: wszrg_v,
|
||||
wanted: neededValue,
|
||||
};
|
||||
}
|
||||
|
||||
const calculateGrounding = () => {
|
||||
setIsCalculating(true);
|
||||
try {
|
||||
const res = getGrounding(
|
||||
parseInt(neededValue),
|
||||
parseFloat(resHValue),
|
||||
parseFloat(resVValue),
|
||||
ground.date
|
||||
);
|
||||
setGround((current) => ({ ...current, ...res }));
|
||||
} catch (error) {
|
||||
alert("Błąd podczas obliczeń: " + error.message);
|
||||
}
|
||||
setIsCalculating(false);
|
||||
};
|
||||
|
||||
const generateDocument = async () => {
|
||||
setIsGeneratingDoc(true);
|
||||
const data = {
|
||||
...ground,
|
||||
resisted_object: ground.objValue1 + " " + ground.objName,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/generateDocx", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error("Response was not ok.");
|
||||
|
||||
const blob = await response.blob();
|
||||
const downloadUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = downloadUrl;
|
||||
a.download = "opis_uziemienia.docx";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
alert("Błąd podczas generowania dokumentu");
|
||||
}
|
||||
setIsGeneratingDoc(false);
|
||||
};
|
||||
|
||||
const generateDxf = async () => {
|
||||
setIsGeneratingDxf(true);
|
||||
const dateParts = ground.date.split(".");
|
||||
const month = dateParts[1];
|
||||
const year = parseInt(dateParts[2], 10);
|
||||
const formattedDate = month.padStart(2, "0") + "." + year;
|
||||
|
||||
const inputData = {
|
||||
args: [
|
||||
ground.objValue1 + ground.objName,
|
||||
ground.pr_title,
|
||||
ground.object,
|
||||
ground.in_city + ", " + ground.commune + ", dz. nr " + ground.all_parcels,
|
||||
formattedDate,
|
||||
ground.hor_len,
|
||||
ground.rod_len,
|
||||
],
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/generateDxf", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(inputData),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error("Response was not ok.");
|
||||
|
||||
const blob = await response.blob();
|
||||
const downloadUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = downloadUrl;
|
||||
a.download = "uziom.dxf";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
alert("Błąd podczas generowania rysunku");
|
||||
}
|
||||
setIsGeneratingDxf(false);
|
||||
};
|
||||
|
||||
if (session) {
|
||||
return (
|
||||
<Layout title="Wastpol - Kalkulator uziemień">
|
||||
<div className="p-6 max-w-7xl mx-auto">
|
||||
{/* Page Header */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center space-x-3 mb-4">
|
||||
<div className="p-3 bg-yellow-100 rounded-lg">
|
||||
<LightningBoltIcon className="w-8 h-8 text-yellow-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900">Kalkulator uziemień</h1>
|
||||
<p className="text-gray-600">Oblicz parametry układu uziomowego i wygeneruj dokumentację</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
{/* Input Data Section */}
|
||||
<div className="lg:col-span-2 space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<ClipboardListIcon className="w-5 h-5 text-blue-600" />
|
||||
<span>Dane pomiarowe</span>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Wprowadź podstawowe dane pomiarowe i parametry projektu
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Data wykonania pomiaru
|
||||
</label>
|
||||
<DatePicker
|
||||
locale="pl"
|
||||
selected={calDate}
|
||||
onChange={(date) => {
|
||||
setCalDate(date);
|
||||
const day = date.getDate().toString().padStart(2, "0");
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
||||
const year = date.getFullYear();
|
||||
const formattedDate = `${day}.${month}.${year}`;
|
||||
setGround((current) => ({ ...current, date: formattedDate }));
|
||||
}}
|
||||
placeholderText="Wybierz datę"
|
||||
dateFormat="dd.MM.yyyy"
|
||||
className="block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Wymagane uziemienie
|
||||
</label>
|
||||
<div className="space-y-2">
|
||||
{options.map((option) => (
|
||||
<label key={option.value} className="flex items-center">
|
||||
<input
|
||||
type="radio"
|
||||
value={option.value}
|
||||
checked={neededValue === option.value}
|
||||
onChange={(e) => setNeededValue(e.target.value)}
|
||||
className="mr-2"
|
||||
/>
|
||||
<span>{option.label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
label="Rezystywność gruntu pozioma (Ωm)"
|
||||
placeholder="86"
|
||||
value={resHValue}
|
||||
onChange={(e) => {
|
||||
setResHValue(e.target.value);
|
||||
const val = parseInt(e.target.value);
|
||||
const randomV = ground.wanted == 30 ?
|
||||
getRandomInt(val - 10, val - 4) :
|
||||
getRandomInt(val - 20, val - 10);
|
||||
setResVValue(randomV.toString());
|
||||
}}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Numer protokołu pomiarowego"
|
||||
placeholder="435"
|
||||
value={ground.no}
|
||||
onChange={(e) => setGround(curr => ({ ...curr, no: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<Input
|
||||
label="Tytuł projektu"
|
||||
placeholder="Budowa przyłącza kablowego nN"
|
||||
value={ground.pr_title}
|
||||
onChange={(e) => setGround(curr => ({ ...curr, pr_title: e.target.value }))}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Obiekt"
|
||||
placeholder="Przyłącz kablowy nN"
|
||||
value={ground.object}
|
||||
onChange={(e) => setGround(curr => ({ ...curr, object: e.target.value }))}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Miejscowość"
|
||||
placeholder="Paszyn"
|
||||
value={ground.in_city}
|
||||
onChange={(e) => setGround(curr => ({ ...curr, in_city: e.target.value }))}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Gmina/Obręb"
|
||||
placeholder="gm. Chełmiec"
|
||||
value={ground.commune}
|
||||
onChange={(e) => setGround(curr => ({ ...curr, commune: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<Input
|
||||
label="Wszystkie działki"
|
||||
placeholder="423, 424, 425"
|
||||
value={ground.all_parcels}
|
||||
onChange={(e) => setGround(curr => ({ ...curr, all_parcels: e.target.value }))}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Działka przyłączana"
|
||||
placeholder="423"
|
||||
value={ground.target_parcel}
|
||||
onChange={(e) => setGround(curr => ({ ...curr, target_parcel: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
label="Współrzędne układu uziomowego"
|
||||
placeholder="49°42'54"N 20°38'0"E"
|
||||
value={ground.geo_data}
|
||||
onChange={(e) => setGround(curr => ({ ...curr, geo_data: e.target.value }))}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Typ obiektu
|
||||
</label>
|
||||
<div className="space-y-2">
|
||||
{objOptions1.map((option) => (
|
||||
<label key={option.value} className="flex items-center">
|
||||
<input
|
||||
type="radio"
|
||||
value={option.value}
|
||||
checked={objValue1 === option.value}
|
||||
onChange={(e) => {
|
||||
setObjValue1(e.target.value);
|
||||
setGround(curr => ({ ...curr, objValue1: e.target.value }));
|
||||
}}
|
||||
className="mr-2"
|
||||
/>
|
||||
<span>{option.label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
label="Nazwa uziemianego obiektu"
|
||||
placeholder="ZK2a-1P lub słup nr 54"
|
||||
value={ground.objName}
|
||||
onChange={(e) => setGround(curr => ({ ...curr, objName: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Results and Actions Section */}
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<CalculatorIcon className="w-5 h-5 text-green-600" />
|
||||
<span>Obliczenia</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<Button
|
||||
onClick={calculateGrounding}
|
||||
disabled={!ground.date || !resHValue || !resVValue || isCalculating}
|
||||
loading={isCalculating}
|
||||
className="w-full"
|
||||
>
|
||||
<CalculatorIcon className="w-5 h-5 mr-2" />
|
||||
{isCalculating ? 'Obliczanie...' : 'Oblicz uziemienie'}
|
||||
</Button>
|
||||
|
||||
{ground.result > 0 && (
|
||||
<div className="space-y-3">
|
||||
<Alert variant="success">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span>Uziemienie poziome:</span>
|
||||
<Badge variant="info">{ground.result_h} Ω</Badge>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Uziemienie pionowe:</span>
|
||||
<Badge variant="info">{ground.result_v} Ω</Badge>
|
||||
</div>
|
||||
<div className="flex justify-between font-semibold">
|
||||
<span>Uziemienie całkowite:</span>
|
||||
<Badge variant="success">{ground.result} Ω</Badge>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Liczba szpil:</span>
|
||||
<Badge>{ground.rod_num} szt</Badge>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Długość bednarki:</span>
|
||||
<Badge>{ground.hor_len} m</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<DocumentDownloadIcon className="w-5 h-5 text-purple-600" />
|
||||
<span>Generuj dokumentację</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<Button
|
||||
onClick={generateDocument}
|
||||
disabled={ground.result <= 0 || isGeneratingDoc}
|
||||
loading={isGeneratingDoc}
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
>
|
||||
{isGeneratingDoc ? 'Generowanie...' : 'Generuj opis (DOCX)'}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={generateDxf}
|
||||
disabled={ground.result <= 0 || isGeneratingDxf}
|
||||
loading={isGeneratingDxf}
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
>
|
||||
{isGeneratingDxf ? 'Generowanie...' : 'Generuj rysunek (DXF)'}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Instructions */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Instrukcje</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3 text-sm">
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-xs font-semibold text-blue-600">1</span>
|
||||
</div>
|
||||
<p className="text-gray-600">Wprowadź dane pomiarowe i parametry projektu</p>
|
||||
</div>
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-xs font-semibold text-blue-600">2</span>
|
||||
</div>
|
||||
<p className="text-gray-600">Kliknij "Oblicz uziemienie" aby wykonać kalkulacje</p>
|
||||
</div>
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-xs font-semibold text-blue-600">3</span>
|
||||
</div>
|
||||
<p className="text-gray-600">Wygeneruj dokumentację - opis i rysunek techniczny</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center p-4">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader className="text-center">
|
||||
<div className="mx-auto w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mb-4">
|
||||
<img src="/logo.png" alt="Wastpol" className="w-10 h-10" />
|
||||
</div>
|
||||
<CardTitle className="text-2xl">Zaloguj się</CardTitle>
|
||||
<p className="text-gray-600 mt-2">Uzyskaj dostęp do kalkulatora uziemień</p>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button onClick={() => signIn()} className="w-full" size="lg">
|
||||
Zaloguj się
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
545
pages/uziomy_new.js
Normal file
545
pages/uziomy_new.js
Normal file
@@ -0,0 +1,545 @@
|
||||
import { useState } from "react";
|
||||
import { useSession, signIn } from "next-auth/react";
|
||||
import Layout from "../components/ui/Layout";
|
||||
import { Card, CardHeader, CardContent, CardTitle, CardDescription, Button, Input, Badge, Alert } from "../components/ui/components";
|
||||
import { BoltIcon as LightningBoltIcon, CalculatorIcon, DocumentArrowDownIcon as DocumentDownloadIcon, ClipboardDocumentListIcon as ClipboardListIcon } from '@heroicons/react/24/outline';
|
||||
import DatePicker from "react-datepicker";
|
||||
import { registerLocale } from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import pl from "date-fns/locale/pl";
|
||||
import PizZip from "pizzip";
|
||||
import Docxtemplater from "docxtemplater";
|
||||
registerLocale("pl", pl);
|
||||
|
||||
export default function Uziomy() {
|
||||
const { data: session } = useSession();
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const [isCalculating, setIsCalculating] = useState(false);
|
||||
const [isGeneratingDoc, setIsGeneratingDoc] = useState(false);
|
||||
const [isGeneratingDxf, setIsGeneratingDxf] = useState(false);
|
||||
const [calDate, setCalDate] = useState(null);
|
||||
|
||||
const [ground, setGround] = useState({
|
||||
wet_coef: 0,
|
||||
resistivity: 0,
|
||||
resistance: 0,
|
||||
measure_dist: 0,
|
||||
rod_len: 0,
|
||||
rod_num: 0,
|
||||
rod_coef: 0,
|
||||
hor_len: 0,
|
||||
result_v: 0,
|
||||
result_h: 0,
|
||||
result: 0,
|
||||
wszrg_h: 0,
|
||||
wszrg_v: 0,
|
||||
wanted: 0,
|
||||
date: undefined,
|
||||
no: 0,
|
||||
pr_title: "Budowa przyłącza kablowego nN",
|
||||
in_city: undefined,
|
||||
commune: undefined,
|
||||
all_parcels: undefined,
|
||||
target_parcel: undefined,
|
||||
geo_data: undefined,
|
||||
object: "Przyłącz kablowy nN",
|
||||
objValue1: "proj.",
|
||||
objName: undefined,
|
||||
});
|
||||
|
||||
const [options] = useState([
|
||||
{ label: "5 Ω", value: "5" },
|
||||
{ label: "10 Ω", value: "10" },
|
||||
{ label: "15 Ω", value: "15" },
|
||||
{ label: "30 Ω", value: "30" },
|
||||
]);
|
||||
const [neededValue, setNeededValue] = useState("5");
|
||||
const [resHValue, setResHValue] = useState("88");
|
||||
const [resVValue, setResVValue] = useState("89");
|
||||
|
||||
const [objOptions1] = useState([
|
||||
{ label: "proj.", value: "proj." },
|
||||
{ label: "istn.", value: "istn." },
|
||||
]);
|
||||
const [objValue1, setObjValue1] = useState("proj.");
|
||||
|
||||
function getRandomInt(min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
function parseDate(dateString) {
|
||||
const parts = dateString.split(".");
|
||||
const day = parseInt(parts[0], 10);
|
||||
const month = parseInt(parts[1], 10) - 1;
|
||||
const year = parseInt(parts[2], 10);
|
||||
return new Date(year, month, day);
|
||||
}
|
||||
|
||||
function getGrounding(wanted, wszrg_h, wszrg_v, date) {
|
||||
const dateObject = parseDate(date);
|
||||
const month = dateObject.getMonth() + 1;
|
||||
|
||||
const wet_coef = month >= 6 && month <= 9 ? 1.2 : 1.6;
|
||||
const rod_len = wanted === 30 ? 2 : 3;
|
||||
|
||||
const resistivity_h = wszrg_h / wet_coef;
|
||||
const measure_dist_h = 1;
|
||||
const resistance_h = resistivity_h / (2 * Math.PI * measure_dist_h);
|
||||
|
||||
const resistivity_v = wszrg_v / wet_coef;
|
||||
const measure_dist_v = 1 + rod_len;
|
||||
const resistance_v = resistivity_v / (2 * Math.PI * measure_dist_v);
|
||||
|
||||
const result_v = (wszrg_v / (2 * Math.PI * rod_len)) * (Math.log((8 * rod_len) / 0.016) - 1);
|
||||
|
||||
let rod_num = 2;
|
||||
let hor_len = 1 + (rod_num - 1) * rod_len * 2;
|
||||
let result_h = (wszrg_h / (2 * Math.PI * hor_len)) * Math.log((hor_len * hor_len) / (1 * 0.0191));
|
||||
|
||||
let rod_coef = Math.pow(rod_num, 4) * 0.00002 - Math.pow(rod_num, 3) * 0.0009 +
|
||||
Math.pow(rod_num, 2) * 0.0137 - rod_num * 0.0981 + 1.0468;
|
||||
|
||||
let result = (result_v * result_h) / (result_v * rod_coef + rod_num * result_h * rod_coef);
|
||||
|
||||
while (result > wanted) {
|
||||
rod_num += 1;
|
||||
hor_len = 1 + (rod_num - 1) * rod_len * 2;
|
||||
result_h = (wszrg_h / (2 * Math.PI * hor_len)) * Math.log((hor_len * hor_len) / (1 * 0.0191));
|
||||
rod_coef = Math.pow(rod_num, 4) * 0.00002 - Math.pow(rod_num, 3) * 0.0009 +
|
||||
Math.pow(rod_num, 2) * 0.0137 - rod_num * 0.0981 + 1.0468;
|
||||
result = (result_v * result_h) / (result_v * rod_coef + rod_num * result_h * rod_coef);
|
||||
}
|
||||
|
||||
return {
|
||||
wet_coef: wet_coef,
|
||||
resistivity_h: resistivity_h.toFixed(2),
|
||||
resistance_h: resistance_h.toFixed(2),
|
||||
measure_dist_h: measure_dist_h,
|
||||
resistivity_v: resistivity_v.toFixed(2),
|
||||
resistance_v: resistance_v.toFixed(2),
|
||||
measure_dist_v: measure_dist_v,
|
||||
rod_len: rod_len,
|
||||
rod_num: rod_num,
|
||||
rod_coef: rod_coef.toFixed(2),
|
||||
hor_len: hor_len,
|
||||
result_v: result_v.toFixed(2),
|
||||
result_h: result_h.toFixed(2),
|
||||
result: result.toFixed(2),
|
||||
wszrg_h: wszrg_h,
|
||||
wszrg_v: wszrg_v,
|
||||
wanted: neededValue,
|
||||
};
|
||||
}
|
||||
|
||||
const calculateGrounding = () => {
|
||||
setIsCalculating(true);
|
||||
try {
|
||||
const res = getGrounding(
|
||||
parseInt(neededValue),
|
||||
parseFloat(resHValue),
|
||||
parseFloat(resVValue),
|
||||
ground.date
|
||||
);
|
||||
setGround((current) => ({ ...current, ...res }));
|
||||
} catch (error) {
|
||||
alert("Błąd podczas obliczeń: " + error.message);
|
||||
}
|
||||
setIsCalculating(false);
|
||||
};
|
||||
|
||||
const generateDocument = async () => {
|
||||
setIsGeneratingDoc(true);
|
||||
const data = {
|
||||
...ground,
|
||||
resisted_object: ground.objValue1 + " " + ground.objName,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/generateDocx", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error("Response was not ok.");
|
||||
|
||||
const blob = await response.blob();
|
||||
const downloadUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = downloadUrl;
|
||||
a.download = "opis_uziemienia.docx";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
alert("Błąd podczas generowania dokumentu");
|
||||
}
|
||||
setIsGeneratingDoc(false);
|
||||
};
|
||||
|
||||
const generateDxf = async () => {
|
||||
setIsGeneratingDxf(true);
|
||||
const dateParts = ground.date.split(".");
|
||||
const month = dateParts[1];
|
||||
const year = parseInt(dateParts[2], 10);
|
||||
const formattedDate = month.padStart(2, "0") + "." + year;
|
||||
|
||||
const inputData = {
|
||||
args: [
|
||||
ground.objValue1 + ground.objName,
|
||||
ground.pr_title,
|
||||
ground.object,
|
||||
ground.in_city + ", " + ground.commune + ", dz. nr " + ground.all_parcels,
|
||||
formattedDate,
|
||||
ground.hor_len,
|
||||
ground.rod_len,
|
||||
],
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/generateDxf", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(inputData),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error("Response was not ok.");
|
||||
|
||||
const blob = await response.blob();
|
||||
const downloadUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = downloadUrl;
|
||||
a.download = "uziom.dxf";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
alert("Błąd podczas generowania rysunku");
|
||||
}
|
||||
setIsGeneratingDxf(false);
|
||||
};
|
||||
|
||||
if (session) {
|
||||
return (
|
||||
<Layout title="Wastpol - Kalkulator uziemień">
|
||||
<div className="p-6 max-w-7xl mx-auto">
|
||||
{/* Page Header */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center space-x-3 mb-4">
|
||||
<div className="p-3 bg-yellow-100 rounded-lg">
|
||||
<LightningBoltIcon className="w-8 h-8 text-yellow-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900">Kalkulator uziemień</h1>
|
||||
<p className="text-gray-600">Oblicz parametry układu uziomowego i wygeneruj dokumentację</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
{/* Input Data Section */}
|
||||
<div className="lg:col-span-2 space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<ClipboardListIcon className="w-5 h-5 text-blue-600" />
|
||||
<span>Dane pomiarowe</span>
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Wprowadź podstawowe dane pomiarowe i parametry projektu
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Data wykonania pomiaru
|
||||
</label>
|
||||
<DatePicker
|
||||
locale="pl"
|
||||
selected={calDate}
|
||||
onChange={(date) => {
|
||||
setCalDate(date);
|
||||
const day = date.getDate().toString().padStart(2, "0");
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
||||
const year = date.getFullYear();
|
||||
const formattedDate = `${day}.${month}.${year}`;
|
||||
setGround((current) => ({ ...current, date: formattedDate }));
|
||||
}}
|
||||
placeholderText="Wybierz datę"
|
||||
dateFormat="dd.MM.yyyy"
|
||||
className="block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Wymagane uziemienie
|
||||
</label>
|
||||
<div className="space-y-2">
|
||||
{options.map((option) => (
|
||||
<label key={option.value} className="flex items-center">
|
||||
<input
|
||||
type="radio"
|
||||
value={option.value}
|
||||
checked={neededValue === option.value}
|
||||
onChange={(e) => setNeededValue(e.target.value)}
|
||||
className="mr-2"
|
||||
/>
|
||||
<span>{option.label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
label="Rezystywność gruntu pozioma (Ωm)"
|
||||
placeholder="86"
|
||||
value={resHValue}
|
||||
onChange={(e) => {
|
||||
setResHValue(e.target.value);
|
||||
const val = parseInt(e.target.value);
|
||||
const randomV = ground.wanted == 30 ?
|
||||
getRandomInt(val - 10, val - 4) :
|
||||
getRandomInt(val - 20, val - 10);
|
||||
setResVValue(randomV.toString());
|
||||
}}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Numer protokołu pomiarowego"
|
||||
placeholder="435"
|
||||
value={ground.no}
|
||||
onChange={(e) => setGround(curr => ({ ...curr, no: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<Input
|
||||
label="Tytuł projektu"
|
||||
placeholder="Budowa przyłącza kablowego nN"
|
||||
value={ground.pr_title}
|
||||
onChange={(e) => setGround(curr => ({ ...curr, pr_title: e.target.value }))}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Obiekt"
|
||||
placeholder="Przyłącz kablowy nN"
|
||||
value={ground.object}
|
||||
onChange={(e) => setGround(curr => ({ ...curr, object: e.target.value }))}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Miejscowość"
|
||||
placeholder="Paszyn"
|
||||
value={ground.in_city}
|
||||
onChange={(e) => setGround(curr => ({ ...curr, in_city: e.target.value }))}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Gmina/Obręb"
|
||||
placeholder="gm. Chełmiec"
|
||||
value={ground.commune}
|
||||
onChange={(e) => setGround(curr => ({ ...curr, commune: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<Input
|
||||
label="Wszystkie działki"
|
||||
placeholder="423, 424, 425"
|
||||
value={ground.all_parcels}
|
||||
onChange={(e) => setGround(curr => ({ ...curr, all_parcels: e.target.value }))}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="Działka przyłączana"
|
||||
placeholder="423"
|
||||
value={ground.target_parcel}
|
||||
onChange={(e) => setGround(curr => ({ ...curr, target_parcel: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
label="Współrzędne układu uziomowego"
|
||||
placeholder="49°42'54"N 20°38'0"E"
|
||||
value={ground.geo_data}
|
||||
onChange={(e) => setGround(curr => ({ ...curr, geo_data: e.target.value }))}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Typ obiektu
|
||||
</label>
|
||||
<div className="space-y-2">
|
||||
{objOptions1.map((option) => (
|
||||
<label key={option.value} className="flex items-center">
|
||||
<input
|
||||
type="radio"
|
||||
value={option.value}
|
||||
checked={objValue1 === option.value}
|
||||
onChange={(e) => {
|
||||
setObjValue1(e.target.value);
|
||||
setGround(curr => ({ ...curr, objValue1: e.target.value }));
|
||||
}}
|
||||
className="mr-2"
|
||||
/>
|
||||
<span>{option.label}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
label="Nazwa uziemianego obiektu"
|
||||
placeholder="ZK2a-1P lub słup nr 54"
|
||||
value={ground.objName}
|
||||
onChange={(e) => setGround(curr => ({ ...curr, objName: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Results and Actions Section */}
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<CalculatorIcon className="w-5 h-5 text-green-600" />
|
||||
<span>Obliczenia</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<Button
|
||||
onClick={calculateGrounding}
|
||||
disabled={!ground.date || !resHValue || !resVValue || isCalculating}
|
||||
loading={isCalculating}
|
||||
className="w-full"
|
||||
>
|
||||
<CalculatorIcon className="w-5 h-5 mr-2" />
|
||||
{isCalculating ? 'Obliczanie...' : 'Oblicz uziemienie'}
|
||||
</Button>
|
||||
|
||||
{ground.result > 0 && (
|
||||
<div className="space-y-3">
|
||||
<Alert variant="success">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span>Uziemienie poziome:</span>
|
||||
<Badge variant="info">{ground.result_h} Ω</Badge>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Uziemienie pionowe:</span>
|
||||
<Badge variant="info">{ground.result_v} Ω</Badge>
|
||||
</div>
|
||||
<div className="flex justify-between font-semibold">
|
||||
<span>Uziemienie całkowite:</span>
|
||||
<Badge variant="success">{ground.result} Ω</Badge>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Liczba szpil:</span>
|
||||
<Badge>{ground.rod_num} szt</Badge>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Długość bednarki:</span>
|
||||
<Badge>{ground.hor_len} m</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center space-x-2">
|
||||
<DocumentDownloadIcon className="w-5 h-5 text-purple-600" />
|
||||
<span>Generuj dokumentację</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<Button
|
||||
onClick={generateDocument}
|
||||
disabled={ground.result <= 0 || isGeneratingDoc}
|
||||
loading={isGeneratingDoc}
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
>
|
||||
{isGeneratingDoc ? 'Generowanie...' : 'Generuj opis (DOCX)'}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={generateDxf}
|
||||
disabled={ground.result <= 0 || isGeneratingDxf}
|
||||
loading={isGeneratingDxf}
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
>
|
||||
{isGeneratingDxf ? 'Generowanie...' : 'Generuj rysunek (DXF)'}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Instructions */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Instrukcje</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3 text-sm">
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-xs font-semibold text-blue-600">1</span>
|
||||
</div>
|
||||
<p className="text-gray-600">Wprowadź dane pomiarowe i parametry projektu</p>
|
||||
</div>
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-xs font-semibold text-blue-600">2</span>
|
||||
</div>
|
||||
<p className="text-gray-600">Kliknij "Oblicz uziemienie" aby wykonać kalkulacje</p>
|
||||
</div>
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-xs font-semibold text-blue-600">3</span>
|
||||
</div>
|
||||
<p className="text-gray-600">Wygeneruj dokumentację - opis i rysunek techniczny</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center p-4">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader className="text-center">
|
||||
<div className="mx-auto w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mb-4">
|
||||
<img src="/logo.png" alt="Wastpol" className="w-10 h-10" />
|
||||
</div>
|
||||
<CardTitle className="text-2xl">Zaloguj się</CardTitle>
|
||||
<p className="text-gray-600 mt-2">Uzyskaj dostęp do kalkulatora uziemień</p>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button onClick={() => signIn()} className="w-full" size="lg">
|
||||
Zaloguj się
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -17,3 +21,80 @@ a {
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
@apply font-semibold text-gray-900;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply text-3xl lg:text-4xl;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply text-2xl lg:text-3xl;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply text-xl lg:text-2xl;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.btn-primary {
|
||||
@apply bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@apply bg-gray-100 hover:bg-gray-200 text-gray-900 font-medium py-2 px-4 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2;
|
||||
}
|
||||
|
||||
.card {
|
||||
@apply bg-white rounded-xl shadow-sm border border-gray-200 hover:shadow-md transition-shadow;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
@apply block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 transition-colors;
|
||||
}
|
||||
}
|
||||
|
||||
/* Custom scrollbar */
|
||||
.scrollbar-thin {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #cbd5e1 #f1f5f9;
|
||||
}
|
||||
|
||||
.scrollbar-thin::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.scrollbar-thin::-webkit-scrollbar-track {
|
||||
background: #f1f5f9;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.scrollbar-thin::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
|
||||
background: #94a3b8;
|
||||
}
|
||||
|
||||
/* Loading animation */
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
/* Canvas styling */
|
||||
canvas {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,38 @@
|
||||
module.exports = {
|
||||
content: [
|
||||
"./pages/**/*.{js,ts,jsx,tsx}",
|
||||
"./templates/**/*.{js,ts,jsx,tsx}",
|
||||
"./components/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
extend: {
|
||||
colors: {
|
||||
primary: {
|
||||
50: '#eff6ff',
|
||||
500: '#3b82f6',
|
||||
600: '#2563eb',
|
||||
700: '#1d4ed8',
|
||||
},
|
||||
plugins: [],
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', 'sans-serif'],
|
||||
},
|
||||
animation: {
|
||||
'fade-in': 'fadeIn 0.5s ease-in-out',
|
||||
'slide-up': 'slideUp 0.3s ease-out',
|
||||
},
|
||||
keyframes: {
|
||||
fadeIn: {
|
||||
'0%': { opacity: '0' },
|
||||
'100%': { opacity: '1' },
|
||||
},
|
||||
slideUp: {
|
||||
'0%': { transform: 'translateY(10px)', opacity: '0' },
|
||||
'100%': { transform: 'translateY(0)', opacity: '1' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
require('@tailwindcss/forms'),
|
||||
],
|
||||
};
|
||||
|
||||
198
uziom.py
Normal file
198
uziom.py
Normal file
@@ -0,0 +1,198 @@
|
||||
import ezdxf
|
||||
import sys
|
||||
|
||||
args = sys.argv[1:]
|
||||
print(args)
|
||||
object1 = args[0] #ZK2a-1P
|
||||
object2 = args[1] #Budowa
|
||||
object3 = args[2] #Przylacze
|
||||
adres = args[3]
|
||||
date = args[4]
|
||||
len_h = int(args[5])
|
||||
len_v = float(args[6])
|
||||
|
||||
|
||||
a4_x = 31.5
|
||||
a4_y = 44.55
|
||||
# len_h = 25
|
||||
# len_v = 3
|
||||
rod_num = int(((len_h - 1)/(len_v*2)) + 1)
|
||||
|
||||
zk_width = 0.665
|
||||
zk_depth = 0.32
|
||||
zk_height = 1.64
|
||||
|
||||
start_x = ((a4_x - len_h) / 2) + zk_width
|
||||
start_y = 28
|
||||
|
||||
overedge = 0.5
|
||||
# date = "06.2023"
|
||||
|
||||
start_y2 = start_y - 7.5 #drugi uziom
|
||||
|
||||
doc = ezdxf.new(dxfversion="R2013", setup=False)
|
||||
msp = doc.modelspace()
|
||||
#doc.layers.add(name="tabele", color=0, linetype="SOLID")
|
||||
#doc.layers.add(name="bednarka", color=1, linetype="SOLID")
|
||||
#doc.layers.add(name="uziom", color=3, linetype="SOLID")
|
||||
#doc.layers.add(name="zk", color=0, linetype="SOLID")
|
||||
#doc.layers.add(name="opisy", color=0, linetype="SOLID")
|
||||
#doc.layers.add(name="grunt", color=0, linetype="SOLID")
|
||||
|
||||
hatch = msp.add_hatch(color=3)
|
||||
hatch_ground = msp.add_hatch(color=0)
|
||||
hatch_gravel = msp.add_hatch(color=254)
|
||||
|
||||
doc.styles.new("Arial", dxfattribs={"font" : "Arial.ttf"})
|
||||
|
||||
#WYDURK
|
||||
msp.add_line((0, 0), (0, a4_y), dxfattribs={"layer": "tabele", "color":0})
|
||||
msp.add_line((0, a4_y), (a4_x, a4_y), dxfattribs={"layer": "tabele", "color":0})
|
||||
msp.add_line((a4_x, a4_y), (a4_x, 0), dxfattribs={"layer": "tabele", "color":0})
|
||||
msp.add_line((a4_x, 0), (0, 0), dxfattribs={"layer": "tabele", "color":0})
|
||||
|
||||
|
||||
#UZIOM WIDOK Z Gory
|
||||
msp.add_line((start_x, start_y), (start_x + len_h, start_y), dxfattribs={"layer": "bednarka", "color":1})
|
||||
for rod in range(0,rod_num):
|
||||
#kola i kreskowanie
|
||||
msp.add_circle((start_x+1+(len_v*2*rod), start_y), 0.15, dxfattribs={"layer": "uziom", "color":3})
|
||||
edge_path = hatch.paths.add_edge_path()
|
||||
edge_path.add_arc((start_x+1+(len_v*2*rod), start_y), radius=0.15, start_angle=0, end_angle=360)
|
||||
#wymiary
|
||||
msp.add_line((start_x+1+(len_v*2*rod), start_y), (start_x+1+(len_v*2*rod), start_y+1), dxfattribs={"layer": "opisy"})
|
||||
if rod == rod_num-1: continue
|
||||
dim = msp.add_linear_dim(
|
||||
base=(start_x+1+(len_v*2*rod), start_y+1), # location of the dimension line
|
||||
p1=(start_x+1+(len_v*2*rod), start_y), # 1st measurement point
|
||||
p2=(start_x+1+(len_v*2*(rod+1)), start_y), # 2nd measurement point
|
||||
dimstyle="EZ_CURVED", # default dimension style
|
||||
override={
|
||||
# "dimtxsty": "Standard",
|
||||
"dimtxt": 0.5,
|
||||
"dimasz": 0.5,
|
||||
},
|
||||
text=str(len_v*2)+" m"
|
||||
).render()
|
||||
#wymiar 1. metra
|
||||
dim = msp.add_linear_dim(
|
||||
base=(start_x, start_y+1), # location of the dimension line
|
||||
p1=(start_x, start_y), # 1st measurement point
|
||||
p2=(start_x+1, start_y), # 2nd measurement point
|
||||
dimstyle="EZ_CURVED", # default dimension style
|
||||
override={
|
||||
# "dimtxsty": "Standard",
|
||||
"dimtxt": 0.5,
|
||||
"dimasz": 0.5,
|
||||
},
|
||||
text="1 m"
|
||||
).render()
|
||||
|
||||
#ZK WIDOK Z Gory
|
||||
msp.add_line((start_x-zk_width, start_y+zk_depth/2), (start_x, start_y+zk_depth/2), dxfattribs={"layer": "zk"})
|
||||
msp.add_line((start_x, start_y+zk_depth/2), (start_x, start_y-zk_depth/2), dxfattribs={"layer": "zk"})
|
||||
msp.add_line((start_x, start_y-zk_depth/2), (start_x-zk_width, start_y-zk_depth/2), dxfattribs={"layer": "zk"})
|
||||
msp.add_line((start_x-zk_width, start_y-zk_depth/2), (start_x-zk_width, start_y+zk_depth/2), dxfattribs={"layer": "zk"})
|
||||
|
||||
|
||||
|
||||
#UZIOM WIDOK Z BOKU
|
||||
msp.add_line((start_x - zk_width / 2, start_y2), (start_x + len_h, start_y2), dxfattribs={"layer": "bednarka", "color":1})
|
||||
msp.add_line((start_x - zk_width / 2, start_y2), (start_x - zk_width / 2, start_y2+0.2), dxfattribs={"layer": "bednarka", "color":1})
|
||||
|
||||
for rod in range(0,rod_num):
|
||||
msp.add_line((start_x+1+(rod*len_v*2), start_y2+0.02), (start_x+1+(rod*len_v*2), start_y2-len_v), dxfattribs={"layer": "uziom", "color":3})
|
||||
msp.add_line((start_x+1+(rod*len_v*2), start_y2+0.02), (start_x+1+(rod*len_v*2)+0.016, start_y2+0.02), dxfattribs={"layer": "uziom", "color":3})
|
||||
msp.add_line((start_x+1+(rod*len_v*2)+0.016, start_y2+0.02), (start_x+1+(rod*len_v*2)+0.016, start_y2-len_v), dxfattribs={"layer": "uziom", "color":3})
|
||||
msp.add_line((start_x+1+(rod*len_v*2), start_y2-len_v), (start_x+1+(rod*len_v*2)+0.016, start_y2-len_v), dxfattribs={"layer": "uziom", "color":3})
|
||||
|
||||
dim = msp.add_linear_dim(
|
||||
base=(start_x+0.5, start_y2+1), # location of the dimension line
|
||||
p1=(start_x+0.5, start_y2+1), # 1st measurement point
|
||||
p2=(start_x+0.5, start_y2), # 2nd measurement point
|
||||
dimstyle="EZ_CURVED", # default dimension style
|
||||
override={
|
||||
# "dimtxsty": "Standard",
|
||||
"dimtxt": 0.5,
|
||||
"dimasz": 0.5,
|
||||
"dimjust": 3,
|
||||
},
|
||||
text="h=1 m",
|
||||
angle=-90
|
||||
).render()
|
||||
|
||||
|
||||
#PODZIEMIE WIDOK Z BOKU
|
||||
msp.add_line((start_x, start_y2 + 1), (start_x + len_h + overedge, start_y2 + 1), dxfattribs={"layer": "grunt"})
|
||||
|
||||
hatch_ground.set_pattern_fill("EARTH2", scale=0.1)
|
||||
hatch_ground.paths.add_polyline_path(
|
||||
[(start_x, start_y2 + 1), (start_x + len_h + overedge, start_y2 + 1), (start_x + len_h + overedge, start_y2 + 0.5), (start_x, start_y2 + 0.5)], is_closed=True
|
||||
)
|
||||
|
||||
hatch_gravel.set_pattern_fill("CONCRETE2", scale=0.1)
|
||||
hatch_gravel.paths.add_polyline_path(
|
||||
[(start_x, start_y2 + 1), (start_x + len_h + overedge, start_y2 + 1), (start_x + len_h + overedge, start_y2 -(len_v+1)), (start_x, start_y2 -(len_v+1))], is_closed=True
|
||||
)
|
||||
|
||||
#ZK WIDOK Z BOKU
|
||||
msp.add_line((start_x - zk_width, start_y2+(zk_height/2)+1), (start_x, start_y2+(zk_height/2)+1), dxfattribs={"layer": "zk"})
|
||||
msp.add_line((start_x, start_y2+(zk_height/2)+1), (start_x, start_y2-(zk_height/2)+1), dxfattribs={"layer": "zk"})
|
||||
msp.add_line((start_x, start_y2-(zk_height/2)+1), (start_x-zk_width, start_y2-(zk_height/2)+1), dxfattribs={"layer": "zk"})
|
||||
msp.add_line((start_x-zk_width, start_y2-(zk_height/2)+1), (start_x - zk_width, start_y2+(zk_height/2)+1), dxfattribs={"layer": "zk"})
|
||||
|
||||
msp.add_text(object1, dxfattribs={"layer": "opisy", "height": 0.55, "rotation": 0, "style": "OpenSans"}).set_pos((start_x - (zk_width/2) , start_y2+2.3), align="CENTER")
|
||||
|
||||
|
||||
#OPISY
|
||||
msp.add_text("SCHEMAT ROZMIESZCZENIA UZIOMÓW", dxfattribs={"layer": "opisy", "height": 0.55, "rotation": 0, "style": "OpenSans"}).set_pos((a4_x/2, a4_y-5.5), align="CENTER")
|
||||
|
||||
#TABELKA
|
||||
start_xt = a4_x - 1.5 - 24
|
||||
start_yt = 2
|
||||
|
||||
points = [(start_xt, start_yt), (start_xt + 24, start_yt), (start_xt + 24, start_yt+4), (start_xt, start_yt+4), (start_xt, start_yt)]
|
||||
msp.add_lwpolyline(points, dxfattribs={"layer": "opisy"})
|
||||
msp.add_line((start_xt, start_yt+1), (start_xt + 16, start_yt+1), dxfattribs={"layer": "opisy"})
|
||||
msp.add_text("RYSUNEK", dxfattribs={"layer": "opisy", "height": 0.25, "rotation": 0, "style": "Arial"}).set_pos((start_xt+1.5, start_yt+1+0.17), align="CENTER")
|
||||
msp.add_text("Konfiguracja układu uziomowego", dxfattribs={"layer": "opisy", "height": 0.25, "rotation": 0, "style": "Arial"}).set_pos((start_xt+3+0.18, start_yt+1+0.17), align="LEFT")
|
||||
msp.add_line((start_xt, start_yt+1.6), (start_xt + 16, start_yt+1.6), dxfattribs={"layer": "opisy"})
|
||||
msp.add_text("TEMAT", dxfattribs={"layer": "opisy", "height": 0.25, "rotation": 0, "style": "Arial"}).set_pos((start_xt+1.5, start_yt+1.6+0.17), align="CENTER")
|
||||
msp.add_text(object2, dxfattribs={"layer": "opisy", "height": 0.25, "rotation": 0, "style": "Arial"}).set_pos((start_xt+3+0.18, start_yt+1.6+0.17), align="LEFT")
|
||||
msp.add_line((start_xt, start_yt+2.2), (start_xt + 16, start_yt+2.2), dxfattribs={"layer": "opisy"})
|
||||
msp.add_text("ADRES", dxfattribs={"layer": "opisy", "height": 0.25, "rotation": 0, "style": "Arial"}).set_pos((start_xt+1.5, start_yt+2.2+0.17), align="CENTER")
|
||||
msp.add_text(adres, dxfattribs={"layer": "opisy", "height": 0.25, "rotation": 0, "style": "Arial"}).set_pos((start_xt+3+0.18, start_yt+2.2+0.17), align="LEFT")
|
||||
msp.add_line((start_xt, start_yt+2.8), (start_xt + 16, start_yt+2.8), dxfattribs={"layer": "opisy"})
|
||||
msp.add_text("OBIEKT", dxfattribs={"layer": "opisy", "height": 0.25, "rotation": 0, "style": "Arial"}).set_pos((start_xt+1.5, start_yt+2.8+0.17), align="CENTER")
|
||||
msp.add_text(object3, dxfattribs={"layer": "opisy", "height": 0.25, "rotation": 0, "style": "Arial"}).set_pos((start_xt+3+0.18, start_yt+2.8+0.17), align="LEFT")
|
||||
msp.add_line((start_xt, start_yt+3.4), (start_xt + 16, start_yt+3.4), dxfattribs={"layer": "opisy"})
|
||||
msp.add_text("INWESTOR", dxfattribs={"layer": "opisy", "height": 0.25, "rotation": 0, "style": "Arial"}).set_pos((start_xt+1.5, start_yt+3.4+0.17), align="CENTER")
|
||||
msp.add_text("TAURON Dystrybucja S.A.", dxfattribs={"layer": "opisy", "height": 0.25, "rotation": 0, "style": "Arial"}).set_pos((start_xt+3+0.18, start_yt+3.4+0.17), align="LEFT")
|
||||
|
||||
msp.add_line((start_xt+3, start_yt), (start_xt + 3, start_yt+4), dxfattribs={"layer": "opisy"})
|
||||
msp.add_line((start_xt+16, start_yt), (start_xt + 16, start_yt+4), dxfattribs={"layer": "opisy"})
|
||||
|
||||
msp.add_line((start_xt+3, start_yt+0.5), (start_xt + 16, start_yt+0.5), dxfattribs={"layer": "opisy"})
|
||||
msp.add_text("STADIUM", dxfattribs={"layer": "opisy", "height": 0.25, "rotation": 0, "style": "Arial"}).set_pos((start_xt+3+1.625, start_yt+0.5+0.15), align="CENTER")
|
||||
msp.add_text("PB", dxfattribs={"layer": "opisy", "height": 0.25, "rotation": 0, "style": "Arial"}).set_pos((start_xt+3+1.625, start_yt+0.15), align="CENTER")
|
||||
msp.add_line((start_xt+3+3.25, start_yt), (start_xt + 3 + 3.25, start_yt+1), dxfattribs={"layer": "opisy"})
|
||||
msp.add_text("DATA", dxfattribs={"layer": "opisy", "height": 0.25, "rotation": 0, "style": "Arial"}).set_pos((start_xt+3+3.25+1.625, start_yt+0.5+0.15), align="CENTER")
|
||||
msp.add_text(date, dxfattribs={"layer": "opisy", "height": 0.25, "rotation": 0, "style": "Arial"}).set_pos((start_xt+3+3.25+1.625, start_yt+0.15), align="CENTER")
|
||||
msp.add_line((start_xt+3+6.5, start_yt), (start_xt + 3 + 6.5, start_yt+1), dxfattribs={"layer": "opisy"})
|
||||
msp.add_text("SKALA", dxfattribs={"layer": "opisy", "height": 0.25, "rotation": 0, "style": "Arial"}).set_pos((start_xt+3+6.5+1.625, start_yt+0.5+0.15), align="CENTER")
|
||||
msp.add_text("1:150", dxfattribs={"layer": "opisy", "height": 0.25, "rotation": 0, "style": "Arial"}).set_pos((start_xt+3+6.5+1.625, start_yt+0.15), align="CENTER")
|
||||
msp.add_line((start_xt+3+9.75, start_yt), (start_xt + 3 + 9.75, start_yt+1), dxfattribs={"layer": "opisy"})
|
||||
msp.add_text("NR RYS", dxfattribs={"layer": "opisy", "height": 0.25, "rotation": 0, "style": "Arial"}).set_pos((start_xt+3+9.75+1.625, start_yt+0.5+0.15), align="CENTER")
|
||||
msp.add_text("5", dxfattribs={"layer": "opisy", "height": 0.25, "rotation": 0, "style": "Arial"}).set_pos((start_xt+3+9.75+1.625, start_yt+0.15), align="CENTER")
|
||||
|
||||
|
||||
msp.add_text("pręt uziemiający Ø16 - dł. "+ str(len_v) +" mb - "+ str(rod_num) +" szt.", dxfattribs={"layer": "opisy", "height": 0.25, "rotation": 0, "style": "Arial"}).set_pos((start_xt+2.5, start_yt+4+0.7), align="LEFT")
|
||||
msp.add_circle((start_xt+1.5, start_yt + 4.8), 0.15, dxfattribs={"layer": "uziom", "color":3})
|
||||
edge_path = hatch.paths.add_edge_path()
|
||||
edge_path.add_arc((start_xt+1.5, start_yt + 4.8), radius=0.15, start_angle=0, end_angle=360)
|
||||
|
||||
msp.add_text("płaskownik St/Zn 30x4 mm - dł. "+ str(len_h) +" mb", dxfattribs={"layer": "opisy", "height": 0.25, "rotation": 0, "style": "Arial"}).set_pos((start_xt+2.5, start_yt+4+1.4), align="LEFT")
|
||||
msp.add_line((start_xt +0.8, start_yt+4+1.5), (start_xt + 2.1, start_yt+4+1.5), dxfattribs={"layer": "bednarka", "color":1})
|
||||
|
||||
|
||||
doc.saveas("public/uziom.dxf")
|
||||
BIN
wet_input_15.docx
Normal file
BIN
wet_input_15.docx
Normal file
Binary file not shown.
BIN
wet_input_3.docx
Normal file
BIN
wet_input_3.docx
Normal file
Binary file not shown.
Reference in New Issue
Block a user