+
This commit is contained in:
Binary file not shown.
+191
-11
@@ -6,14 +6,20 @@ const bodyParser = require('body-parser');
|
|||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const multer = require('multer'); // Add this to your dependencies
|
const multer = require('multer'); // Add this to your dependencies
|
||||||
const storage = multer.memoryStorage();
|
const storage = multer.memoryStorage();
|
||||||
const upload = multer({ storage: storage });
|
const upload = multer({
|
||||||
|
storage: storage,
|
||||||
|
limits: {
|
||||||
|
fileSize: 50 * 1024 * 1024 // 50MB limit
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const port = 5000;
|
const port = 5000;
|
||||||
|
|
||||||
// Middleware
|
// Middleware
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json({limit: '50mb'})); // Increase JSON payload limit
|
||||||
|
app.use(bodyParser.urlencoded({limit: '50mb', extended: true})); // Increase URL-encoded payload limit
|
||||||
|
|
||||||
// SQLite database setup
|
// SQLite database setup
|
||||||
const db = new sqlite3.Database('./database.db', (err) => {
|
const db = new sqlite3.Database('./database.db', (err) => {
|
||||||
@@ -195,31 +201,46 @@ app.get('/games/:gameId/playerchars', (req, res) => {
|
|||||||
// Fetch game NPCs
|
// Fetch game NPCs
|
||||||
app.get('/games/:gameId/npcs', (req, res) => {
|
app.get('/games/:gameId/npcs', (req, res) => {
|
||||||
const gameId = req.params.gameId;
|
const gameId = req.params.gameId;
|
||||||
db.all('SELECT * FROM NPC WHERE GameID = ?', [gameId], (err, row) => {
|
db.all('SELECT * FROM NPC WHERE GameID = ?', [gameId], (err, rows) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return res.status(500).json({ error: 'Internal server error' });
|
return res.status(500).json({ error: 'Internal server error' });
|
||||||
}
|
}
|
||||||
res.json(row);
|
// Process each row and convert BLOB to base64 if image exists
|
||||||
|
const processedRows = rows.map(row => {
|
||||||
|
if (row.Img) {
|
||||||
|
row.Img = `data:image/jpeg;base64,${row.Img.toString('base64')}`;
|
||||||
|
}
|
||||||
|
return row;
|
||||||
});
|
});
|
||||||
})
|
res.json(processedRows);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Fetch game Items
|
// Fetch game Items
|
||||||
app.get('/games/:gameId/items', (req, res) => {
|
app.get('/games/:gameId/items', (req, res) => {
|
||||||
const gameId = req.params.gameId;
|
const gameId = req.params.gameId;
|
||||||
db.all(`
|
db.all(`
|
||||||
SELECT Item.*, PlayerCharacter.CharName as OwnerName
|
SELECT i.*,
|
||||||
FROM Item
|
COALESCE(pc.CharName, n.Name) as OwnerName
|
||||||
LEFT JOIN PlayerCharacter ON Item.OwnerID = PlayerCharacter.CharID
|
FROM Item i
|
||||||
WHERE Item.GameID = ?`,
|
LEFT JOIN PlayerCharacter pc ON i.OwnerID = pc.CharID
|
||||||
|
LEFT JOIN NPC n ON i.OwnerID = n.NPCID
|
||||||
|
WHERE i.GameID = ?`,
|
||||||
[gameId],
|
[gameId],
|
||||||
(err, rows) => {
|
(err, rows) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return res.status(500).json({ error: 'Internal server error' });
|
return res.status(500).json({ error: 'Internal server error' });
|
||||||
}
|
}
|
||||||
res.json(rows);
|
const processedRows = rows.map(row => {
|
||||||
|
if (row.img) {
|
||||||
|
row.Img = `data:image/jpeg;base64,${row.img.toString('base64')}`;
|
||||||
|
}
|
||||||
|
return row;
|
||||||
|
});
|
||||||
|
res.json(processedRows);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
// Player Part
|
// Player Part
|
||||||
@@ -398,6 +419,165 @@ app.post('/games/:charId/:itemId/owner', (req, res) => {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Update Item details including owner
|
||||||
|
app.put('/games/item/:itemId', async (req, res) => {
|
||||||
|
const itemId = req.params.itemId;
|
||||||
|
const {
|
||||||
|
ItemName, Type, Art, Rarity, MaxDurability, CurrentDurability,
|
||||||
|
GoldValue, Abilities, OwnerID, AP
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
|
const stmt = db.prepare(`
|
||||||
|
UPDATE Item
|
||||||
|
SET ItemName = ?, Type = ?, Art = ?, Rarity = ?,
|
||||||
|
MaxDurability = ?, CurrentDurability = ?, GoldValue = ?,
|
||||||
|
Abilities = ?, OwnerID = ?, AP = ?
|
||||||
|
WHERE ItemID = ?
|
||||||
|
`);
|
||||||
|
|
||||||
|
stmt.run([
|
||||||
|
ItemName, Type, Art, Rarity, MaxDurability, CurrentDurability,
|
||||||
|
GoldValue, Abilities, OwnerID, AP, itemId
|
||||||
|
], function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.error('Database error:', err);
|
||||||
|
return res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
if (this.changes === 0) {
|
||||||
|
return res.status(404).json({ error: 'Item not found' });
|
||||||
|
}
|
||||||
|
res.json({ message: 'Item updated successfully!' });
|
||||||
|
});
|
||||||
|
stmt.finalize();
|
||||||
|
});
|
||||||
|
|
||||||
|
//NPC part
|
||||||
|
|
||||||
|
// Create NPC
|
||||||
|
app.post('/games/npc/create', upload.single('image'), (req, res) => {
|
||||||
|
const {
|
||||||
|
Name, Race, Sex, Age, Job, Description,
|
||||||
|
MaxHealth, MaxMana, Strength, Dexterity,
|
||||||
|
Agility, Endurance, Allied, Level, GameID
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
|
const imageBuffer = req.file ? req.file.buffer : null;
|
||||||
|
|
||||||
|
const stmt = db.prepare(`
|
||||||
|
INSERT INTO NPC (
|
||||||
|
GameID, Name, Race, Sex, Age, Job, Description,
|
||||||
|
MaxHealth, CurrentHealth, MaxMana, CurrentMana,
|
||||||
|
Strength, Dexterity, Agility, Endurance,
|
||||||
|
Level, Allied, Img
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
`);
|
||||||
|
|
||||||
|
stmt.run([
|
||||||
|
GameID, Name, Race, Sex, Age, Job, Description,
|
||||||
|
MaxHealth, MaxHealth, MaxMana, MaxMana,
|
||||||
|
Strength, Dexterity, Agility, Endurance,
|
||||||
|
Level, Allied, imageBuffer
|
||||||
|
], function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.error('Database error:', err);
|
||||||
|
return res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
res.status(201).json({
|
||||||
|
message: 'NPC created successfully!',
|
||||||
|
npcId: this.lastID
|
||||||
|
});
|
||||||
|
});
|
||||||
|
stmt.finalize();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update NPC details
|
||||||
|
app.put('/games/npc/:npcId', upload.single('image'), (req, res) => {
|
||||||
|
const npcId = req.params.npcId;
|
||||||
|
const {
|
||||||
|
Name, Race, Sex, Age, Job, Description,
|
||||||
|
MaxHealth, CurrentHealth, MaxMana, CurrentMana,
|
||||||
|
Strength, Dexterity, Agility, Endurance,
|
||||||
|
Level, Allied
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
|
let updateQuery = `
|
||||||
|
UPDATE NPC SET
|
||||||
|
Name = ?, Race = ?, Sex = ?, Age = ?, Job = ?,
|
||||||
|
Description = ?, MaxHealth = ?, CurrentHealth = ?,
|
||||||
|
MaxMana = ?, CurrentMana = ?, Strength = ?,
|
||||||
|
Dexterity = ?, Agility = ?, Endurance = ?,
|
||||||
|
Level = ?, Allied = ?
|
||||||
|
`;
|
||||||
|
|
||||||
|
let params = [
|
||||||
|
Name, Race, Sex, Age, Job, Description,
|
||||||
|
MaxHealth, CurrentHealth, MaxMana, CurrentMana,
|
||||||
|
Strength, Dexterity, Agility, Endurance,
|
||||||
|
Level, Allied
|
||||||
|
];
|
||||||
|
|
||||||
|
// If new image is uploaded, add it to the update
|
||||||
|
if (req.file) {
|
||||||
|
updateQuery += `, Img = ?`;
|
||||||
|
params.push(req.file.buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateQuery += ` WHERE NPCID = ?`;
|
||||||
|
params.push(npcId);
|
||||||
|
|
||||||
|
const stmt = db.prepare(updateQuery);
|
||||||
|
stmt.run(params, function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.error('Database error:', err);
|
||||||
|
return res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
if (this.changes === 0) {
|
||||||
|
return res.status(404).json({ error: 'NPC not found' });
|
||||||
|
}
|
||||||
|
res.json({ message: 'NPC updated successfully!' });
|
||||||
|
});
|
||||||
|
stmt.finalize();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get NPC details
|
||||||
|
app.get('/games/npc/:npcId', (req, res) => {
|
||||||
|
const npcId = req.params.npcId;
|
||||||
|
db.get('SELECT * FROM NPC WHERE NPCID = ?', [npcId], (err, row) => {
|
||||||
|
if (err) {
|
||||||
|
return res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
if (!row) {
|
||||||
|
return res.status(404).json({ error: 'NPC not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert BLOB to base64 string if image exists
|
||||||
|
if (row.Img) {
|
||||||
|
row.Img = `data:image/jpeg;base64,${row.Img.toString('base64')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(row);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update NPC image
|
||||||
|
app.put('/games/npc/:npcId/image', upload.single('image'), (req, res) => {
|
||||||
|
const npcId = req.params.npcId;
|
||||||
|
const imageBuffer = req.file.buffer;
|
||||||
|
|
||||||
|
const stmt = db.prepare('UPDATE NPC SET Img = ? WHERE NPCID = ?');
|
||||||
|
stmt.run([imageBuffer, npcId], function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.error('Database error:', err);
|
||||||
|
return res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
if (this.changes === 0) {
|
||||||
|
return res.status(404).json({ error: 'NPC not found' });
|
||||||
|
}
|
||||||
|
res.json({ message: 'NPC image updated successfully!' });
|
||||||
|
});
|
||||||
|
stmt.finalize();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import Games from './pages/games.jsx';
|
|||||||
import GameMasterPage from './pages/gameMasterPage.jsx';
|
import GameMasterPage from './pages/gameMasterPage.jsx';
|
||||||
import CreateItem from './pages/createItem.jsx';
|
import CreateItem from './pages/createItem.jsx';
|
||||||
import CreateCharacter from './pages/createCharacter.jsx';
|
import CreateCharacter from './pages/createCharacter.jsx';
|
||||||
|
import CreateNpc from './pages/createNpc.jsx';
|
||||||
import { UserProvider } from './context/UserContext.jsx';
|
import { UserProvider } from './context/UserContext.jsx';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
@@ -79,6 +80,10 @@ function App() {
|
|||||||
path='/create-item'
|
path='/create-item'
|
||||||
element={<CreateItem isLoggedIn={isLoggedIn} />}
|
element={<CreateItem isLoggedIn={isLoggedIn} />}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path='/create-npc'
|
||||||
|
element={<CreateNpc isLoggedIn={isLoggedIn} />}
|
||||||
|
/>
|
||||||
|
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -89,6 +89,18 @@ const CreateItem = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 3, background: 'rgba(30, 30, 47, 0.9)', borderRadius: '8px', marginTop: '40px' }}>
|
<Box sx={{ p: 3, background: 'rgba(30, 30, 47, 0.9)', borderRadius: '8px', marginTop: '40px' }}>
|
||||||
|
<Button
|
||||||
|
onClick={() => navigate(`/games/${gameId}/master`)}
|
||||||
|
variant="contained"
|
||||||
|
sx={{
|
||||||
|
backgroundColor: '#764ACB',
|
||||||
|
'&:hover': { backgroundColor: '#9865f7' },
|
||||||
|
mb: 3
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Back to Game
|
||||||
|
</Button>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<Grid2 container spacing={3}>
|
<Grid2 container spacing={3}>
|
||||||
{/* Left Column - Image and Basic Info */}
|
{/* Left Column - Image and Basic Info */}
|
||||||
|
|||||||
@@ -0,0 +1,417 @@
|
|||||||
|
import React, { useState, useContext } from 'react';
|
||||||
|
import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
|
import { UserContext } from '../context/UserContext';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { Box, TextField, Button, Typography, Select, MenuItem, FormControl, InputLabel, Grid2, Card, CardContent, CardMedia } from '@mui/material';
|
||||||
|
|
||||||
|
// Import the same icons as games.jsx
|
||||||
|
import PaidIcon from '@mui/icons-material/Paid';
|
||||||
|
import CalendarTodayIcon from '@mui/icons-material/CalendarToday';
|
||||||
|
import PetsIcon from '@mui/icons-material/Pets';
|
||||||
|
import WcIcon from '@mui/icons-material/Wc';
|
||||||
|
import WorkIcon from '@mui/icons-material/Work';
|
||||||
|
import HeartIcon from '@mui/icons-material/Favorite';
|
||||||
|
import WaterDropIcon from '@mui/icons-material/WaterDrop';
|
||||||
|
import KeyboardDoubleArrowUpIcon from '@mui/icons-material/KeyboardDoubleArrowUp';
|
||||||
|
import SecurityIcon from '@mui/icons-material/Security';
|
||||||
|
|
||||||
|
import defaultCharacterImage from '../assets/default-character.png';
|
||||||
|
|
||||||
|
const CreateNpc = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
const { userId } = useContext(UserContext);
|
||||||
|
const gameId = new URLSearchParams(location.search).get('gameId');
|
||||||
|
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
Name: '',
|
||||||
|
Race: '',
|
||||||
|
Sex: '',
|
||||||
|
Age: '',
|
||||||
|
Job: '',
|
||||||
|
Description: '',
|
||||||
|
MaxHealth: 100,
|
||||||
|
MaxMana: 100,
|
||||||
|
Strength: 10,
|
||||||
|
Dexterity: 10,
|
||||||
|
Agility: 10,
|
||||||
|
Endurance: 10,
|
||||||
|
Allied: 0, // 0 = Allied, 1 = Neutral, 2 = Enemy
|
||||||
|
Level: 1,
|
||||||
|
GameID: gameId
|
||||||
|
});
|
||||||
|
|
||||||
|
const [selectedImage, setSelectedImage] = useState(null);
|
||||||
|
const [imagePreview, setImagePreview] = useState(null);
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
[e.target.name]: e.target.value
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleImageChange = (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
setSelectedImage(file);
|
||||||
|
setImagePreview(URL.createObjectURL(file));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const formDataToSend = new FormData();
|
||||||
|
|
||||||
|
Object.keys(formData).forEach(key => {
|
||||||
|
formDataToSend.append(key, formData[key]);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selectedImage) {
|
||||||
|
formDataToSend.append('image', selectedImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
'http://localhost:5000/games/npc/create',
|
||||||
|
formDataToSend,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.status === 201) {
|
||||||
|
navigate(`/games/${gameId}/master`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating NPC:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const inputStyles = {
|
||||||
|
'& .MuiOutlinedInput-root': {
|
||||||
|
'& fieldset': { borderColor: '#444' },
|
||||||
|
'&:hover fieldset': { borderColor: '#764ACB' },
|
||||||
|
'&.Mui-focused fieldset': { borderColor: '#764ACB' },
|
||||||
|
},
|
||||||
|
'& .MuiInputLabel-root': { color: '#fff' },
|
||||||
|
'& .MuiInputBase-input': { color: '#fff' }
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 3, background: 'rgba(30, 30, 47, 0.9)', borderRadius: '8px', marginTop: '40px' }}>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<Grid2 container spacing={3}>
|
||||||
|
{/* NPC Image and Basic Info */}
|
||||||
|
<Grid2 item xs={12} md={4}>
|
||||||
|
<Box sx={{ border: '1px solid #444', borderRadius: '3px', p: 2, backgroundColor: '#2e2e3f' }}>
|
||||||
|
<Card sx={{ width: '400px', backgroundColor: '#1e1e2f', color: '#fff', height: '635px' }}>
|
||||||
|
{/* Image Upload Section - Similar to CreateCharacter */}
|
||||||
|
{imagePreview ? (
|
||||||
|
<Box component="label" htmlFor="image-upload" sx={{ cursor: 'pointer', position: 'relative' }}>
|
||||||
|
<input
|
||||||
|
accept="image/*"
|
||||||
|
type="file"
|
||||||
|
id="image-upload"
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
onChange={handleImageChange}
|
||||||
|
/>
|
||||||
|
<CardMedia
|
||||||
|
component="img"
|
||||||
|
height="300"
|
||||||
|
image={imagePreview}
|
||||||
|
alt="NPC Preview"
|
||||||
|
sx={{ borderRadius: '3px', objectFit: 'contain', margin: '0 auto', borderBottom: '1px solid #444' }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box
|
||||||
|
component="label"
|
||||||
|
htmlFor="image-upload"
|
||||||
|
sx={{
|
||||||
|
height: 300,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: '#2e2e3f',
|
||||||
|
borderRadius: '3px',
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
accept="image/*"
|
||||||
|
type="file"
|
||||||
|
id="image-upload"
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
onChange={handleImageChange}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
component="span"
|
||||||
|
sx={{
|
||||||
|
backgroundColor: '#764ACB',
|
||||||
|
'&:hover': { backgroundColor: '#9865f7' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Bild Hochladen
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<CardContent sx={{ p: 3 }}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Name des NPCs"
|
||||||
|
name="Name"
|
||||||
|
value={formData.Name}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
sx={{ ...inputStyles, mb: 2, width: '347px'}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Grid2 container spacing={2}>
|
||||||
|
<Grid2 item xs={6}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Alter"
|
||||||
|
name="Age"
|
||||||
|
type="number"
|
||||||
|
value={formData.Age}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
sx={{ ...inputStyles, width: '165px' }}
|
||||||
|
/>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2 item xs={6}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Rasse"
|
||||||
|
name="Race"
|
||||||
|
value={formData.Race}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
sx={{ ...inputStyles, width: '165px' }}
|
||||||
|
/>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
|
||||||
|
<Grid2 container spacing={2} sx={{ mt: 1 }}>
|
||||||
|
<Grid2 item xs={6}>
|
||||||
|
<FormControl sx={{ width: '165px' }}>
|
||||||
|
<InputLabel sx={{ color: '#fff' }}>Geschlecht</InputLabel>
|
||||||
|
<Select
|
||||||
|
name="Sex"
|
||||||
|
value={formData.Sex}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
sx={{
|
||||||
|
color: '#fff',
|
||||||
|
'& .MuiOutlinedInput-notchedOutline': { borderColor: '#444' },
|
||||||
|
'&:hover .MuiOutlinedInput-notchedOutline': { borderColor: '#764ACB' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MenuItem value="Male">Männlich</MenuItem>
|
||||||
|
<MenuItem value="Female">Weiblich</MenuItem>
|
||||||
|
<MenuItem value="Other">Divers</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2 item xs={6}>
|
||||||
|
<FormControl sx={{ width: '165px' }}>
|
||||||
|
<InputLabel sx={{ color: '#fff' }}>Status</InputLabel>
|
||||||
|
<Select
|
||||||
|
name="Allied"
|
||||||
|
value={formData.Allied}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
sx={{
|
||||||
|
color: '#fff',
|
||||||
|
'& .MuiOutlinedInput-notchedOutline': { borderColor: '#444' },
|
||||||
|
'&:hover .MuiOutlinedInput-notchedOutline': { borderColor: '#764ACB' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MenuItem value={0}>Verbündet</MenuItem>
|
||||||
|
<MenuItem value={1}>Neutral</MenuItem>
|
||||||
|
<MenuItem value={2}>Feindlich</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
|
||||||
|
<Grid2 container spacing={2} sx={{ mt: 1 }}>
|
||||||
|
<Grid2 item xs={6}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Beruf/Klasse"
|
||||||
|
name="Job"
|
||||||
|
value={formData.Job}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
sx={{ ...inputStyles, width: '165px' }}
|
||||||
|
/>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2 item xs={6}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Level"
|
||||||
|
name="Level"
|
||||||
|
type="number"
|
||||||
|
value={formData.Level}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
sx={{ ...inputStyles, width: '165px' }}
|
||||||
|
/>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
</Grid2>
|
||||||
|
|
||||||
|
{/* Right Column - Stats and Description */}
|
||||||
|
<Grid2 item xs={12} md={8}>
|
||||||
|
{/* Health/Mana Bars */}
|
||||||
|
<Box sx={{ display: 'flex', gap: 2, mb: 4, width: '810px' }}>
|
||||||
|
<Box sx={{ flex: 1, backgroundColor: '#2e2e3f', borderRadius: '8px', p: 2, border: '1px solid #444' }}>
|
||||||
|
<Typography variant="body1" sx={{ display: 'flex', justifyContent: 'space-between', color: '#fff' }}>
|
||||||
|
<HeartIcon sx={{ color: "red" }} />
|
||||||
|
<span>Maximale Gesundheit</span>
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
name="MaxHealth"
|
||||||
|
type="number"
|
||||||
|
value={formData.MaxHealth}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
sx={inputStyles}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ flex: 1, backgroundColor: '#2e2e3f', borderRadius: '8px', p: 2, border: '1px solid #444' }}>
|
||||||
|
<Typography variant="body1" sx={{ display: 'flex', justifyContent: 'space-between', color: '#fff' }}>
|
||||||
|
<WaterDropIcon sx={{ color: 'blue' }} />
|
||||||
|
<span>Maximales Mana</span>
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
name="MaxMana"
|
||||||
|
type="number"
|
||||||
|
value={formData.MaxMana}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
sx={inputStyles}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
<Box sx={{ mb: 4, width: '810px' }}>
|
||||||
|
<Typography variant="h5" sx={{ color: '#fff', mb: 2 }}>Beschreibung</Typography>
|
||||||
|
<Box sx={{ backgroundColor: '#2e2e3f', p: 2, borderRadius: '8px', border: '1px solid #444' }}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
rows={4}
|
||||||
|
name="Description"
|
||||||
|
value={formData.Description}
|
||||||
|
onChange={handleChange}
|
||||||
|
sx={inputStyles}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Stats */}
|
||||||
|
<Box sx={{ mb: 4, width: '810px' }}>
|
||||||
|
<Typography variant="h5" sx={{ color: '#fff', mb: 2 }}>Attribute</Typography>
|
||||||
|
<Box sx={{
|
||||||
|
backgroundColor: '#2e2e3f',
|
||||||
|
p: 2,
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #444'
|
||||||
|
}}>
|
||||||
|
<Grid2 container spacing={2} justifyContent="space-between">
|
||||||
|
<Grid2 item>
|
||||||
|
<TextField
|
||||||
|
label="Stärke"
|
||||||
|
name="Strength"
|
||||||
|
type="number"
|
||||||
|
value={formData.Strength}
|
||||||
|
onChange={handleChange}
|
||||||
|
sx={{ ...inputStyles, width: '180px' }}
|
||||||
|
/>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2 item>
|
||||||
|
<TextField
|
||||||
|
label="Geschicklichkeit"
|
||||||
|
name="Dexterity"
|
||||||
|
type="number"
|
||||||
|
value={formData.Dexterity}
|
||||||
|
onChange={handleChange}
|
||||||
|
sx={{ ...inputStyles, width: '180px' }}
|
||||||
|
/>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2 item>
|
||||||
|
<TextField
|
||||||
|
label="Beweglichkeit"
|
||||||
|
name="Agility"
|
||||||
|
type="number"
|
||||||
|
value={formData.Agility}
|
||||||
|
onChange={handleChange}
|
||||||
|
sx={{ ...inputStyles, width: '180px' }}
|
||||||
|
/>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2 item>
|
||||||
|
<TextField
|
||||||
|
label="Ausdauer"
|
||||||
|
name="Endurance"
|
||||||
|
type="number"
|
||||||
|
value={formData.Endurance}
|
||||||
|
onChange={handleChange}
|
||||||
|
sx={{ ...inputStyles, width: '180px' }}
|
||||||
|
/>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Button Container */}
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
gap: 2,
|
||||||
|
width: '800px'
|
||||||
|
}}>
|
||||||
|
<Button
|
||||||
|
onClick={() => navigate(`/games/${gameId}/master`)}
|
||||||
|
variant="contained"
|
||||||
|
sx={{
|
||||||
|
backgroundColor: '#764ACB',
|
||||||
|
'&:hover': { backgroundColor: '#9865f7' },
|
||||||
|
width: '200px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Zurück ohne Speichern
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
sx={{
|
||||||
|
backgroundColor: '#764ACB',
|
||||||
|
'&:hover': { backgroundColor: '#9865f7' },
|
||||||
|
width: '580px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
NPC Erstellen
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
</form>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateNpc;
|
||||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect, useContext } from 'react';
|
|||||||
import { Link, useParams } from 'react-router-dom';
|
import { Link, useParams } from 'react-router-dom';
|
||||||
import { UserContext } from '../context/UserContext';
|
import { UserContext } from '../context/UserContext';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { Box, Typography, Grid2, Card, CardContent, CardMedia, Button, IconButton } from '@mui/material';
|
import { Box, Typography, Grid2, Card, CardContent, CardMedia, Button, IconButton, Dialog, DialogTitle, DialogContent, DialogActions, TextField, FormControl, InputLabel, Select, MenuItem, Tooltip } from '@mui/material';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
|
|
||||||
import defaultCharacterImage from '../assets/default-character.png';
|
import defaultCharacterImage from '../assets/default-character.png';
|
||||||
@@ -14,6 +14,12 @@ const GameMasterPage = () => {
|
|||||||
const [playerCharacters, setPlayerCharacters] = useState([]);
|
const [playerCharacters, setPlayerCharacters] = useState([]);
|
||||||
const [npcs, setNpcs] = useState([]);
|
const [npcs, setNpcs] = useState([]);
|
||||||
const [items, setItems] = useState([]);
|
const [items, setItems] = useState([]);
|
||||||
|
const [editModalOpen, setEditModalOpen] = useState(false);
|
||||||
|
const [selectedItem, setSelectedItem] = useState(null);
|
||||||
|
const [editType, setEditType] = useState(''); // 'item', 'npc', or 'character'
|
||||||
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
const [allOwners, setAllOwners] = useState([]);
|
||||||
|
const [formData, setFormData] = useState(null);
|
||||||
|
|
||||||
// Fix useEffect data fetching
|
// Fix useEffect data fetching
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -39,10 +45,400 @@ const GameMasterPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fetchData();
|
fetchData();
|
||||||
const interval = setInterval(fetchData, 5000);
|
let interval;
|
||||||
return () => clearInterval(interval);
|
if (!isEditing && !editModalOpen) { // Add editModalOpen check
|
||||||
|
interval = setInterval(fetchData, 10000);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
if (interval) clearInterval(interval);
|
||||||
|
};
|
||||||
|
}, [gameId, isEditing, editModalOpen]); // Add editModalOpen dependency
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchOwners = async () => {
|
||||||
|
try {
|
||||||
|
// Only fetch player characters
|
||||||
|
const pcsResponse = await axios.get(`http://localhost:5000/games/${gameId}/playerchars`);
|
||||||
|
|
||||||
|
const pcs = pcsResponse.data.map(pc => ({
|
||||||
|
id: pc.CharID,
|
||||||
|
name: pc.CharName,
|
||||||
|
type: 'Player Character'
|
||||||
|
}));
|
||||||
|
|
||||||
|
setAllOwners([
|
||||||
|
{ id: null, name: 'Unassigned', type: 'None' },
|
||||||
|
...pcs
|
||||||
|
]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching potential owners:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchOwners();
|
||||||
}, [gameId]);
|
}, [gameId]);
|
||||||
|
|
||||||
|
const handleItemClick = (item) => {
|
||||||
|
setSelectedItem(item);
|
||||||
|
setFormData({...item});
|
||||||
|
setEditType('item');
|
||||||
|
setEditModalOpen(true);
|
||||||
|
setIsEditing(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNpcClick = (npc) => {
|
||||||
|
setSelectedItem(npc);
|
||||||
|
setFormData({...npc});
|
||||||
|
setEditType('npc');
|
||||||
|
setEditModalOpen(true);
|
||||||
|
setIsEditing(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCharacterClick = (character) => {
|
||||||
|
setSelectedItem(character);
|
||||||
|
setFormData({...character});
|
||||||
|
setEditType('character');
|
||||||
|
setEditModalOpen(true);
|
||||||
|
setIsEditing(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdate = async () => {
|
||||||
|
try {
|
||||||
|
let response;
|
||||||
|
if (editType === 'item') {
|
||||||
|
response = await axios.put(`http://localhost:5000/games/item/${selectedItem.ItemID}`, formData);
|
||||||
|
} else if (editType === 'npc') {
|
||||||
|
response = await axios.put(`http://localhost:5000/games/npc/${selectedItem.NPCID}`, formData);
|
||||||
|
} else if (editType === 'character') {
|
||||||
|
response = await axios.put(`http://localhost:5000/games/character/${selectedItem.CharID}`, formData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
setEditModalOpen(false);
|
||||||
|
setIsEditing(false);
|
||||||
|
setFormData(null);
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalClose = () => {
|
||||||
|
setEditModalOpen(false);
|
||||||
|
setIsEditing(false);
|
||||||
|
setFormData(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// First, define the common input styles at component level
|
||||||
|
const commonStyles = {
|
||||||
|
dialog: {
|
||||||
|
'& .MuiPaper-root': {
|
||||||
|
backgroundColor: '#1e1e2f',
|
||||||
|
color: '#fff',
|
||||||
|
border: '1px solid #444',
|
||||||
|
borderRadius: '8px',
|
||||||
|
minWidth: '500px'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
'& .MuiOutlinedInput-root': {
|
||||||
|
color: '#fff',
|
||||||
|
'& fieldset': { borderColor: '#444' },
|
||||||
|
'&:hover fieldset': { borderColor: '#764ACB' },
|
||||||
|
'&.Mui-focused fieldset': { borderColor: '#764ACB' },
|
||||||
|
},
|
||||||
|
'& .MuiInputLabel-root': {
|
||||||
|
color: '#fff',
|
||||||
|
'&.Mui-focused': {
|
||||||
|
color: '#764ACB'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'& .MuiInputBase-input': { color: '#fff' }
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
color: '#fff',
|
||||||
|
'.MuiInputLabel-root': {
|
||||||
|
color: '#fff',
|
||||||
|
'&.Mui-focused': {
|
||||||
|
color: '#764ACB'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'.MuiSelect-select': {
|
||||||
|
color: '#fff',
|
||||||
|
backgroundColor: '#1e1e2f'
|
||||||
|
},
|
||||||
|
'.MuiOutlinedInput-root': {
|
||||||
|
color: '#fff',
|
||||||
|
backgroundColor: '#1e1e2f',
|
||||||
|
'& fieldset': { borderColor: '#444' },
|
||||||
|
'&:hover fieldset': { borderColor: '#764ACB' },
|
||||||
|
'&.Mui-focused fieldset': { borderColor: '#764ACB' }
|
||||||
|
},
|
||||||
|
'.MuiSelect-icon': { color: '#fff' }
|
||||||
|
},
|
||||||
|
menuItem: {
|
||||||
|
backgroundColor: '#1e1e2f',
|
||||||
|
color: '#fff',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: '#2e2e3f'
|
||||||
|
},
|
||||||
|
'&.Mui-selected': {
|
||||||
|
backgroundColor: '#764ACB',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: '#9865f7'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update the EditModal component
|
||||||
|
const EditModal = () => {
|
||||||
|
const nonEditableFields = [
|
||||||
|
'GameID', 'CharID', 'ItemID', 'NPCID', 'Img', 'img',
|
||||||
|
'GameId', 'NpcID', 'itemID', 'PlayerID', 'OwnerName'
|
||||||
|
];
|
||||||
|
|
||||||
|
const renderField = (key) => {
|
||||||
|
if (key === 'Allied') {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth key={key} sx={{ mb: 2, ...commonStyles.select }}>
|
||||||
|
<InputLabel>Status</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={formData[key] || 0}
|
||||||
|
label="Status"
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
[key]: e.target.value
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<MenuItem value={0}>Allied</MenuItem>
|
||||||
|
<MenuItem value={1}>Neutral</MenuItem>
|
||||||
|
<MenuItem value={2}>Enemy</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'Sex') {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth key={key} sx={{ mb: 2, ...commonStyles.select }}>
|
||||||
|
<InputLabel>Sex</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={formData[key] || ''}
|
||||||
|
label="Sex"
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
[key]: e.target.value
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<MenuItem value="Male">Male</MenuItem>
|
||||||
|
<MenuItem value="Female">Female</MenuItem>
|
||||||
|
<MenuItem value="Other">Other</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'Rarity') {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth key={key} sx={{ mb: 2, ...commonStyles.select }}>
|
||||||
|
<InputLabel>Rarity</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={formData[key] || 1}
|
||||||
|
label="Rarity"
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
[key]: e.target.value
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<MenuItem value={1}>Poor</MenuItem>
|
||||||
|
<MenuItem value={2}>Common</MenuItem>
|
||||||
|
<MenuItem value={3}>Uncommon</MenuItem>
|
||||||
|
<MenuItem value={4}>Rare</MenuItem>
|
||||||
|
<MenuItem value={5}>Epic</MenuItem>
|
||||||
|
<MenuItem value={6}>Legendary</MenuItem>
|
||||||
|
<MenuItem value={7}>Artifact</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'Type') {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth key={key} sx={{ mb: 2, ...commonStyles.select }}>
|
||||||
|
<InputLabel>Type</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={formData[key] || ''}
|
||||||
|
label="Type"
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
[key]: e.target.value
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<MenuItem value="Weapon">Weapon</MenuItem>
|
||||||
|
<MenuItem value="Armor">Armor</MenuItem>
|
||||||
|
<MenuItem value="Consumable">Consumable</MenuItem>
|
||||||
|
<MenuItem value="Quest">Quest Item</MenuItem>
|
||||||
|
<MenuItem value="Other">Other</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'Art') {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth key={key} sx={{ mb: 2, ...commonStyles.select }}>
|
||||||
|
<InputLabel>Art</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={formData[key] || ''}
|
||||||
|
label="Art"
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
[key]: e.target.value
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<MenuItem value="Sword">Sword</MenuItem>
|
||||||
|
<MenuItem value="Axe">Axe</MenuItem>
|
||||||
|
<MenuItem value="Bow">Bow</MenuItem>
|
||||||
|
<MenuItem value="Shield">Shield</MenuItem>
|
||||||
|
<MenuItem value="Staff">Staff</MenuItem>
|
||||||
|
<MenuItem value="Potion">Potion</MenuItem>
|
||||||
|
<MenuItem value="Other">Other</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'OwnerID') {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth key={key} sx={{ mb: 2, ...commonStyles.select }}>
|
||||||
|
<InputLabel sx={{ color: '#fff' }}>Owner</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={formData[key] || ''}
|
||||||
|
label="Owner"
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
[key]: e.target.value
|
||||||
|
})}
|
||||||
|
sx={{
|
||||||
|
color: '#fff',
|
||||||
|
'& .MuiOutlinedInput-notchedOutline': { borderColor: '#444' },
|
||||||
|
'&:hover .MuiOutlinedInput-notchedOutline': { borderColor: '#764ACB' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{allOwners.map((owner) => (
|
||||||
|
<MenuItem
|
||||||
|
key={owner.id}
|
||||||
|
value={owner.id || ''}
|
||||||
|
sx={{
|
||||||
|
...commonStyles.menuItem,
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>{owner.name}</span>
|
||||||
|
{owner.type !== 'None' && (
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
sx={{
|
||||||
|
ml: 2,
|
||||||
|
color: '#764ACB'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{owner.type}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TextField
|
||||||
|
key={key}
|
||||||
|
fullWidth
|
||||||
|
label={key}
|
||||||
|
value={formData[key] || ''}
|
||||||
|
onChange={(e) => setFormData({
|
||||||
|
...formData,
|
||||||
|
[key]: e.target.value
|
||||||
|
})}
|
||||||
|
sx={{ ...commonStyles.input, mb: 2 }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={editModalOpen}
|
||||||
|
onClose={handleModalClose}
|
||||||
|
sx={commonStyles.dialog}
|
||||||
|
>
|
||||||
|
<DialogTitle sx={{
|
||||||
|
borderBottom: '1px solid #444',
|
||||||
|
color: '#fff',
|
||||||
|
backgroundColor: '#2e2e3f',
|
||||||
|
padding: '16px 24px'
|
||||||
|
}}>
|
||||||
|
{editType === 'item' ? 'Edit Item' : editType === 'npc' ? 'Edit NPC' : 'Edit Character'}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent sx={{
|
||||||
|
p: 3,
|
||||||
|
backgroundColor: '#1e1e2f'
|
||||||
|
}}>
|
||||||
|
{formData && (
|
||||||
|
<Box sx={{ p: 2 }}>
|
||||||
|
{Object.keys(formData)
|
||||||
|
.filter(key => !nonEditableFields.includes(key))
|
||||||
|
.map(key => renderField(key))}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions sx={{
|
||||||
|
borderTop: '1px solid #444',
|
||||||
|
p: 2,
|
||||||
|
backgroundColor: '#2e2e3f',
|
||||||
|
'& .MuiButton-root': {
|
||||||
|
textTransform: 'none',
|
||||||
|
fontSize: '1rem',
|
||||||
|
padding: '6px 16px'
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<Button
|
||||||
|
onClick={handleModalClose}
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
color: '#fff',
|
||||||
|
borderColor: '#444',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: '#764ACB',
|
||||||
|
backgroundColor: 'rgba(118, 74, 203, 0.1)'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleUpdate}
|
||||||
|
variant="contained"
|
||||||
|
sx={{
|
||||||
|
backgroundColor: '#764ACB',
|
||||||
|
color: '#fff',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: '#9865f7'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Save Changes
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const Section = ({ title, items, createPath, createText }) => (
|
const Section = ({ title, items, createPath, createText }) => (
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
mb: 4,
|
mb: 4,
|
||||||
@@ -70,12 +466,21 @@ const GameMasterPage = () => {
|
|||||||
<Grid2 container spacing={2}>
|
<Grid2 container spacing={2}>
|
||||||
{items.map((item, index) => (
|
{items.map((item, index) => (
|
||||||
<Grid2 item xs={12} sm={6} md={3} key={index}>
|
<Grid2 item xs={12} sm={6} md={3} key={index}>
|
||||||
<Card sx={{
|
<Tooltip title={item.Abilities || 'No description available'} arrow>
|
||||||
|
<Card
|
||||||
|
onClick={() => handleItemClick(item)}
|
||||||
|
sx={{
|
||||||
backgroundColor: '#1e1e2f',
|
backgroundColor: '#1e1e2f',
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
height: '95%', // Reduced height
|
|
||||||
border: '1px solid #444',
|
border: '1px solid #444',
|
||||||
}}>
|
height: '95%',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'border-color 0.2s',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: '#764ACB'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
@@ -85,7 +490,7 @@ const GameMasterPage = () => {
|
|||||||
<CardMedia
|
<CardMedia
|
||||||
component="img"
|
component="img"
|
||||||
height="135" // Reduced from 140
|
height="135" // Reduced from 140
|
||||||
image={item.Img || defaultItemImage}
|
image={item.img || defaultItemImage} // Changed from item.img to item.Img
|
||||||
alt={item.ItemName}
|
alt={item.ItemName}
|
||||||
className={`rarity-${item.Rarity} rarity-image`}
|
className={`rarity-${item.Rarity} rarity-image`}
|
||||||
sx={{
|
sx={{
|
||||||
@@ -113,6 +518,7 @@ const GameMasterPage = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
</Tooltip>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
))}
|
))}
|
||||||
</Grid2>
|
</Grid2>
|
||||||
@@ -147,12 +553,20 @@ const GameMasterPage = () => {
|
|||||||
<Grid2 container spacing={2}>
|
<Grid2 container spacing={2}>
|
||||||
{playerCharacters.map((character, index) => (
|
{playerCharacters.map((character, index) => (
|
||||||
<Grid2 item xs={12} sm={6} md={3} key={index}>
|
<Grid2 item xs={12} sm={6} md={3} key={index}>
|
||||||
<Card sx={{
|
<Card
|
||||||
|
onClick={() => handleCharacterClick(character)}
|
||||||
|
sx={{
|
||||||
backgroundColor: '#1e1e2f',
|
backgroundColor: '#1e1e2f',
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
border: '1px solid #444',
|
border: '1px solid #444',
|
||||||
height: '100%'
|
height: '100%',
|
||||||
}}>
|
cursor: 'pointer',
|
||||||
|
transition: 'border-color 0.2s',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: '#764ACB'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
@@ -215,12 +629,20 @@ const GameMasterPage = () => {
|
|||||||
<Grid2 container spacing={2}>
|
<Grid2 container spacing={2}>
|
||||||
{npcs.map((npc, index) => (
|
{npcs.map((npc, index) => (
|
||||||
<Grid2 item xs={12} sm={6} md={3} key={index}>
|
<Grid2 item xs={12} sm={6} md={3} key={index}>
|
||||||
<Card sx={{
|
<Card
|
||||||
|
onClick={() => handleNpcClick(npc)}
|
||||||
|
sx={{
|
||||||
backgroundColor: '#1e1e2f',
|
backgroundColor: '#1e1e2f',
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
border: '1px solid #444',
|
border: '1px solid #444',
|
||||||
height: '100%'
|
height: '100%',
|
||||||
}}>
|
cursor: 'pointer',
|
||||||
|
transition: 'border-color 0.2s',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: '#764ACB'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
@@ -231,7 +653,7 @@ const GameMasterPage = () => {
|
|||||||
component="img"
|
component="img"
|
||||||
height="140"
|
height="140"
|
||||||
image={npc.Img || defaultCharacterImage}
|
image={npc.Img || defaultCharacterImage}
|
||||||
alt={npc.CharName}
|
alt={npc.Name || 'NPC'}
|
||||||
sx={{
|
sx={{
|
||||||
objectFit: 'contain',
|
objectFit: 'contain',
|
||||||
width: '140px',
|
width: '140px',
|
||||||
@@ -276,6 +698,7 @@ const GameMasterPage = () => {
|
|||||||
createPath="/create-item"
|
createPath="/create-item"
|
||||||
createText="Create Item"
|
createText="Create Item"
|
||||||
/>
|
/>
|
||||||
|
<EditModal />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user