main page

This commit is contained in:
2025-12-17 17:55:13 +01:00
commit 7fb446c53a
8943 changed files with 1209030 additions and 0 deletions

47
src/pages/AssetsPage.jsx Normal file
View File

@@ -0,0 +1,47 @@
import { useState } from 'react'
import { FaServer, FaDesktop, FaPrint, FaNetworkWired } from 'react-icons/fa6'
export default function AssetsPage() {
const [searchTerm, setSearchTerm] = useState('')
// Placeholder data - would come from Appwrite in production
const assetCategories = [
{ icon: FaServer, name: 'Servers', count: 0 },
{ icon: FaDesktop, name: 'Workstations', count: 0 },
{ icon: FaPrint, name: 'Printers', count: 0 },
{ icon: FaNetworkWired, name: 'Network Devices', count: 0 }
]
return (
<div className="main-content">
<header className="text-center mb-2">
<h2>Assets Management</h2>
</header>
<div className="search-bar mb-2">
<input
type="text"
placeholder="Search assets..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="input"
style={{ width: '100%', maxWidth: '400px' }}
/>
</div>
<div className="assets-grid">
{assetCategories.map(({ icon: Icon, name, count }) => (
<div key={name} className="asset-card">
<Icon size={48} className="text-teal" />
<h4>{name}</h4>
<span className="asset-count">{count}</span>
</div>
))}
</div>
<div className="text-center mt-4 text-grey">
<p>Asset management module - Connect to Appwrite to manage your IT assets.</p>
</div>
</div>
)
}

View File

@@ -0,0 +1,73 @@
import { useState, useEffect } from 'react'
import { databases, DATABASE_ID, COLLECTIONS, Query } from '../lib/appwrite'
import { FaTicket, FaClipboardList, FaClock, FaCircleCheck } from 'react-icons/fa6'
export default function DashboardPage() {
const [stats, setStats] = useState({
totalTickets: 0,
openTickets: 0,
closedTickets: 0,
pendingTickets: 0
})
const [loading, setLoading] = useState(true)
useEffect(() => {
async function fetchStats() {
try {
const [total, open, closed, pending] = await Promise.all([
databases.listDocuments(DATABASE_ID, COLLECTIONS.WORKORDERS, [Query.limit(1)]),
databases.listDocuments(DATABASE_ID, COLLECTIONS.WORKORDERS, [Query.equal('status', 'Open'), Query.limit(1)]),
databases.listDocuments(DATABASE_ID, COLLECTIONS.WORKORDERS, [Query.equal('status', 'Closed'), Query.limit(1)]),
databases.listDocuments(DATABASE_ID, COLLECTIONS.WORKORDERS, [Query.equal('status', 'Awaiting'), Query.limit(1)])
])
setStats({
totalTickets: total.total,
openTickets: open.total,
closedTickets: closed.total,
pendingTickets: pending.total
})
} catch (err) {
console.error('Error fetching stats:', err)
} finally {
setLoading(false)
}
}
fetchStats()
}, [])
const StatCard = ({ icon: Icon, label, value, color }) => (
<div className="stat-card" style={{ borderLeft: `4px solid ${color}` }}>
<div className="stat-icon" style={{ color }}>
<Icon size={32} />
</div>
<div className="stat-info">
<span className="stat-value">{loading ? '...' : value}</span>
<span className="stat-label">{label}</span>
</div>
</div>
)
return (
<div className="main-content">
<header className="text-center mb-2">
<h2>Dashboard</h2>
</header>
<div className="stats-grid">
<StatCard icon={FaTicket} label="Total Tickets" value={stats.totalTickets} color="#2196F3" />
<StatCard icon={FaClipboardList} label="Open Tickets" value={stats.openTickets} color="#4CAF50" />
<StatCard icon={FaClock} label="Pending" value={stats.pendingTickets} color="#FF9800" />
<StatCard icon={FaCircleCheck} label="Closed" value={stats.closedTickets} color="#9E9E9E" />
</div>
<div className="dashboard-section mt-4">
<h3>Quick Actions</h3>
<div className="quick-actions">
<a href="/tickets" className="btn btn-teal">View All Tickets</a>
<a href="/reports" className="btn btn-dark">Generate Report</a>
</div>
</div>
</div>
)
}

66
src/pages/DocsPage.jsx Normal file
View File

@@ -0,0 +1,66 @@
import { FaBook, FaCircleQuestion, FaKeyboard } from 'react-icons/fa6'
export default function DocsPage() {
const sections = [
{
icon: FaBook,
title: 'Getting Started',
content: 'Learn how to create and manage work orders in WOMS 2.0.'
},
{
icon: FaCircleQuestion,
title: 'FAQ',
content: 'Frequently asked questions about the system.'
},
{
icon: FaKeyboard,
title: 'Keyboard Shortcuts',
content: 'Speed up your workflow with keyboard shortcuts.'
}
]
const shortcuts = [
{ key: 'N', action: 'New Ticket' },
{ key: 'F', action: 'Focus Search' },
{ key: 'R', action: 'Refresh List' },
{ key: 'Esc', action: 'Close Modal' }
]
return (
<div className="main-content">
<header className="text-center mb-2">
<h2>Documentation</h2>
</header>
<div className="docs-grid">
{sections.map(({ icon: Icon, title, content }) => (
<div key={title} className="doc-card">
<Icon size={32} className="text-teal" />
<h4>{title}</h4>
<p className="text-grey">{content}</p>
</div>
))}
</div>
<div className="shortcuts-section mt-4">
<h3 className="text-center">Keyboard Shortcuts</h3>
<table className="table" style={{ maxWidth: '400px', margin: '0 auto' }}>
<thead>
<tr>
<th>Key</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{shortcuts.map(({ key, action }) => (
<tr key={key}>
<td><kbd>{key}</kbd></td>
<td>{action}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)
}

85
src/pages/LoginPage.jsx Normal file
View File

@@ -0,0 +1,85 @@
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useAuth } from '../context/AuthContext'
export default function LoginPage() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)
const { login } = useAuth()
const navigate = useNavigate()
const handleSubmit = async (e) => {
e.preventDefault()
setError('')
setLoading(true)
const result = await login(email, password)
if (result.success) {
navigate('/tickets')
} else {
setError(result.error || 'Login failed')
}
setLoading(false)
}
return (
<div style={{
minHeight: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: '#f1f1f1'
}}>
<div className="card" style={{ width: '400px' }}>
<div className="card-header text-center">
<h2>NetWEB Systems WOMS 2.0</h2>
</div>
<div className="card-body">
<form onSubmit={handleSubmit}>
{error && (
<div className="bg-red text-white p-1 mb-2" style={{ borderRadius: '4px' }}>
{error}
</div>
)}
<div className="form-group">
<label className="form-label">Email</label>
<input
type="email"
className="form-control"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="form-group">
<label className="form-label">Password</label>
<input
type="password"
className="form-control"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button
type="submit"
className="btn btn-green"
style={{ width: '100%' }}
disabled={loading}
>
{loading ? 'Logging in...' : 'Login'}
</button>
</form>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,46 @@
import { useState } from 'react'
import { format, addDays, startOfWeek } from 'date-fns'
export default function PlanboardPage() {
const [currentWeek, setCurrentWeek] = useState(startOfWeek(new Date(), { weekStartsOn: 1 }))
const weekDays = Array.from({ length: 7 }, (_, i) => addDays(currentWeek, i))
const navigateWeek = (direction) => {
setCurrentWeek(prev => addDays(prev, direction * 7))
}
return (
<div className="main-content">
<header className="text-center mb-2">
<h2>Planboard</h2>
</header>
<div className="planboard-nav text-center mb-2">
<button className="btn btn-dark" onClick={() => navigateWeek(-1)}> Previous Week</button>
<span className="mx-2">
{format(currentWeek, 'dd.MM.yyyy')} - {format(addDays(currentWeek, 6), 'dd.MM.yyyy')}
</span>
<button className="btn btn-dark" onClick={() => navigateWeek(1)}>Next Week </button>
</div>
<div className="planboard-grid">
{weekDays.map(day => (
<div key={day.toISOString()} className="planboard-day">
<div className="day-header">
<strong>{format(day, 'EEEE')}</strong>
<span>{format(day, 'dd.MM')}</span>
</div>
<div className="day-content">
<p className="text-grey text-small">No tasks scheduled</p>
</div>
</div>
))}
</div>
<div className="text-center mt-4 text-grey">
<p>Drag and drop tickets to schedule them on the planboard.</p>
</div>
</div>
)
}

View File

@@ -0,0 +1,39 @@
import { useState } from 'react'
import { FaFolder, FaPlus } from 'react-icons/fa6'
export default function ProjectsPage() {
const [projects] = useState([])
return (
<div className="main-content">
<header className="text-center mb-2">
<h2>Projects</h2>
</header>
<div className="text-center mb-2">
<button className="btn btn-teal">
<FaPlus /> New Project
</button>
</div>
{projects.length === 0 ? (
<div className="text-center p-4">
<FaFolder size={64} className="text-grey" />
<p className="text-grey mt-2">No projects yet. Create your first project to get started.</p>
</div>
) : (
<div className="projects-grid">
{projects.map(project => (
<div key={project.$id} className="project-card">
<h4>{project.name}</h4>
<p className="text-grey">{project.description}</p>
<div className="project-meta">
<span>{project.ticketCount || 0} tickets</span>
</div>
</div>
))}
</div>
)}
</div>
)
}

82
src/pages/ReportsPage.jsx Normal file
View File

@@ -0,0 +1,82 @@
import { useState } from 'react'
import { format, subDays } from 'date-fns'
import { FaFileExport, FaChartBar } from 'react-icons/fa6'
export default function ReportsPage() {
const [dateRange, setDateRange] = useState({
from: format(subDays(new Date(), 30), 'yyyy-MM-dd'),
to: format(new Date(), 'yyyy-MM-dd')
})
const [reportType, setReportType] = useState('tickets')
const reportTypes = [
{ value: 'tickets', label: 'Ticket Summary' },
{ value: 'performance', label: 'Performance Report' },
{ value: 'customer', label: 'Customer Report' },
{ value: 'technician', label: 'Technician Report' }
]
const handleGenerateReport = () => {
// Would generate report from Appwrite data
alert(`Generating ${reportType} report from ${dateRange.from} to ${dateRange.to}`)
}
return (
<div className="main-content">
<header className="text-center mb-2">
<h2>Reports</h2>
</header>
<div className="report-filters card p-2">
<div className="filter-row">
<label>
Report Type:
<select
value={reportType}
onChange={(e) => setReportType(e.target.value)}
className="input ml-1"
>
{reportTypes.map(type => (
<option key={type.value} value={type.value}>{type.label}</option>
))}
</select>
</label>
</div>
<div className="filter-row mt-2">
<label>
From:
<input
type="date"
value={dateRange.from}
onChange={(e) => setDateRange(prev => ({ ...prev, from: e.target.value }))}
className="input ml-1"
/>
</label>
<label className="ml-2">
To:
<input
type="date"
value={dateRange.to}
onChange={(e) => setDateRange(prev => ({ ...prev, to: e.target.value }))}
className="input ml-1"
/>
</label>
</div>
<div className="text-center mt-2">
<button className="btn btn-teal" onClick={handleGenerateReport}>
<FaChartBar /> Generate Report
</button>
<button className="btn btn-dark ml-1">
<FaFileExport /> Export PDF
</button>
</div>
</div>
<div className="text-center mt-4 text-grey">
<p>Select report parameters and click Generate to view your report.</p>
</div>
</div>
)
}

216
src/pages/TicketsPage.jsx Normal file
View File

@@ -0,0 +1,216 @@
import { useState } from 'react'
import { format } from 'date-fns'
import { FaAngleDown, FaSpinner } from 'react-icons/fa6'
import { useWorkorders } from '../hooks/useWorkorders'
import TicketRow from '../components/TicketRow'
import TicketFilters from '../components/TicketFilters'
import CreateTicketModal from '../components/CreateTicketModal'
import QuickOverviewModal from '../components/QuickOverviewModal'
export default function TicketsPage() {
const [limit, setLimit] = useState(10)
const [filters, setFilters] = useState({
status: ['Open', 'Occupied', 'Assigned', 'Awaiting', 'Added Info'],
type: [],
priority: [],
limit: 10
})
const { workorders, loading, error, refresh, updateWorkorder, createWorkorder } = useWorkorders(filters)
const [showCreateModal, setShowCreateModal] = useState(false)
const [showOverviewModal, setShowOverviewModal] = useState(false)
const handleFilterChange = (newFilters) => {
setFilters({ ...newFilters, limit })
}
const handleApplyFilters = () => {
refresh()
}
const handleLimitChange = (e) => {
const newLimit = parseInt(e.target.value)
setLimit(newLimit)
setFilters(prev => ({ ...prev, limit: newLimit }))
}
const handleUpdate = async (id, data) => {
await updateWorkorder(id, data)
}
const handleCreate = async (data) => {
const result = await createWorkorder(data)
if (result.success) {
setShowCreateModal(false)
}
return result
}
const handleLoadMore = () => {
setLimit(prev => prev + 10)
setFilters(prev => ({ ...prev, limit: prev.limit + 10 }))
}
return (
<div className="main-content">
<header className="text-center mb-2">
<h2>Active Tickets Overview</h2>
</header>
<p className="text-center text-grey">
Work Order loading limit is set to <span className="text-xlarge">{limit}</span>.
Reduce value to increase reload speed.
</p>
<p className="text-center text-grey text-small">
Last page reload: {format(new Date(), 'dd.MM.yyyy, HH:mm:ss')}
</p>
<hr className="mb-2" />
<table className="table table-hover">
<tbody>
<TicketFilters
filters={filters}
onChange={handleFilterChange}
onApply={handleApplyFilters}
/>
<tr>
<td colSpan={10} className="bg-dark-grey">
<div className="slider-container text-center">
<input
type="range"
min="5"
max="50"
value={limit}
className="slider"
onChange={handleLimitChange}
/>
</div>
</td>
</tr>
<tr>
<td colSpan={10} className="text-center p-1">
<button
className="btn btn-teal"
onClick={() => setFilters(prev => ({ ...prev, type: ['Procurement'] }))}
>
Procurements
</button>
{' '}
<button
className="btn btn-teal"
onClick={() => setFilters(prev => ({ ...prev, priority: [4] }))}
>
Criticals
</button>
{' '}
<button
className="btn btn-teal"
onClick={() => setFilters(prev => ({ ...prev, priority: [3] }))}
>
Highs
</button>
{' '}
<button
className="btn btn-teal"
onClick={() => { setLimit(10); setFilters(prev => ({ ...prev, limit: 10 })) }}
>
10
</button>
{' '}
<button
className="btn btn-teal"
onClick={() => { setLimit(25); setFilters(prev => ({ ...prev, limit: 25 })) }}
>
25
</button>
</td>
</tr>
<tr>
<td colSpan={10} className="text-center p-1">
<button
className="btn btn-dark"
onClick={() => setShowCreateModal(true)}
>
CREATE NEW TICKET
</button>
{' '}
<button
className="btn btn-dark"
onClick={() => setShowOverviewModal(true)}
>
QUICK OVERVIEW
</button>
</td>
</tr>
<tr className="spacer">
<td colSpan={10} style={{ height: '16px' }}></td>
</tr>
{loading ? (
<tr>
<td colSpan={10} className="text-center p-2">
<FaSpinner className="spinner" size={32} />
<p>Loading...</p>
</td>
</tr>
) : error ? (
<tr>
<td colSpan={10} className="text-center p-2 text-red">
Error: {error}
</td>
</tr>
) : workorders.length === 0 ? (
<tr>
<td colSpan={10} className="text-center p-2">
No tickets found matching your filters.
</td>
</tr>
) : (
workorders.map(ticket => (
<TicketRow
key={ticket.$id}
ticket={ticket}
onUpdate={handleUpdate}
/>
))
)}
</tbody>
</table>
{workorders.length > 0 && workorders.length >= limit && (
<div className="text-center mt-2">
<button
className="btn"
style={{ background: 'none', border: 'none' }}
onClick={handleLoadMore}
>
<FaAngleDown size={48} />
</button>
</div>
)}
<p className="text-center text-grey mt-2">
Summary: Listed a total of {workorders.length} Workorders.
<br />EOL =)
</p>
<CreateTicketModal
isOpen={showCreateModal}
onClose={() => setShowCreateModal(false)}
onCreate={handleCreate}
/>
<QuickOverviewModal
isOpen={showOverviewModal}
onClose={() => setShowOverviewModal(false)}
workorders={workorders}
/>
</div>
)
}