From 21671d6beeb8e6337d7cd1d2777c0597aa074b2a Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 26 Jan 2026 11:35:29 -0600 Subject: [PATCH] v0.12.0 - Add modular system architecture with user-based module access - Add Modules and UserModules database tables - Create home page with module selection grid - Implement per-user module assignment in user management - Add route guards for module access control - Refactor navigation: login -> home -> modules, admin console via button - Add Font Awesome icons --- AI Prompt.txt | 60 ++++++++++++-- app.py | 41 ++++++---- .../__pycache__/counting.cpython-313.pyc | Bin 24204 -> 25255 bytes blueprints/__pycache__/users.cpython-313.pyc | Bin 8558 -> 10409 bytes blueprints/counting.py | 36 +++++++-- blueprints/users.py | 47 ++++++++++- database/init_db.py | 31 +++++++- static/css/style.css | 74 ++++++++++++++++++ templates/admin_dashboard.html | 7 +- templates/base.html | 3 +- templates/count_session.html | 2 +- templates/create_session.html | 2 +- templates/home.html | 42 ++++++++++ templates/manage_users.html | 52 ++++++++++-- templates/my_counts.html | 2 +- templates/session_detail.html | 4 +- templates/staff_dashboard.html | 9 ++- 17 files changed, 365 insertions(+), 47 deletions(-) create mode 100644 templates/home.html diff --git a/AI Prompt.txt b/AI Prompt.txt index e58f42d..96de210 100644 --- a/AI Prompt.txt +++ b/AI Prompt.txt @@ -29,6 +29,9 @@ Long-term goal: evolve into a WMS, but right now focus on making this workflow r 5) **Keep it to the point.** Default to short answers. Only explain more if I ask. 6) **Verify safety.** Warn me before destructive actions (delete/overwrite/migrations). Offer a safer alternative. 7) **Evidence-based debugging.** Ask for exact error text/logs and versions before guessing. +8) **CSS changes:** Ask which device(s) the change is for (desktop/mobile/scanner) before editing. Each has its own file. +9) **Database changes:** The app auto-initializes the database if it doesn't exist. Schema is in /database/init_db.py. +10) **Docker deployment:** Production runs in Docker on Linux (jisoo). Volume mounts only /app/database to preserve data between updates. ## How you should respond - Start by confirming which mode we’re working on: Cycle Count or Physical Inventory. @@ -37,10 +40,53 @@ Long-term goal: evolve into a WMS, but right now focus on making this workflow r - When writing SQL: be explicit about constraints/indexes that matter for lots/bins/sessions. - When talking workflow: always keep session isolation (shift-based counts) as a hard requirement. -## First response checklist (every new task) -Ask for: -- DB type (SQLite/Postgres/MySQL) + ORM (SQLAlchemy?) or raw SQL -- Current data model (tables or SQLAlchemy models) for: count_session, bin/location, expected_lines, scans -- How the Master Inventory list is formatted (CSV columns) -- What β€œFinalize BIN” should do exactly (lock? allow reopen? who can override?) -Then proceed one step at a time. +## Scanlook (current product summary) +Scanlook is a web app for warehouse counting workflows built with Flask + SQLite. + +**Current Version:** 0.11.3 + +**Tech Stack:** +- Backend: Python/Flask, raw SQL (no ORM) +- Database: SQLite (located in /database/scanlook.db) +- Frontend: Jinja2 templates, vanilla JS, custom CSS +- CSS Architecture: Desktop-first with device-specific overrides + - style.css (base/desktop) + - mobile.css (phones, 360-767px) + - scanner.css (MC9300 scanners, max-width 359px) +- Deployment: Docker container, Gitea for version control + container registry + +**Project Structure:** +- app.py (main Flask app, routes for auth + dashboard) +- /blueprints/ (modular routes: counting.py, sessions.py, users.py, data_imports.py, admin_locations.py) +- /templates/ (Jinja2 HTML templates) +- /static/css/ (style.css, mobile.css, scanner.css) +- /database/ (scanlook.db, init_db.py) +- db.py (database helper functions: query_db, execute_db) +- utils.py (decorators: login_required, role_required) + +**Key Features (implemented):** +- Count Sessions with archive/activate functionality +- Master baseline upload (CSV) +- Current baseline upload (optional, for comparison) +- Staff scanning interface optimized for MC9300 Zebra scanners +- Scan statuses: Match, Duplicate, Wrong Location, Ghost Lot, Weight Discrepancy +- Location/BIN workflow with Expected β†’ Scanned flow +- Session isolation (archived sessions blocked from access) +- Role-based access: owner, admin, staff +- Auto-initialize database on first run + +**Two count types:** +1. Cycle Count: shows Expected list for the BIN +2. Physical Inventory: blind count (no Expected list shown) + +**Long-term goal:** Modular WMS with future modules for Shipping, Receiving, Transfers, Production. + + + +## Quick Reference +- Database: SQLite at /database/scanlook.db +- Scanner viewport: 320px wide (MC9300) +- Mobile breakpoint: 360-767px +- Desktop: 768px+ +- Git remote: http://10.44.44.33:3000/stuff/ScanLook.git +- Docker registry: 10.44.44.33:3000/stuff/scanlook \ No newline at end of file diff --git a/app.py b/app.py index 26adfe7..c1a4839 100644 --- a/app.py +++ b/app.py @@ -36,7 +36,7 @@ app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=1) # 1. Define the version -APP_VERSION = '0.11.3' +APP_VERSION = '0.12.0' # 2. Inject it into all templates automatically @app.context_processor @@ -59,7 +59,7 @@ if not os.path.exists(db_path): def index(): """Landing page - redirect based on login status""" if 'user_id' in session: - return redirect(url_for('dashboard')) + return redirect(url_for('home')) return redirect(url_for('login')) @@ -79,7 +79,7 @@ def login(): session['full_name'] = user['full_name'] session['role'] = user['role'] flash(f'Welcome back, {user["full_name"]}!', 'success') - return redirect(url_for('dashboard')) + return redirect(url_for('home')) else: flash('Invalid username or password', 'danger') @@ -94,11 +94,30 @@ def logout(): return redirect(url_for('login')) +# ==================== ROUTES: HOME ==================== +@app.route('/home') +@login_required +def home(): + """Module selection landing page""" + user_id = session.get('user_id') + + # Get modules this user has access to + modules = query_db(''' + SELECT m.module_id, m.module_name, m.module_key, m.description, m.icon + FROM Modules m + JOIN UserModules um ON m.module_id = um.module_id + WHERE um.user_id = ? AND m.is_active = 1 + ORDER BY m.display_order + ''', [user_id]) + + return render_template('home.html', modules=modules) + + # ==================== ROUTES: DASHBOARD ==================== -@app.route('/dashboard') +@app.route('/admin') @login_required -def dashboard(): +def admin_dashboard(): """Main dashboard - different views for admin vs staff""" role = session.get('role') @@ -136,17 +155,7 @@ def dashboard(): ''') return render_template('admin_dashboard.html', sessions=sessions_list, show_archived=show_archived) - - else: - # Staff dashboard - active_sessions = query_db(''' - SELECT session_id, session_name, session_type, created_timestamp - FROM CountSessions - WHERE status = 'active' - ORDER BY created_timestamp DESC - ''') - - return render_template('staff_dashboard.html', sessions=active_sessions) + @app.route('/staff-mode') diff --git a/blueprints/__pycache__/counting.cpython-313.pyc b/blueprints/__pycache__/counting.cpython-313.pyc index 4f563455b5d172e68b87010091e3852ba8531242..a22ef0deed95e4a6ddd35fd20a9559c491fc0b33 100644 GIT binary patch delta 2978 zcmaJ@eN0=|6@T~He*WYmKp@~?UK(0(A$$eXuq_QGC6Ej-v4aW$#eLWh>;&8NzULGi zEwHUs+kC9aZmXtSwRIy=qfTlQJynZVZPmI(`=fu1mTH@xqA8K4X^k`uly0i>$F_6c zvkj5jT6({C&OIOZoO91T&*y$k-nvPO?-dofIQR`-el{`u%*Enuy)es{KczY(CAOvf^_ zvN$WHVu{p@xJy*x>1RbLswPg#q9Q9wBArrndq$CIBoWKL;}UbfaNl6x{v%?$cpwxU z5=TKkWVlkq%wn>jbtrgnSj-EOVsKc@BwNfq!JW8Q+{6`(o*L*2^`T;JS5Vt0J~8~{ zhVlt{0TvsKB16~>IcY+PVC0Js`d+c!@OOlkLH^l?$I}@xmKIZKRg6m@m!eTfPE^yP z8c!&>GU!4~O3lbrx5m>+IeWbjQ7+kBJs|Std~G5rDJgGo>cX7dTq8!QEU9uVq9&5E zqDslRyeD?YR^FXwL^DxPP%{eVW0t+4APq62DkToE#C`7KN-AGIapuG&|7vmlQvasWJe?y?uF zFIm@})-vDHq5pU|PvtE~(MoaP+We=Eo?9hVZ`j`yeq6Fz(zxPi{9C1OjkAf4dz`Jx zxfJ}onDcm7yzMuFAG-%vtb^1Gn`Nt7quRf%hsjpz1NMG#z5!Z{(Ac;Ta`{2f0FN@v z-V@q9D`MN~w)WMMjul6TwkG_M2xv(k(QZ0EAU2}}+IiO{Lj3Fz&ydiJ!d+~^Q|oF0 zvI`yEU~hVwJ2yMJZFj#o@ZHmwYF6z#jf`5ZIX`uL!^mi}p7vkQWsA^cY{;&bN&*H( z8(GPgUBsvD-69iG%a+ULZFC0+m{>ka0_Y_7CTy>>?8Kt@ZL|trx??sSl~kA!x=o2nDTVf; z(HI-3Yunn5P2XoZ!*8R^}WrTYWG<4T& zT8*SK$tjuAt{*-+L;AJ&qn)G-2f6OT7*hGf-)WkHa&BopLVRqx`L_XVS9%=b1b~9W zG}qcRg{x_RCipbbIpm%J$iCuY>#gN1z3W#~Mv2Q(vK&()(R6A$L6b(}^bjcOrLoN1 zYywIlN6hO?=h4XmfZs~dmSz#Yd=*{LQ`*^kY$?zoVE@o>vKxV(gN3r|)|jM9^cadF2u~wO2zVfRu{@tpR2ZKT^XN{Y zSJ3tbkbl>#kNwCl;;B$-#DMBe6rw-Sh2MBRwldKz{}n4~`snOrDjorYOD);!m{a zdw9O>r?>&`?B~tmE+cI*hn*8>$UPBNL23lO%zoMXknJk0-`D=ydzK$CDu;b+6h^m4 z)5)ZqQt3}%$^2p*poKlYGCMlOuJ`@<;92xz9sp8q1dPEL!t@+c=Mg*re!-0BA}$zF z7$@^KvdeHcb_l-!(#dOI9O$+X$kzo*?A=^zhX z0BzkV!v&TpTo(ERbn!BPztk8g^cZUkRl2bg%z?4Y4u{HXlE^cJR}ePbzRFIA+FU=! zMT8dZM(6|Uj>5R0{jg{rC>=nh!vNXWU98zx%`6=i?B-LmFe5s~PD6WMAN!r#IFj$6 zW-VJBuPZYebrktV0PHq)Z9K5EaL3Hi)(bM-I13y;FrgHhWR9c#w)a8ss&?e)C?R$1 z?FkyGM^}CX{Jc;c?i9zeF#;)GE2GWnRiv;LjI=2FF-MmdscHb-@h$Uz)Z_F+U0`;rLPn-e@-y;(oOj>= delta 2008 zcmah~Yiv_>6yLvVZ`<2$l=0|Tx3zP{QQ3>igiWHt#wOsvtlNUH(cQI|Qe0a+_ZHZE zVB!N2M9E1|!GRJM;uk+~eZ{Cq)DR=_5uzqHn(#qCNJ0!66T=7MIk(*giH|1t*Yp0L z|2gNLd**X-=p#}7M_HL%LcgQm?oNM@IZ|FJjvZ;Z>Wz6?M9kZ68!C^LVMr`>mKP-0 zC!(%4!5$?gcu2ImrY-R^vCLt4;dODR$3Np=i7RbOor{Y8J8e~REKpSf)kD6%#rR0Y zyv5biA?UE0s*<)s@R@(8N!3Pz#tmDe&G^yPFXow2Qpu)NFx^Q-+cBC`Ac~@Z0CkW9G77<~(Gvo0@E}zvkXdu3tE#AcgzQ&tR!t=iBn|b3`-|14R zo@}8OalRr2nGE6e`Awq6Sh7GBB8-O@?sb@@DOE?0e?T;&;*Urx*e;4M`5WYWiCj;Q z)`jQ&;aj3#^M7IsEF+!i%&3~Cq*PP3a#WM?(Qydl#i}bJith!MIC{xyxAAjeWod9J zkx~{8Y^naJfUPYSjAEL*un`_IM+1~H`Xs)5M^m4LqEd+!zvuF56go@zCe$b#547` zToteRBWJ_kyZu)3on&LmV>vyZ&1XhcfEK*kloG3rf#&6+qS?}N2HEL49B&z=!ro}< z7j}%bo^Kf?6G$?s1e%SY*k{{69g=ZdVVCN&y_?DjNdtWrQ1 zwni65fh{$Hh#lAzbcTPHcCjjN?2TTqOIfDl#@ZEA!a4#TCxKa(Q8b+HOQmA zJs}l>VWvGQEoZMboNB*s-Ap-6ds5LA*vzVj7`8BMW#B@acsp z)!`Kq^+N?=H_V*zuXlnWJhASFbDyfWUt^5l^(_zg z&}0{9n7zgrG7Q$#<(S&Vk^c9Ec#}*YoDF8Y9g~G;HqFP3FZ!cm-}7W;x>b59RZwVr zpW_1#5JY^|hOlPS^d<5D)Q9-=x`5+sXYGMWjv&&eybILb#W_u}me&FtW|%$oMK*SD zN5@S!IpD-#o%|ZxFw8fm2G7|8GYbP&ld#ZsSi?peaNpLGv=)LxPtdTQ82Z#>wQ3LR z!Z$?B| diff --git a/blueprints/__pycache__/users.cpython-313.pyc b/blueprints/__pycache__/users.cpython-313.pyc index 0de51c9ea2e5e12140b9473cc383034d764baba3..1718cebc8e837f3b6376cc73d00f736d5db9266e 100644 GIT binary patch delta 2593 zcmb7GO>7fK6rR~#$A5_($9C+*kW9!ASsW4wq$DB#*a-Jho-yHttjaxd&#je zpMQ)DoFb8!a{g@i;^d4RiOT9c@9=VRG2LNBe@>e&^0J4kJ)#@GNyR=Ty*k$SWYO<98rM;6V_zyh||%8cEmn9;!{Vge+y2ksgFEl@r}|3 zo%bdBH7>JL%Ll0bucJa=JBUv8D`yQpsNQ$hYcXbLea3kLmL7FS`8T+6@z?UJI4MF8 zOD;I4<$4!rUJ#I28Bi}bf!IxjXw_oV3>|Rb7l@LU?-np-S=kF(D;0{@N~IQ(g4AMT zo;@E4C!$(-2B_6*i|rL}3jZr2_qJc|+;E)N9A`eW&7r{PuDSgp(X-L~em*+#rTTf* zV{Wcq&3dYmxoS9*t3x`B5L6;7*Vn1?F$xvNBpgI7xD60TTO0?m9Zhfw*Fx*Yjt4Amy~O%w=1SXpF)9O zK#eG9ku3uod`=#sZw0sloY10Mnn(JiwuB-N)A8@X&`(7QJuy(;$Rf^dcwn+!s#w=? zR!3@R&49eE8C&b%7dsb@e{kfZ=4(4sBWYXfs=4e+;(F(@y(eStO$)t$KIT!`KDOtF zmOa9jGy#Sou`kt_xr|D3M&Y8q5sE{Hq1SH3r#6j{T8vYJGPSXGk0xd$RB?L!i zP0~q4l71sWb*Ku3%!N_IW&jWrgtWUU6od_%q+~oPgn(;VlT3hz7C!G1K>=F8fQ(I> z@{)P$rZp%8jK`WDg=^>*x=r@F5Rf%%!NMJqTOq)@6|Ndd#c^G~t}geU!c(Z5uSQc? zESakbvLgHssIg?vdDYv7Is@A>6|scxJEFMQPC(v>}3%I8*qb2iZ(GIU|plZLC_el0F!DCY2deSqOt=1k5Hp~^igh8!SG*f|IzHZYxZQ! z$?YlMPd4Xe_q_W`(^W2G^QK1V#air0JNlP~mO9d76U*ikDc^mwb=6wF(3WoVFS`aa z)}gdvC@YlcdLA0e()OMu*OKv`VPw4sRn}b^UM()qBHmq^EkgFn&!(@&mK)mBwvONR ze96Hzo|-%?&jP#78jfF|!vyNHnbE?`CIqJ@ea{m8)OQhlin1(1UAp z!m*Os$Ki2#U0R)9m(*rLPx7w37W86N!Qb!(FDpw4+KN^8E_>>}ik$c8e*R2(bTZa9 zY@^8BRwUSiXLSYq_<<3EN{AsG3BlLYbHOn*0_2(HKh#qAla}6?EWc#$ zwAzMR8!UR?czxp-hBS|9W48-krUnnSRpmhe%}YDsWh^CU zcY3+ooxW|t6kQef!(`^^jTYl2luuKp& zWyv*SISu1h&$`F-094Q;!#LGC>efnEs|a0&{rr7IU!)^&2C)^ijW%m^4d^*AN#4B# z%H#MDIkCXi*L=dj(-*n|`{jO>B-06Y^JjDhl(s$MWlKghxlj@&J z6}M7Rq{vNEj#+o}nXJWr<-3597yN1Vo3f-Qc;ld5p_vr}2hO=^') @login_required def count_session(session_id): @@ -19,7 +45,7 @@ def count_session(session_id): if not sess: flash('Session not found or not active', 'danger') - return redirect(url_for('dashboard')) + return redirect(url_for('counting.index')) # Redirect to my_counts page (staff can manage multiple bins) return redirect(url_for('counting.my_counts', session_id=session_id)) @@ -33,11 +59,11 @@ def my_counts(session_id): if not sess: flash('Session not found', 'danger') - return redirect(url_for('dashboard')) + return redirect(url_for('counting.index')) if sess['status'] == 'archived': flash('This session has been archived', 'warning') - return redirect(url_for('dashboard')) + return redirect(url_for('counting.index')) # Get this user's active bins active_bins = query_db(''' @@ -78,7 +104,7 @@ def start_bin_count(session_id): sess = get_active_session(session_id) if not sess: flash('Session not found or archived', 'warning') - return redirect(url_for('dashboard')) + return redirect(url_for('counting.index')) if not sess['master_baseline_timestamp']: flash('Master File not uploaded. Please upload it before starting bins.', 'warning') return redirect(url_for('counting.my_counts', session_id=session_id)) @@ -146,7 +172,7 @@ def count_location(session_id, location_count_id): sess = get_active_session(session_id) if not sess: flash('Session not found or archived', 'warning') - return redirect(url_for('dashboard')) + return redirect(url_for('counting.index')) if not sess['master_baseline_timestamp']: flash('Master File not uploaded. Please upload it before starting bins.', 'warning') return redirect(url_for('counting.my_counts', session_id=session_id)) diff --git a/blueprints/users.py b/blueprints/users.py index 47c1acd..6176365 100644 --- a/blueprints/users.py +++ b/blueprints/users.py @@ -17,7 +17,10 @@ def manage_users(): # Admins can only see staff users = query_db("SELECT * FROM Users WHERE role = 'staff' ORDER BY full_name") - return render_template('manage_users.html', users=users) + # Get all active modules + modules = query_db('SELECT * FROM Modules WHERE is_active = 1 ORDER BY display_order') + + return render_template('manage_users.html', users=users, modules=modules) @users_bp.route('/settings/users/add', methods=['POST']) @@ -191,4 +194,44 @@ def delete_user(user_id): execute_db('UPDATE Users SET is_active = 0 WHERE user_id = ?', [user_id]) return jsonify({'success': True, 'message': 'User deleted successfully'}) except Exception as e: - return jsonify({'success': False, 'message': f'Error deleting user: {str(e)}'}) \ No newline at end of file + return jsonify({'success': False, 'message': f'Error deleting user: {str(e)}'}) + + +@users_bp.route('/settings/users//modules', methods=['GET']) +@role_required('owner', 'admin') +def get_user_modules(user_id): + """Get modules assigned to a user""" + modules = query_db(''' + SELECT module_id FROM UserModules WHERE user_id = ? + ''', [user_id]) + + module_ids = [m['module_id'] for m in modules] + return jsonify({'success': True, 'module_ids': module_ids}) + + +@users_bp.route('/settings/users//modules', methods=['POST']) +@role_required('owner', 'admin') +def update_user_modules(user_id): + """Update modules assigned to a user""" + data = request.get_json() + module_ids = data.get('module_ids', []) + + # Verify user exists + user = query_db('SELECT user_id FROM Users WHERE user_id = ?', [user_id], one=True) + if not user: + return jsonify({'success': False, 'message': 'User not found'}) + + try: + # Remove all current assignments + execute_db('DELETE FROM UserModules WHERE user_id = ?', [user_id]) + + # Add new assignments + for module_id in module_ids: + execute_db(''' + INSERT INTO UserModules (user_id, module_id, granted_by) + VALUES (?, ?, ?) + ''', [user_id, module_id, session['user_id']]) + + return jsonify({'success': True, 'message': 'Modules updated'}) + except Exception as e: + return jsonify({'success': False, 'message': str(e)}) \ No newline at end of file diff --git a/database/init_db.py b/database/init_db.py index 5784954..8409b28 100644 --- a/database/init_db.py +++ b/database/init_db.py @@ -31,7 +31,36 @@ def init_database(): created_timestamp DATETIME DEFAULT CURRENT_TIMESTAMP ) ''') - + + # Modules Table - defines available system modules + cursor.execute(''' + CREATE TABLE IF NOT EXISTS Modules ( + module_id INTEGER PRIMARY KEY AUTOINCREMENT, + module_name TEXT UNIQUE NOT NULL, + module_key TEXT UNIQUE NOT NULL, + description TEXT, + icon TEXT, + is_active INTEGER DEFAULT 1, + display_order INTEGER DEFAULT 0 + ) + ''') + + # UserModules Table - which modules each user can access + cursor.execute(''' + CREATE TABLE IF NOT EXISTS UserModules ( + user_module_id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + module_id INTEGER NOT NULL, + granted_by INTEGER, + granted_timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES Users(user_id), + FOREIGN KEY (module_id) REFERENCES Modules(module_id), + FOREIGN KEY (granted_by) REFERENCES Users(user_id), + UNIQUE(user_id, module_id) + ) + ''') + + # CountSessions Table # NOTE: current_baseline_version removed - CURRENT is now global cursor.execute(''' diff --git a/static/css/style.css b/static/css/style.css index 945a233..a7319d6 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -2151,4 +2151,78 @@ body { .session-actions-header { flex-shrink: 0; +} + +/* ==================== MODULE GRID (Home Page) ==================== */ + +.module-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: var(--space-xl); + margin-top: var(--space-xl); +} + +.module-card { + background: var(--color-surface); + border: 2px solid var(--color-border); + border-radius: var(--radius-xl); + padding: var(--space-2xl) var(--space-xl); + text-decoration: none; + text-align: center; + transition: var(--transition); + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-md); +} + +.module-card:hover { + border-color: var(--color-primary); + transform: translateY(-4px); + box-shadow: var(--shadow-glow), var(--shadow-lg); +} + +.module-icon { + width: 80px; + height: 80px; + background: var(--color-primary-glow); + border: 2px solid var(--color-primary); + border-radius: var(--radius-lg); + display: flex; + align-items: center; + justify-content: center; + font-size: 2.5rem; + color: var(--color-primary); + transition: var(--transition); +} + +.module-card:hover .module-icon { + background: var(--color-primary); + color: var(--color-bg); + box-shadow: 0 0 30px var(--color-primary-glow); +} + +.module-name { + font-size: 1.5rem; + font-weight: 700; + color: var(--color-text); + margin: 0; +} + +.module-desc { + font-size: 0.9rem; + color: var(--color-text-muted); + margin: 0; + line-height: 1.5; +} +/* ==================== MODAL SCROLL FIX ==================== */ + +.modal { + overflow-y: auto; + padding: var(--space-xl) 0; +} + +.modal-content { + max-height: none; + margin: auto; } \ No newline at end of file diff --git a/templates/admin_dashboard.html b/templates/admin_dashboard.html index cb3fcaf..9768058 100644 --- a/templates/admin_dashboard.html +++ b/templates/admin_dashboard.html @@ -6,7 +6,10 @@
-
diff --git a/templates/home.html b/templates/home.html new file mode 100644 index 0000000..37396c5 --- /dev/null +++ b/templates/home.html @@ -0,0 +1,42 @@ +{% extends "base.html" %} + +{% block title %}Home - ScanLook{% endblock %} + +{% block content %} +
+ + + {% if session.role in ['owner', 'admin'] %} + + {% endif %} + +
+

Welcome, {{ session.full_name }}

+

Select a module to get started

+
+ + {% if modules %} +
+ {% for m in modules %} + +
+ +
+

{{ m.module_name }}

+

{{ m.description }}

+
+ {% endfor %} +
+ {% else %} +
+
πŸ”’
+

No Modules Available

+

You don't have access to any modules. Please contact your administrator.

+
+ {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/templates/manage_users.html b/templates/manage_users.html index e288e57..25f2ff7 100644 --- a/templates/manage_users.html +++ b/templates/manage_users.html @@ -129,6 +129,18 @@
+
+ +
+ {% for module in modules %} + + {% endfor %} +
+
+