added react frontend
This commit is contained in:
@@ -2,17 +2,16 @@ import { useState, useEffect } from 'react'
|
|||||||
import './App.css'
|
import './App.css'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const TextTile = ({ label }) => (
|
const TextTile = ({ label }) => (
|
||||||
<div className="tile-content">
|
<div className="tile-content">
|
||||||
<span className="text-purple-400 text-lg font-semibold">{label}</span>
|
<span className="text-purple-400 text-lg font-semibold">{label}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const ImageTile = ({ imageUrl, label, enabled, toggleEnabled }) => (
|
const ImageTile = ({ imageUrl, label, enabled, toggleEnabled }) => (
|
||||||
<div className="tile-content cursor-pointer" onClick={toggleEnabled}>
|
<div className="tile-content cursor-pointer" onClick={toggleEnabled}>
|
||||||
<img src={imageUrl} className="rounded-lg w-[6.3rem] h-[6.3rem] object-cover" alt={label} />
|
<img src={imageUrl} className="rounded-lg w-[6.3rem] h-[6.3rem] object-cover" alt={label} />
|
||||||
<span className="text-purple-400 text-lg font-semibold">{label}</span>
|
<span className="text-purple-400 text-lg font-semibold">{label}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -21,201 +20,207 @@ const ClockTile = ({ id }) => {
|
|||||||
const [date, setDate] = useState("");
|
const [date, setDate] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updateClock = () => {
|
const updateClock = () => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const time = now.toLocaleTimeString('ru-RU', { hour12: false });
|
const time = now.toLocaleTimeString('ru-RU', { hour12: false });
|
||||||
const options = { weekday: 'short', day: 'numeric', month: 'long', year: 'numeric' };
|
const options = { weekday: 'short', day: 'numeric', month: 'long', year: 'numeric' };
|
||||||
const date = now.toLocaleDateString('ru-RU', options)
|
const date = now.toLocaleDateString('ru-RU', options)
|
||||||
.replace(/,/, '')
|
.replace(/,/, '')
|
||||||
.replace(/(\d+)\s(\S+)/, '$1 $2')
|
.replace(/(\d+)\s(\S+)/, '$1 $2')
|
||||||
.replace(/г\./, '');
|
.replace(/г\./, '');
|
||||||
setTime(time);
|
setTime(time);
|
||||||
setDate(date.charAt(0).toUpperCase() + date.slice(1));
|
setDate(date.charAt(0).toUpperCase() + date.slice(1));
|
||||||
};
|
};
|
||||||
updateClock();
|
updateClock();
|
||||||
const interval = setInterval(updateClock, 1000);
|
const interval = setInterval(updateClock, 1000);
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tile-content">
|
<div className="tile-content">
|
||||||
<span className="text-5xl font-bold text-purple-200">{time}</span>
|
<span className="text-5xl font-bold text-purple-200">{time}</span>
|
||||||
<span className="text-purple-400 text-lg font-semibold">{date}</span>
|
<span className="text-purple-400 text-lg font-semibold">{date}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const GaugeTile = ({ gauges }) => (
|
const GaugeTile = ({ gauges }) => (
|
||||||
<div className="tile-content gauge-tile-content">
|
<div className="tile-content gauge-tile-content">
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
{gauges.map((g, index) => (
|
{gauges.map((g, index) => (
|
||||||
<div key={index} className="flex items-center gap-4">
|
<div key={index} className="flex items-center gap-4">
|
||||||
<div className="gauge" style={{ "--value": `${g.value}deg` }}>
|
<div className="gauge" style={{ "--value": `${g.value}deg` }}>
|
||||||
<span>{Math.round((g.value / 360) * 100)}%</span>
|
<span>{Math.round((g.value / 360) * 100)}%</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-purple-400 text-lg font-semibold">{g.label}</span>
|
<span className="text-purple-400 text-lg font-semibold">{g.label}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const SliderTile = ({ sliders, tileId, updateSliderValue }) => {
|
const SliderTile = ({ sliders, tileId, updateSliderValue }) => {
|
||||||
return (
|
return (
|
||||||
<div className="tile-content">
|
<div className="tile-content">
|
||||||
<span className="text-purple-400 text-lg font-semibold">Slider Controls</span>
|
<span className="text-purple-400 text-lg font-semibold">Slider Controls</span>
|
||||||
<div className="flex flex-col w-full gap-4">
|
<div className="flex flex-col w-full gap-4">
|
||||||
{sliders.map((s, index) => (
|
{sliders.map((s, index) => (
|
||||||
<div key={index} className="flex flex-col items-start">
|
<div key={index} className="flex flex-col items-start">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-purple-400 text-sm font-semibold">{s.label}</span>
|
<span className="text-purple-400 text-sm font-semibold">{s.label}</span>
|
||||||
<span className="text-purple-300 text-sm font-normal">{s.value}</span>
|
<span className="text-purple-300 text-sm font-normal">{s.value}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
min="0"
|
min="0"
|
||||||
max="100"
|
max="100"
|
||||||
value={s.value}
|
value={s.value}
|
||||||
onChange={(e) => updateSliderValue(tileId, index, e.target.value)}
|
onChange={(e) => updateSliderValue(tileId, index, e.target.value)}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const NumberTile = ({ value, label }) => (
|
const NumberTile = ({ value, label }) => (
|
||||||
<div className="tile-content">
|
<div className="tile-content">
|
||||||
<span className="text-5xl font-bold text-purple-200">{value}</span>
|
<span className="text-5xl font-bold text-purple-200">{value}</span>
|
||||||
<span className="text-purple-400 text-lg font-semibold">{label}</span>
|
<span className="text-purple-400 text-lg font-semibold">{label}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [tiles, setTiles] = useState([
|
const [tiles, setTiles] = useState([
|
||||||
{ id: 1, type: "text", label: "Tile 1" },
|
{ id: 1, type: "text", label: "Tile 1" },
|
||||||
{ id: 2, type: "gauge", gauges: [{ value: 75, label: "Metric 1" }, { value: 120, label: "Metric 2" }] },
|
{ id: 2, type: "gauge", gauges: [{ value: 75, label: "Metric 1" }, { value: 120, label: "Metric 2" }] },
|
||||||
{ id: 3, type: "image", imageUrl: "https://cloud.viadev.su/storage/branding_media/d076be24-e369-41ca-9b24-5f94efc5ecfd.png", label: "Image 1", enabled: true },
|
{ id: 3, type: "image", imageUrl: "https://cloud.viadev.su/storage/branding_media/d076be24-e369-41ca-9b24-5f94efc5ecfd.png", label: "Image 1", enabled: true },
|
||||||
{ id: 4, type: "text", label: "Tile 4" },
|
{ id: 4, type: "break" },
|
||||||
{ id: 5, type: "image", imageUrl: "https://cloud.viadev.su/storage/branding_media/d076be24-e369-41ca-9b24-5f94efc5ecfd.png", label: "Image 2", enabled: true },
|
{ id: 5, type: "text", label: "Tile 4" },
|
||||||
{ id: 6, type: "clock" },
|
{ id: 6, type: "image", imageUrl: "https://cloud.viadev.su/storage/branding_media/d076be24-e369-41ca-9b24-5f94efc5ecfd.png", label: "Image 2", enabled: true },
|
||||||
{ id: 7, type: "slider", sliders: [{ value: 50, label: "Slider 1" }, { value: 75, label: "Slider 2" }] },
|
{ id: 7, type: "clock" },
|
||||||
{ id: 8, type: "text", label: "Tile 5 (Double)" },
|
{ id: 8, type: "slider", sliders: [{ value: 50, label: "Slider 1" }, { value: 75, label: "Slider 2" }] },
|
||||||
{ id: 9, type: "text", label: "Tile 6" },
|
{ id: 9, type: "text", label: "Tile 5 (Double)" },
|
||||||
{ id: 10, type: "number", value: 42, label: "Data Point" }
|
{ id: 10, type: "text", label: "Tile 6" },
|
||||||
|
{ id: 11, type: "number", value: 42, label: "Data Point" }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const updateGaugeValue = (tileId, gaugeIndex) => {
|
const updateGaugeValue = (tileId, gaugeIndex) => {
|
||||||
setTiles(prevTiles =>
|
setTiles(prevTiles =>
|
||||||
prevTiles.map(tile => {
|
prevTiles.map(tile => {
|
||||||
if (tile.id === tileId && tile.type === "gauge") {
|
if (tile.id === tileId && tile.type === "gauge") {
|
||||||
const newGauges = [...tile.gauges];
|
const newGauges = [...tile.gauges];
|
||||||
newGauges[gaugeIndex] = {
|
newGauges[gaugeIndex] = {
|
||||||
...newGauges[gaugeIndex],
|
...newGauges[gaugeIndex],
|
||||||
value: (newGauges[gaugeIndex].value + 10) % 360
|
value: (newGauges[gaugeIndex].value + 10) % 360
|
||||||
};
|
};
|
||||||
return { ...tile, gauges: newGauges };
|
return { ...tile, gauges: newGauges };
|
||||||
}
|
}
|
||||||
return tile;
|
return tile;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateSliderValue = (tileId, sliderIndex, newValue) => {
|
const updateSliderValue = (tileId, sliderIndex, newValue) => {
|
||||||
setTiles(prevTiles =>
|
setTiles(prevTiles =>
|
||||||
prevTiles.map(tile => {
|
prevTiles.map(tile => {
|
||||||
if (tile.id === tileId && tile.type === "slider") {
|
if (tile.id === tileId && tile.type === "slider") {
|
||||||
const newSliders = [...tile.sliders];
|
const newSliders = [...tile.sliders];
|
||||||
newSliders[sliderIndex] = {
|
newSliders[sliderIndex] = {
|
||||||
...newSliders[sliderIndex],
|
...newSliders[sliderIndex],
|
||||||
value: parseInt(newValue)
|
value: parseInt(newValue)
|
||||||
};
|
};
|
||||||
return { ...tile, sliders: newSliders };
|
return { ...tile, sliders: newSliders };
|
||||||
}
|
}
|
||||||
return tile;
|
return tile;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateNumberValue = (tileId) => {
|
const updateNumberValue = (tileId) => {
|
||||||
setTiles(prevTiles =>
|
setTiles(prevTiles =>
|
||||||
prevTiles.map(tile => {
|
prevTiles.map(tile => {
|
||||||
if (tile.id === tileId && tile.type === "number") {
|
if (tile.id === tileId && tile.type === "number") {
|
||||||
return { ...tile, value: tile.value + 1 };
|
return { ...tile, value: tile.value + 1 };
|
||||||
}
|
}
|
||||||
return tile;
|
return tile;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleImageEnabled = (tileId) => {
|
const toggleImageEnabled = (tileId) => {
|
||||||
setTiles(prevTiles =>
|
setTiles(prevTiles =>
|
||||||
prevTiles.map(tile => {
|
prevTiles.map(tile => {
|
||||||
if (tile.id === tileId && tile.type === "image") {
|
if (tile.id === tileId && tile.type === "image") {
|
||||||
return { ...tile, enabled: !tile.enabled };
|
return { ...tile, enabled: !tile.enabled };
|
||||||
}
|
}
|
||||||
return tile;
|
return tile;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex flex-wrap gap-4">
|
<div className="flex flex-wrap gap-4">
|
||||||
{tiles.map(tile => (
|
{tiles.map(tile => (
|
||||||
<div
|
<div key={tile.id}>
|
||||||
key={tile.id}
|
{tile.type === "break" ? (
|
||||||
className={`glass rounded-lg ${tile.type === "gauge" || tile.type === "slider" || tile.type === "clock" ? "w-96 h-48" : "w-48 h-48"} relative ${tile.type === "image" && tile.enabled ? "glass_enabled" : ""}`}
|
<div className="w-full"></div>
|
||||||
>
|
) : (
|
||||||
{tile.type === "text" && <TextTile label={tile.label} />}
|
<div
|
||||||
{tile.type === "image" && (
|
className={`glass rounded-lg ${tile.type === "gauge" || tile.type === "slider" || tile.type === "clock" ? "w-96 h-48" : "w-48 h-48"} relative ${tile.type === "image" && tile.enabled ? "glass_enabled" : ""}`}
|
||||||
<ImageTile
|
>
|
||||||
imageUrl={tile.imageUrl}
|
{tile.type === "text" && <TextTile label={tile.label} />}
|
||||||
label={tile.label}
|
{tile.type === "image" && (
|
||||||
enabled={tile.enabled}
|
<ImageTile
|
||||||
toggleEnabled={() => toggleImageEnabled(tile.id)}
|
imageUrl={tile.imageUrl}
|
||||||
/>
|
label={tile.label}
|
||||||
)}
|
enabled={tile.enabled}
|
||||||
{tile.type === "clock" && <ClockTile id={tile.id} />}
|
toggleEnabled={() => toggleImageEnabled(tile.id)}
|
||||||
{tile.type === "gauge" && <GaugeTile gauges={tile.gauges} />}
|
/>
|
||||||
{tile.type === "slider" && <SliderTile sliders={tile.sliders} tileId={tile.id} updateSliderValue={updateSliderValue} />}
|
)}
|
||||||
{tile.type === "number" && <NumberTile value={tile.value} label={tile.label} />}
|
{tile.type === "clock" && <ClockTile id={tile.id} />}
|
||||||
{(tile.type === "gauge" || tile.type === "slider" || tile.type === "number") && (
|
{tile.type === "gauge" && <GaugeTile gauges={tile.gauges} />}
|
||||||
<div className="absolute top-2 right-2 flex gap-2">
|
{tile.type === "slider" && <SliderTile sliders={tile.sliders} tileId={tile.id} updateSliderValue={updateSliderValue} />}
|
||||||
{tile.type === "gauge" && tile.gauges.map((_, index) => (
|
{tile.type === "number" && <NumberTile value={tile.value} label={tile.label} />}
|
||||||
<button
|
{(tile.type === "gauge" || tile.type === "slider" || tile.type === "number") && (
|
||||||
key={index}
|
<div className="absolute top-2 right-2 flex gap-2">
|
||||||
onClick={() => updateGaugeValue(tile.id, index)}
|
{tile.type === "gauge" && tile.gauges.map((_, index) => (
|
||||||
className="bg-purple-600 text-white text-xs px-2 py-1 rounded"
|
<button
|
||||||
>
|
key={index}
|
||||||
Update Gauge {index + 1}
|
onClick={() => updateGaugeValue(tile.id, index)}
|
||||||
</button>
|
className="bg-purple-600 text-white text-xs px-2 py-1 rounded"
|
||||||
))}
|
>
|
||||||
{tile.type === "slider" && tile.sliders.map((_, index) => (
|
Update Gauge {index + 1}
|
||||||
<button
|
</button>
|
||||||
key={index}
|
))}
|
||||||
onClick={() => updateSliderValue(tile.id, index, (parseInt(tile.sliders[index].value) + 10) % 101)}
|
{tile.type === "slider" && tile.sliders.map((_, index) => (
|
||||||
className="bg-purple-600 text-white text-xs px-2 py-1 rounded"
|
<button
|
||||||
>
|
key={index}
|
||||||
Increment Slider {index + 1}
|
onClick={() => updateSliderValue(tile.id, index, (parseInt(tile.sliders[index].value) + 10) % 101)}
|
||||||
</button>
|
className="bg-purple-600 text-white text-xs px-2 py-1 rounded"
|
||||||
))}
|
>
|
||||||
{tile.type === "number" && (
|
Increment Slider {index + 1}
|
||||||
<button
|
</button>
|
||||||
onClick={() => updateNumberValue(tile.id)}
|
))}
|
||||||
className="bg-purple-600 text-white text-xs px-2 py-1 rounded"
|
{tile.type === "number" && (
|
||||||
>
|
<button
|
||||||
Increment Number
|
onClick={() => updateNumberValue(tile.id)}
|
||||||
</button>
|
className="bg-purple-600 text-white text-xs px-2 py-1 rounded"
|
||||||
)}
|
>
|
||||||
</div>
|
Increment Number
|
||||||
)}
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default App
|
export default App
|
||||||
|
|||||||
Reference in New Issue
Block a user