Login and Dashboard added

This commit is contained in:
Dylan Wright 2025-06-03 20:02:11 +10:00
parent 962d611b09
commit 058b7f8233
11 changed files with 289 additions and 7 deletions

8
.idea/do-tracker.iml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.13 virtualenv at ~/Projects/do-tracker/venv" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.13 virtualenv at ~/Projects/do-tracker/venv" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 virtualenv at ~/Projects/do-tracker/venv" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/do-tracker.iml" filepath="$PROJECT_DIR$/.idea/do-tracker.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

143
.idea/workspace.xml generated Normal file
View File

@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="5112d569-3a3f-4a44-9bd9-2c9295e7fe64" name="Changes" comment="Web front enabled basic flask setup">
<change afterPath="$PROJECT_DIR$/app/templates/login.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/__init__.py" beforeDir="false" afterPath="$PROJECT_DIR$/app/__init__.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/auth.py" beforeDir="false" afterPath="$PROJECT_DIR$/app/auth.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/app/models.py" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="HTML File" />
</list>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_BRANCH_BY_REPOSITORY">
<map>
<entry key="$PROJECT_DIR$" value="main" />
</map>
</option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="HighlightingSettingsPerFile">
<setting file="file://$PROJECT_DIR$/run.py" root0="FORCE_HIGHLIGHTING" />
</component>
<component name="ProjectColorInfo"><![CDATA[{
"associatedIndex": 2
}]]></component>
<component name="ProjectId" id="2xzZKYTQ3ONQyWMQvvsDEzRY2pu" />
<component name="ProjectLevelVcsManager">
<ConfirmationsSetting value="2" id="Add" />
</component>
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"DefaultHtmlFileTemplate": "HTML File",
"ModuleVcsDetector.initialDetectionPerformed": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.git.unshallow": "true",
"git-widget-placeholder": "main",
"settings.editor.selected.configurable": "preferences.lookFeel"
}
}]]></component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="5112d569-3a3f-4a44-9bd9-2c9295e7fe64" name="Changes" comment="" />
<created>1748942798494</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1748942798494</updated>
</task>
<task id="LOCAL-00001" summary="First Commit">
<option name="closed" value="true" />
<created>1748943061863</created>
<option name="number" value="00001" />
<option name="presentableId" value="LOCAL-00001" />
<option name="project" value="LOCAL" />
<updated>1748943061863</updated>
</task>
<task id="LOCAL-00002" summary="First Commit">
<option name="closed" value="true" />
<created>1748943110291</created>
<option name="number" value="00002" />
<option name="presentableId" value="LOCAL-00002" />
<option name="project" value="LOCAL" />
<updated>1748943110291</updated>
</task>
<task id="LOCAL-00003" summary="First Commit">
<option name="closed" value="true" />
<created>1748943342764</created>
<option name="number" value="00003" />
<option name="presentableId" value="LOCAL-00003" />
<option name="project" value="LOCAL" />
<updated>1748943342764</updated>
</task>
<task id="LOCAL-00004" summary="Web front enabled basic flask setup">
<option name="closed" value="true" />
<created>1748943934876</created>
<option name="number" value="00004" />
<option name="presentableId" value="LOCAL-00004" />
<option name="project" value="LOCAL" />
<updated>1748943934876</updated>
</task>
<option name="localTasksCounter" value="5" />
<servers />
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="RECENT_FILTERS">
<map>
<entry key="Branch">
<value>
<list>
<RecentGroup>
<option name="FILTER_VALUES">
<option value="origin/main" />
</option>
</RecentGroup>
</list>
</value>
</entry>
</map>
</option>
<option name="TAB_STATES">
<map>
<entry key="MAIN">
<value>
<State>
<option name="FILTERS">
<map>
<entry key="branch">
<value>
<list>
<option value="main" />
</list>
</value>
</entry>
</map>
</option>
</State>
</value>
</entry>
</map>
</option>
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="First Commit" />
<MESSAGE value="Web front enabled basic flask setup" />
<option name="LAST_COMMIT_MESSAGE" value="Web front enabled basic flask setup" />
</component>
</project>

View File

@ -1,20 +1,25 @@
from flask import Flask from flask import Flask
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager ##from flask_login import LoginManager
db = SQLAlchemy() db = SQLAlchemy()
login_manager = LoginManager() #login_manager = LoginManager()
def create_app(): def create_app():
app = Flask(__name__) app = Flask(__name__)
app.config['SECRET_KEY'] = 'devkey' # Change this for production app.config['SECRET_KEY'] = 'devkey' # Change this later
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///do_tracker.db' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///do_tracker.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app) db.init_app(app)
login_manager.init_app(app) # login_manager.init_app(app)
from .routes import main as main_blueprint from .routes import main as main_blueprint
app.register_blueprint(main_blueprint) app.register_blueprint(main_blueprint)
from .auth import auth as auth_blueprint
app.register_blueprint(auth_blueprint)
from . import models # <-- 🔥 this is what was missing
return app return app

View File

@ -0,0 +1,26 @@
from flask import Blueprint, render_template, redirect, request, session, url_for, flash
from .models import Store
from . import db
auth = Blueprint('auth', __name__)
@auth.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
store_id = request.form.get('store_id')
password = request.form.get('password')
store = Store.query.filter_by(id=store_id, password=password).first()
if store:
session['store_id'] = store.id
session['store_name'] = store.name
flash(f"Logged in as {store.name}", "success")
return redirect(url_for('main.store_dashboard'))
else:
flash("Invalid store ID or password", "danger")
return render_template('login.html')
@auth.route('/logout')
def logout():
session.clear()
flash("Logged out successfully", "info")
return redirect(url_for('auth.login'))

View File

@ -0,0 +1,28 @@
from datetime import datetime
from . import db
class Store(db.Model):
id = db.Column(db.Integer, primary_key=True) # Store number like 210
name = db.Column(db.String(100), nullable=False)
password = db.Column(db.String(128), nullable=False) # plaintext for now (hash later)
class DeliveryOrder(db.Model):
id = db.Column(db.Integer, primary_key=True)
do_number = db.Column(db.String(50), unique=True, nullable=False)
delivery_number = db.Column(db.String(50), nullable=False)
final_location = db.Column(db.Integer, db.ForeignKey('store.id'), nullable=False)
created_by = db.Column(db.String(100), nullable=False)
status = db.Column(db.String(50), default='Ready for Collection')
created_at = db.Column(db.DateTime, default=datetime.utcnow)
collected_by = db.Column(db.String(100), nullable=True)
movements = db.relationship('Movement', backref='delivery_order', cascade='all, delete-orphan')
class Movement(db.Model):
id = db.Column(db.Integer, primary_key=True)
do_id = db.Column(db.Integer, db.ForeignKey('delivery_order.id'), nullable=False)
branch_id = db.Column(db.Integer, db.ForeignKey('store.id'), nullable=False)
arrived_at = db.Column(db.DateTime, default=datetime.utcnow)
departed_at = db.Column(db.DateTime, nullable=True)
handled_by = db.Column(db.String(100), nullable=False)
comment = db.Column(db.Text, nullable=True)

View File

@ -1,7 +1,31 @@
from flask import Blueprint from flask import Blueprint, render_template, redirect, url_for, session, flash
from .models import DeliveryOrder
main = Blueprint('main', __name__) main = Blueprint('main', __name__)
@main.route("/")
@main.route('/')
def home(): def home():
return "<h1>DO Tracker Online</h1><p>Welcome to the system.</p>" return """
<h1>DO Tracker Online</h1>
<p><a href='/login'>Login as Store</a></p>
<p><a href='/store'>Store Dashboard</a></p>
"""
@main.route('/store')
def store_dashboard():
if 'store_id' not in session:
flash("You must be logged in to access the store dashboard.", "warning")
return redirect(url_for('auth.login'))
store_name = session.get('store_name')
return f"""
<h1>Welcome, {store_name}</h1>
<p><a href='/logout'>Logout</a></p>
<ul>
<li><a href='/store/do-entry'>Enter New DO</a> (coming next)</li>
<li><a href='/store/track'>Track DOs</a> (coming soon)</li>
</ul>
"""

21
app/templates/login.html Normal file
View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Store Login</title>
</head>
<body>
<h1>Store Login</h1>
<form method="POST">
<label>Store ID:</label><br>
<input type="text" name="store_id"><br>
<label>Password:</label><br>
<input type="password" name="password"><br><br>
<button type="submit">Login</button>
</form>
{% with messages = get_flashed_messages(with_categories=true) %}
{% for category, message in messages %}
<p style="color:red;">{{ message }}</p>
{% endfor %}
{% endwith %}
</body>
</html>