From 4be0e4844227f69d9b7ccf9c3a626a8f13f7e6e3 Mon Sep 17 00:00:00 2001 From: Markus Zimmerberger Date: Tue, 29 Jul 2025 14:47:03 +0200 Subject: [PATCH] Add frontend, member_state_log, etc. --- migrations/member_state_migration.sql | 117 ++ migrations/member_state_migration_v2.sql | 252 +++ migrations/member_state_migration_v4.sql | 255 +++ migrations/sqlalchemy_models.py | 197 ++ package-lock.json | 1061 ++++++++++ package.json | 10 + sustApp/main.py | 62 +- sustApp/models/models.py | 50 +- sustApp/react-frontend/index.html | 14 + sustApp/react-frontend/package-lock.json | 1817 +++++++++++++++++ sustApp/react-frontend/package.json | 30 + .../react-frontend/src/FastAPIFrontend.tsx | 748 +++++++ .../src/FastAPIFrontend_old.tsx | 458 +++++ .../react-frontend/src/FastAPIFrontend_v1.tsx | 233 +++ .../react-frontend/src/FastAPIFrontend_v7.tsx | 515 +++++ sustApp/react-frontend/src/index.css | 3 + sustApp/react-frontend/src/index.tsx | 12 + sustApp/react-frontend/tsconfig.json | 20 + sustApp/react-frontend/tsconfig_old.json | 113 + 19 files changed, 5962 insertions(+), 5 deletions(-) create mode 100644 migrations/member_state_migration.sql create mode 100644 migrations/member_state_migration_v2.sql create mode 100644 migrations/member_state_migration_v4.sql create mode 100644 migrations/sqlalchemy_models.py create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 sustApp/react-frontend/index.html create mode 100644 sustApp/react-frontend/package-lock.json create mode 100644 sustApp/react-frontend/package.json create mode 100644 sustApp/react-frontend/src/FastAPIFrontend.tsx create mode 100644 sustApp/react-frontend/src/FastAPIFrontend_old.tsx create mode 100644 sustApp/react-frontend/src/FastAPIFrontend_v1.tsx create mode 100644 sustApp/react-frontend/src/FastAPIFrontend_v7.tsx create mode 100644 sustApp/react-frontend/src/index.css create mode 100644 sustApp/react-frontend/src/index.tsx create mode 100644 sustApp/react-frontend/tsconfig.json create mode 100644 sustApp/react-frontend/tsconfig_old.json diff --git a/migrations/member_state_migration.sql b/migrations/member_state_migration.sql new file mode 100644 index 0000000..61310d5 --- /dev/null +++ b/migrations/member_state_migration.sql @@ -0,0 +1,117 @@ +-- Step 1: Create the member_states lookup table +CREATE TABLE member_states ( + state_code CHAR(1) PRIMARY KEY, + state_name VARCHAR(20) NOT NULL, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Insert the three states +INSERT INTO member_states (state_code, state_name, description) VALUES +('A', 'Active', 'Member is currently active'), +('I', 'Inactive', 'Member is temporarily inactive'), +('D', 'Deactivated', 'Member has been permanently deactivated'); + +-- Step 2: Create the member_state_log table +CREATE TABLE member_state_log ( + log_id SERIAL PRIMARY KEY, + member_id INTEGER NOT NULL, + state_code CHAR(1) NOT NULL, + changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + changed_by INTEGER, -- Optional: user ID who made the change + reason TEXT, -- Optional: reason for state change + FOREIGN KEY (member_id) REFERENCES members(member_id), + FOREIGN KEY (state_code) REFERENCES member_states(state_code) +); + +-- Create indexes for better performance +CREATE INDEX idx_member_state_log_member_id ON member_state_log(member_id); +CREATE INDEX idx_member_state_log_changed_at ON member_state_log(changed_at); +CREATE INDEX idx_member_state_log_state_code ON member_state_log(state_code); + +-- Step 3: Migration script to populate the log table from existing data +INSERT INTO member_state_log (member_id, state_code, changed_at) +SELECT + member_id, + CASE + WHEN member_active = true THEN 'A' + ELSE 'I' + END, + COALESCE(updated_at, created_at, CURRENT_TIMESTAMP) +FROM members; + +-- Step 4: Create a view to get current member states (most recent log entry per member) +CREATE OR REPLACE VIEW member_current_state AS +SELECT DISTINCT ON (msl.member_id) + msl.member_id, + msl.state_code, + ms.state_name, + ms.description, + msl.changed_at, + msl.changed_by, + msl.reason +FROM member_state_log msl +JOIN member_states ms ON msl.state_code = ms.state_code +ORDER BY msl.member_id, msl.changed_at DESC, msl.log_id DESC; + +-- Step 5: Function to change member state (ensures proper logging) +CREATE OR REPLACE FUNCTION change_member_state( + p_member_id INTEGER, + p_new_state_code CHAR(1), + p_changed_by INTEGER DEFAULT NULL, + p_reason TEXT DEFAULT NULL +) RETURNS VOID AS $$ +BEGIN + -- Validate that the state exists + IF NOT EXISTS (SELECT 1 FROM member_states WHERE state_code = p_new_state_code) THEN + RAISE EXCEPTION 'Invalid state code: %', p_new_state_code; + END IF; + + -- Validate that the member exists + IF NOT EXISTS (SELECT 1 FROM members WHERE member_id = p_member_id) THEN + RAISE EXCEPTION 'Member not found: %', p_member_id; + END IF; + + -- Insert the new state log entry + INSERT INTO member_state_log (member_id, state_code, changed_by, reason) + VALUES (p_member_id, p_new_state_code, p_changed_by, p_reason); +END; +$$ LANGUAGE plpgsql; + +-- Step 6: Common queries you'll need + +-- Get all active members +SELECT m.*, mcs.state_name, mcs.changed_at as last_state_change +FROM members m +JOIN member_current_state mcs ON m.member_id = mcs.member_id +WHERE mcs.state_code = 'A'; + +-- Get member state history +SELECT + msl.changed_at, + ms.state_name, + msl.reason, + msl.changed_by +FROM member_state_log msl +JOIN member_states ms ON msl.state_code = ms.state_code +WHERE msl.member_id = 123 -- Replace with actual member_id +ORDER BY msl.changed_at DESC; + +-- Count members by state +SELECT + ms.state_name, + COUNT(*) as member_count +FROM member_current_state mcs +JOIN member_states ms ON mcs.state_code = ms.state_code +GROUP BY ms.state_code, ms.state_name +ORDER BY ms.state_code; + +-- Step 7: Example usage of the change_member_state function +-- Change member 123 to inactive +SELECT change_member_state(123, 'I', 1, 'Member requested temporary suspension'); + +-- Reactivate member 123 +SELECT change_member_state(123, 'A', 1, 'Member reactivation approved'); + +-- Step 8: After testing, remove the old column (CAREFUL!) +-- ALTER TABLE members DROP COLUMN member_active; \ No newline at end of file diff --git a/migrations/member_state_migration_v2.sql b/migrations/member_state_migration_v2.sql new file mode 100644 index 0000000..547db4b --- /dev/null +++ b/migrations/member_state_migration_v2.sql @@ -0,0 +1,252 @@ +-- Step 1: Create the member_states lookup table +CREATE TABLE member_states ( + state_code CHAR(1) PRIMARY KEY, + state_name VARCHAR(20) NOT NULL, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Insert the three states +INSERT INTO member_states (state_code, state_name, description) VALUES +('A', 'Active', 'Member is currently active'), +('I', 'Inactive', 'Member is temporarily inactive'), +('D', 'Deactivated', 'Member has been permanently deactivated'); + +-- Step 2: Create the member_state_log table +drop table member_state_log; +CREATE TABLE member_state_log ( + log_id SERIAL PRIMARY KEY, + member_id INTEGER NOT NULL, + state_code CHAR(2) NOT NULL, + changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + changed_by INTEGER, -- Optional: user ID who made the change + reason TEXT, -- Optional: reason for state change + FOREIGN KEY (member_id) REFERENCES members(id), + FOREIGN KEY (state_code) REFERENCES member_states(state_code) +); + +-- Create indexes for better performance +CREATE INDEX idx_member_state_log_member_id ON member_state_log(member_id); +CREATE INDEX idx_member_state_log_changed_at ON member_state_log(changed_at); +CREATE INDEX idx_member_state_log_state_code ON member_state_log(state_code); + +-- Step 3: Migration script to populate the log table from existing data +INSERT INTO member_state_log (member_id, state_code, changed_at) +SELECT + id, + case + when member_died is not null then 'D' + when member_active = true THEN 'A' + ELSE 'I' + END, + COALESCE(member_died, updated_date, created_date, CURRENT_TIMESTAMP) +FROM members; + +-- Step 4: Create a view to get current member states (most recent log entry per member) +CREATE OR REPLACE VIEW member_current_state AS +SELECT DISTINCT ON (msl.member_id) + msl.member_id, + msl.state_code, + ms.state_name, + ms.description, + msl.changed_at, + msl.changed_by, + msl.reason +FROM member_state_log msl +JOIN member_states ms ON msl.state_code = ms.state_code +ORDER BY msl.member_id, msl.changed_at DESC, msl.log_id DESC; + +-- Step 5: Function to change member state (ensures proper logging) +CREATE OR REPLACE FUNCTION change_member_state( + p_member_id INTEGER, + p_new_state_code CHAR(1), + p_changed_by INTEGER DEFAULT NULL, + p_reason TEXT DEFAULT NULL +) RETURNS VOID AS $$ +BEGIN + -- Validate that the state exists + IF NOT EXISTS (SELECT 1 FROM member_states WHERE state_code = p_new_state_code) THEN + RAISE EXCEPTION 'Invalid state code: %', p_new_state_code; + END IF; + + -- Validate that the member exists + IF NOT EXISTS (SELECT 1 FROM members WHERE member_id = p_member_id) THEN + RAISE EXCEPTION 'Member not found: %', p_member_id; + END IF; + + -- Insert the new state log entry + INSERT INTO member_state_log (member_id, state_code, changed_by, reason) + VALUES (p_member_id, p_new_state_code, p_changed_by, p_reason); +END; +$$ LANGUAGE plpgsql; + +-- Step 6: Common queries you'll need + +-- Get all active members +SELECT m.*, mcs.state_name, mcs.changed_at as last_state_change +FROM members m +JOIN member_current_state mcs ON m.member_id = mcs.member_id +WHERE mcs.state_code = 'A'; + +-- Get member state history +SELECT + msl.changed_at, + ms.state_name, + msl.reason, + msl.changed_by +FROM member_state_log msl +JOIN member_states ms ON msl.state_code = ms.state_code +WHERE msl.member_id = 123 -- Replace with actual member_id +ORDER BY msl.changed_at DESC; + +-- Count members by state +SELECT + ms.state_name, + COUNT(*) as member_count +FROM member_current_state mcs +JOIN member_states ms ON mcs.state_code = ms.state_code +GROUP BY ms.state_code, ms.state_name +ORDER BY ms.state_code; + +-- Step 7: Example usage of the change_member_state function +-- Change member 123 to inactive +SELECT change_member_state(123, 'I', 1, 'Member requested temporary suspension'); + +-- Reactivate member 123 +SELECT change_member_state(123, 'A', 1, 'Member reactivation approved'); + +-- Step 8: Options for "calculated column" - current member state + +-- OPTION 1: Add a cached state column with triggers (RECOMMENDED) +ALTER TABLE members ADD COLUMN current_state_code CHAR(1); +ALTER TABLE members ADD COLUMN current_state_changed_at TIMESTAMP; +ALTER TABLE members ADD CONSTRAINT fk_members_current_state + FOREIGN KEY (current_state_code) REFERENCES member_states(state_code); + +-- Function to update member's current state cache +CREATE OR REPLACE FUNCTION update_member_current_state_cache(p_member_id INTEGER) +RETURNS VOID AS $ +DECLARE + current_state RECORD; +BEGIN + -- Get the most recent state for this member + SELECT state_code, changed_at + INTO current_state + FROM member_state_log + WHERE member_id = p_member_id + ORDER BY changed_at DESC, log_id DESC + LIMIT 1; + + -- Update the cached values in members table + IF FOUND THEN + UPDATE members + SET current_state_code = current_state.state_code, + current_state_changed_at = current_state.changed_at + WHERE member_id = p_member_id; + END IF; +END; +$ LANGUAGE plpgsql; + +-- Trigger to automatically update cache when state changes +CREATE OR REPLACE FUNCTION trigger_update_member_state_cache() +RETURNS TRIGGER AS $ +BEGIN + PERFORM update_member_current_state_cache(NEW.member_id); + RETURN NEW; +END; +$ LANGUAGE plpgsql; + +CREATE TRIGGER tr_member_state_log_update_cache + AFTER INSERT ON member_state_log + FOR EACH ROW + EXECUTE FUNCTION trigger_update_member_state_cache(); + +-- Initialize the cache for existing members +UPDATE members +SET (current_state_code, current_state_changed_at) = ( + SELECT mcs.state_code, mcs.changed_at + FROM member_current_state mcs + WHERE mcs.member_id = members.member_id +); + +-- Update the change_member_state function to work with cache +CREATE OR REPLACE FUNCTION change_member_state( + p_member_id INTEGER, + p_new_state_code CHAR(1), + p_changed_by INTEGER DEFAULT NULL, + p_reason TEXT DEFAULT NULL +) RETURNS VOID AS $ +BEGIN + -- Validate that the state exists + IF NOT EXISTS (SELECT 1 FROM member_states WHERE state_code = p_new_state_code) THEN + RAISE EXCEPTION 'Invalid state code: %', p_new_state_code; + END IF; + + -- Validate that the member exists + IF NOT EXISTS (SELECT 1 FROM members WHERE member_id = p_member_id) THEN + RAISE EXCEPTION 'Member not found: %', p_member_id; + END IF; + + -- Insert the new state log entry (trigger will update cache automatically) + INSERT INTO member_state_log (member_id, state_code, changed_by, reason) + VALUES (p_member_id, p_new_state_code, p_changed_by, p_reason); +END; +$ LANGUAGE plpgsql; + +-- OPTION 2: Generated column using a function (PostgreSQL 12+) +-- Note: This approach has performance implications for large tables +ALTER TABLE members ADD COLUMN member_state CHAR(2) +GENERATED ALWAYS AS ( + (SELECT state_code + FROM member_state_log msl + WHERE msl.member_id = members.id + ORDER BY changed_at DESC, log_id DESC + LIMIT 1) +) STORED; + +-- OPTION 3: Materialized view (refresh manually or on schedule) +/* +CREATE MATERIALIZED VIEW members_with_current_state AS +SELECT + m.*, + mcs.state_code as current_state_code, + mcs.state_name as current_state_name, + mcs.changed_at as current_state_changed_at +FROM members m +LEFT JOIN member_current_state mcs ON m.member_id = mcs.member_id; + +CREATE UNIQUE INDEX idx_members_with_current_state_id +ON members_with_current_state(member_id); + +-- Refresh the materialized view (call this after state changes) +-- REFRESH MATERIALIZED VIEW members_with_current_state; +*/ + +-- Updated common queries using the cached column (OPTION 1) + +-- Get all active members (much faster now!) +SELECT m.*, ms.state_name +FROM members m +JOIN member_states ms ON m.current_state_code = ms.state_code +WHERE m.current_state_code = 'A'; + +-- Get member with current state info +SELECT + m.*, + ms.state_name as current_state_name, + m.current_state_changed_at +FROM members m +LEFT JOIN member_states ms ON m.current_state_code = ms.state_code +WHERE m.member_id = 123; + +-- Count members by current state (very fast) +SELECT + ms.state_name, + COUNT(*) as member_count +FROM members m +JOIN member_states ms ON m.current_state_code = ms.state_code +GROUP BY ms.state_code, ms.state_name +ORDER BY ms.state_code; + +-- Step 9: After testing, remove the old column (CAREFUL!) +-- ALTER TABLE members DROP COLUMN member_active; \ No newline at end of file diff --git a/migrations/member_state_migration_v4.sql b/migrations/member_state_migration_v4.sql new file mode 100644 index 0000000..c5a6a16 --- /dev/null +++ b/migrations/member_state_migration_v4.sql @@ -0,0 +1,255 @@ +-- Step 1: Create the member_states lookup table +CREATE TABLE member_states ( + state_code CHAR(1) PRIMARY KEY, + state_name VARCHAR(20) NOT NULL, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Insert the three states +INSERT INTO member_states (state_code, state_name, description) VALUES +('A', 'Active', 'Member is currently active'), +('I', 'Inactive', 'Member is temporarily inactive'), +('D', 'Deactivated', 'Member has been permanently deactivated'); + +-- Step 2: Create the member_state_log table +CREATE TABLE member_state_log ( + log_id SERIAL PRIMARY KEY, + member_id INTEGER NOT NULL, + state_code CHAR(1) NOT NULL, + changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + changed_by INTEGER, -- Optional: user ID who made the change + reason TEXT, -- Optional: reason for state change + FOREIGN KEY (member_id) REFERENCES members(member_id), + FOREIGN KEY (state_code) REFERENCES member_states(state_code) +); + +-- Create indexes for better performance +CREATE INDEX idx_member_state_log_member_id ON member_state_log(member_id); +CREATE INDEX idx_member_state_log_changed_at ON member_state_log(changed_at); +CREATE INDEX idx_member_state_log_state_code ON member_state_log(state_code); + +-- Step 3: Migration script to populate the log table from existing data +INSERT INTO member_state_log (member_id, state_code, changed_at) +SELECT + member_id, + CASE + WHEN member_died IS NOT NULL THEN 'D' + WHEN member_active = true THEN 'A' + ELSE 'I' + END, + COALESCE(updated_at, created_at, CURRENT_TIMESTAMP) +FROM members; + +-- Step 4: Create a view to get current member states (most recent log entry per member) +CREATE OR REPLACE VIEW member_current_state AS +SELECT DISTINCT ON (msl.member_id) + msl.member_id, + msl.state_code, + ms.state_name, + ms.description, + msl.changed_at, + msl.changed_by, + msl.reason +FROM member_state_log msl +JOIN member_states ms ON msl.state_code = ms.state_code +ORDER BY msl.member_id, msl.changed_at DESC, msl.log_id DESC; + +-- Step 5: Function to change member state (ensures proper logging) +CREATE OR REPLACE FUNCTION change_member_state( + p_member_id INTEGER, + p_new_state_code CHAR(1), + p_changed_by INTEGER DEFAULT NULL, + p_reason TEXT DEFAULT NULL +) RETURNS VOID AS $$ +BEGIN + -- Validate that the state exists + IF NOT EXISTS (SELECT 1 FROM member_states WHERE state_code = p_new_state_code) THEN + RAISE EXCEPTION 'Invalid state code: %', p_new_state_code; + END IF; + + -- Validate that the member exists + IF NOT EXISTS (SELECT 1 FROM members WHERE member_id = p_member_id) THEN + RAISE EXCEPTION 'Member not found: %', p_member_id; + END IF; + + -- Insert the new state log entry + INSERT INTO member_state_log (member_id, state_code, changed_by, reason) + VALUES (p_member_id, p_new_state_code, p_changed_by, p_reason); +END; +$$ LANGUAGE plpgsql; + +-- Step 6: Common queries you'll need + +-- Get all active members +SELECT m.*, mcs.state_name, mcs.changed_at as last_state_change +FROM members m +JOIN member_current_state mcs ON m.member_id = mcs.member_id +WHERE mcs.state_code = 'A'; + +-- Get member state history +SELECT + msl.changed_at, + ms.state_name, + msl.reason, + msl.changed_by +FROM member_state_log msl +JOIN member_states ms ON msl.state_code = ms.state_code +WHERE msl.member_id = 123 -- Replace with actual member_id +ORDER BY msl.changed_at DESC; + +-- Count members by state +SELECT + ms.state_name, + COUNT(*) as member_count +FROM member_current_state mcs +JOIN member_states ms ON mcs.state_code = ms.state_code +GROUP BY ms.state_code, ms.state_name +ORDER BY ms.state_code; + +-- Step 7: Example usage of the change_member_state function +-- Change member 123 to inactive +SELECT change_member_state(123, 'I', 1, 'Member requested temporary suspension'); + +-- Reactivate member 123 +SELECT change_member_state(123, 'A', 1, 'Member reactivation approved'); + +-- Step 8: Options for "calculated column" - current member state + +-- OPTION 1: Add a cached state column with triggers (RECOMMENDED) +ALTER TABLE members ADD COLUMN current_state_code CHAR(1); +ALTER TABLE members ADD COLUMN current_state_changed_at TIMESTAMP; +ALTER TABLE members ADD CONSTRAINT fk_members_current_state + FOREIGN KEY (current_state_code) REFERENCES member_states(state_code); + +-- Function to update member's current state cache +CREATE OR REPLACE FUNCTION update_member_current_state_cache(p_member_id INTEGER) +RETURNS VOID AS $ +DECLARE + current_state RECORD; +BEGIN + -- Get the most recent state for this member + SELECT state_code, changed_at + INTO current_state + FROM member_state_log + WHERE member_id = p_member_id + ORDER BY changed_at DESC, log_id DESC + LIMIT 1; + + -- Update the cached values in members table + IF FOUND THEN + UPDATE members + SET current_state_code = current_state.state_code, + current_state_changed_at = current_state.changed_at + WHERE member_id = p_member_id; + END IF; +END; +$ LANGUAGE plpgsql; + +-- Trigger to automatically update cache when state changes +CREATE OR REPLACE FUNCTION trigger_update_member_state_cache() +RETURNS TRIGGER AS $ +BEGIN + PERFORM update_member_current_state_cache(NEW.member_id); + RETURN NEW; +END; +$ LANGUAGE plpgsql; + +CREATE TRIGGER tr_member_state_log_update_cache + AFTER INSERT ON member_state_log + FOR EACH ROW + EXECUTE FUNCTION trigger_update_member_state_cache(); + +-- Initialize the cache for existing members +UPDATE members +SET (current_state_code, current_state_changed_at) = ( + SELECT mcs.state_code, mcs.changed_at + FROM member_current_state mcs + WHERE mcs.member_id = members.member_id +); + +-- Update the change_member_state function to work with cache +CREATE OR REPLACE FUNCTION change_member_state( + p_member_id INTEGER, + p_new_state_code CHAR(1), + p_changed_by INTEGER DEFAULT NULL, + p_reason TEXT DEFAULT NULL +) RETURNS VOID AS $ +BEGIN + -- Validate that the state exists + IF NOT EXISTS (SELECT 1 FROM member_states WHERE state_code = p_new_state_code) THEN + RAISE EXCEPTION 'Invalid state code: %', p_new_state_code; + END IF; + + -- Validate that the member exists + IF NOT EXISTS (SELECT 1 FROM members WHERE member_id = p_member_id) THEN + RAISE EXCEPTION 'Member not found: %', p_member_id; + END IF; + + -- Insert the new state log entry (trigger will update cache automatically) + INSERT INTO member_state_log (member_id, state_code, changed_by, reason) + VALUES (p_member_id, p_new_state_code, p_changed_by, p_reason); +END; +$ LANGUAGE plpgsql; + +-- OPTION 2: Generated column using a function (NOT POSSIBLE) +-- PostgreSQL doesn't allow subqueries in generated column expressions +-- This approach won't work: +/* +ALTER TABLE members ADD COLUMN current_state_code_generated CHAR(1) +GENERATED ALWAYS AS ( + (SELECT state_code + FROM member_state_log msl + WHERE msl.member_id = members.member_id + ORDER BY changed_at DESC, log_id DESC + LIMIT 1) +) STORED; +-- ERROR: cannot use subquery in column generation expression +*/ + +-- OPTION 3: Materialized view (refresh manually or on schedule) +/* +CREATE MATERIALIZED VIEW members_with_current_state AS +SELECT + m.*, + mcs.state_code as current_state_code, + mcs.state_name as current_state_name, + mcs.changed_at as current_state_changed_at +FROM members m +LEFT JOIN member_current_state mcs ON m.member_id = mcs.member_id; + +CREATE UNIQUE INDEX idx_members_with_current_state_id +ON members_with_current_state(member_id); + +-- Refresh the materialized view (call this after state changes) +-- REFRESH MATERIALIZED VIEW members_with_current_state; +*/ + +-- Updated common queries using the cached column (OPTION 1) + +-- Get all active members (much faster now!) +SELECT m.*, ms.state_name +FROM members m +JOIN member_states ms ON m.current_state_code = ms.state_code +WHERE m.current_state_code = 'A'; + +-- Get member with current state info +SELECT + m.*, + ms.state_name as current_state_name, + m.current_state_changed_at +FROM members m +LEFT JOIN member_states ms ON m.current_state_code = ms.state_code +WHERE m.member_id = 123; + +-- Count members by current state (very fast) +SELECT + ms.state_name, + COUNT(*) as member_count +FROM members m +JOIN member_states ms ON m.current_state_code = ms.state_code +GROUP BY ms.state_code, ms.state_name +ORDER BY ms.state_code; + +-- Step 9: After testing, remove the old column (CAREFUL!) +-- ALTER TABLE members DROP COLUMN member_active; \ No newline at end of file diff --git a/migrations/sqlalchemy_models.py b/migrations/sqlalchemy_models.py new file mode 100644 index 0000000..ad3e008 --- /dev/null +++ b/migrations/sqlalchemy_models.py @@ -0,0 +1,197 @@ +from sqlalchemy import Column, String, Integer, DateTime, Text, ForeignKey +from sqlalchemy.orm import relationship +from sqlalchemy.ext.declarative import declarative_base +from datetime import datetime + +Base = declarative_base() + +# OPTION 1: Correct relationship setup +class MemberStates(Base): + __tablename__ = 'member_states' + + state_code = Column(String(1), primary_key=True) + state_name = Column(String(20), nullable=False) + description = Column(Text) + created_at = Column(DateTime, default=datetime.utcnow) + + # Backref will create 'members' attribute on MemberStates + # members = relationship('Members', back_populates='current_state') + +class Members(Base): + __tablename__ = 'members' + + id = Column(Integer, primary_key=True) # Assuming 'id' is your primary key + # ... other member fields ... + + # Add foreign key constraint + current_state_code = Column(String(1), ForeignKey('member_states.state_code'), index=True) + current_state_changed_at = Column(DateTime) + + # Correct relationship definition + current_state = relationship('MemberStates', back_populates='members') + +# Update MemberStates to complete the bidirectional relationship +MemberStates.members = relationship('Members', back_populates='current_state') + +class MemberStateLog(Base): + __tablename__ = 'member_state_log' + + log_id = Column(Integer, primary_key=True) + member_id = Column(Integer, ForeignKey('members.id'), nullable=False) + state_code = Column(String(1), ForeignKey('member_states.state_code'), nullable=False) + changed_at = Column(DateTime, default=datetime.utcnow) + changed_by = Column(Integer) # Optional: user ID who made the change + reason = Column(Text) + + # Relationships + member = relationship('Members', backref='state_history') + state = relationship('MemberStates') + +# OPTION 2: Alternative approach with primaryjoin (if you can't add foreign key) +class MembersAlternative(Base): + __tablename__ = 'members' + + id = Column(Integer, primary_key=True) + current_state_code = Column(String(1), index=True) # No foreign key + current_state_changed_at = Column(DateTime) + + # Use primaryjoin to specify the join condition + current_state = relationship( + 'MemberStates', + primaryjoin='Members.current_state_code == MemberStates.state_code', + foreign_keys=[current_state_code] + ) + +# USAGE EXAMPLES: + +# Example 1: Query members with their current state +def get_members_with_state(): + from sqlalchemy.orm import sessionmaker + Session = sessionmaker() + session = Session() + + members = session.query(Members).join(Members.current_state).all() + + for member in members: + print(f"Member: {member.name}") + print(f"State: {member.current_state.state_name}") + print(f"Description: {member.current_state.description}") + + session.close() + +# Example 2: Get member state name directly +def get_member_state_name(member): + if member.current_state: + return member.current_state.state_name + return "Unknown" + +# Example 3: Filter members by state +def get_active_members(): + from sqlalchemy.orm import sessionmaker + Session = sessionmaker() + session = Session() + + active_members = session.query(Members).filter( + Members.current_state_code == 'A' + ).all() + + session.close() + return active_members + +# Example 4: Count members by state +def count_members_by_state(): + from sqlalchemy.orm import sessionmaker + from sqlalchemy import func + Session = sessionmaker() + session = Session() + + counts = session.query( + MemberStates.state_name, + func.count(Members.id).label('count') + ).outerjoin(Members).group_by( + MemberStates.state_code, + MemberStates.state_name + ).all() + + session.close() + return counts + +# Example 5: Update member state (with logging) +def change_member_state(session, member_id, new_state_code, changed_by=None, reason=None): + # Get the member + member = session.query(Members).get(member_id) + if not member: + raise ValueError(f"Member {member_id} not found") + + # Validate state exists + state = session.query(MemberStates).get(new_state_code) + if not state: + raise ValueError(f"Invalid state code: {new_state_code}") + + # Log the state change + log_entry = MemberStateLog( + member_id=member_id, + state_code=new_state_code, + changed_by=changed_by, + reason=reason + ) + session.add(log_entry) + + # Update member's current state + member.current_state_code = new_state_code + member.current_state_changed_at = datetime.utcnow() + + session.commit() + +# Example 6: Serialization for API responses +def serialize_member_with_state(member): + return { + 'id': member.id, + 'name': member.name, # Assuming you have a name field + 'current_state_code': member.current_state_code, + 'current_state_name': member.current_state.state_name if member.current_state else None, + 'current_state_description': member.current_state.description if member.current_state else None, + 'current_state_changed_at': member.current_state_changed_at.isoformat() if member.current_state_changed_at else None + } + +# Example 7: Query with eager loading to avoid N+1 queries +def get_all_members_with_states_optimized(): + from sqlalchemy.orm import sessionmaker, joinedload + Session = sessionmaker() + session = Session() + + members = session.query(Members).options( + joinedload(Members.current_state) + ).all() + + # Now you can access member.current_state.state_name without additional queries + result = [serialize_member_with_state(member) for member in members] + + session.close() + return result + +# Example 8: Custom property for easier access +class MembersWithProperty(Base): + __tablename__ = 'members' + + id = Column(Integer, primary_key=True) + current_state_code = Column(String(1), ForeignKey('member_states.state_code'), index=True) + current_state_changed_at = Column(DateTime) + + current_state = relationship('MemberStates') + + @property + def state_name(self): + return self.current_state.state_name if self.current_state else "Unknown" + + @property + def is_active(self): + return self.current_state_code == 'A' + + @property + def is_inactive(self): + return self.current_state_code == 'I' + + @property + def is_deactivated(self): + return self.current_state_code == 'D' \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..d022013 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1061 @@ +{ + "name": "SUST", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@types/react": "^19.1.8", + "lucide-react": "^0.525.0", + "react": "^19.1.0" + }, + "devDependencies": { + "vite": "^7.0.6" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.3.tgz", + "integrity": "sha512-8oQkCTve4H4B4JpmD2FV7fV2ZPTxJHN//bRhCqPUU8v6c5APlxteAXyc7BFaEb4aGpUzrPLU4PoAcGhwmRzZTA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.3.tgz", + "integrity": "sha512-StOsmdXHU2hx3UFTTs6yYxCSwSIgLsfjUBICXyWj625M32OOjakXlaZuGKL+jA3Nvv35+hMxrm/64eCoT07SYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.3.tgz", + "integrity": "sha512-6CfLF3eqKhCdhK0GUnR5ZS99OFz+dtOeB/uePznLKxjCsk5QjT/V0eSEBb4vj+o/ri3i35MseSEQHCLLAgClVw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.3.tgz", + "integrity": "sha512-QLWyWmAJG9elNTNLdcSXUT/M+J7DhEmvs1XPHYcgYkse3UHf9iWTJ+yTPlKMIetiQnNi+cNp+gY4gvjDpREfKw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.3.tgz", + "integrity": "sha512-ZOvBq+5nL0yrZIEo1eq6r7MPvkJ8kC1XATS/yHvcq3WbDNKNKBQ1uIF4hicyzDMoJt72G+sn1nKsFXpifZyRDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.3.tgz", + "integrity": "sha512-AYvGR07wecEnyYSovyJ71pTOulbNvsrpRpK6i/IM1b0UGX1vFx51afYuPYPxnvE9aCl5xPnhQicEvdIMxClRgQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.3.tgz", + "integrity": "sha512-Yx8Cp38tfRRToVLuIWzBHV25/QPzpUreOPIiUuNV7KahNPurYg2pYQ4l7aYnvpvklO1riX4643bXLvDsYSBIrA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.3.tgz", + "integrity": "sha512-4dIYRNxlXGDKnO6qgcda6LxnObPO6r1OBU9HG8F9pAnHHLtfbiOqCzDvkeHknx+5mfFVH4tWOl+h+cHylwsPWA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.3.tgz", + "integrity": "sha512-M6uVlWKmhLN7LguLDu6396K1W5IBlAaRonjlHQgc3s4dOGceu0FeBuvbXiUPYvup/6b5Ln7IEX7XNm68DN4vrg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.3.tgz", + "integrity": "sha512-emaYiOTQJUd6fC9a6jcw9zIWtzaUiuBC+vomggaM4In2iOra/lA6IMHlqZqQZr08NYXrOPMVigreLMeSAwv3Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.3.tgz", + "integrity": "sha512-3P77T5AQ4UfVRJSrTKLiUZDJ6XsxeP80027bp6mOFh8sevSD038mYuIYFiUtrSJxxgFb+NgRJFF9oIa0rlUsmg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.45.3.tgz", + "integrity": "sha512-/VPH3ZVeSlmCBPhZdx/+4dMXDjaGMhDsWOBo9EwSkGbw2+OAqaslL53Ao2OqCxR0GgYjmmssJ+OoG+qYGE7IBg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.3.tgz", + "integrity": "sha512-Hs5if0PjROl1MGMmZX3xMAIfqcGxQE2SJWUr/CpDQsOQn43Wq4IvXXxUMWtiY/BrzdqCCJlRgJ5DKxzS3qWkCw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.3.tgz", + "integrity": "sha512-Qm0WOwh3Lk388+HJFl1ILGbd2iOoQf6yl4fdGqOjBzEA+5JYbLcwd+sGsZjs5pkt8Cr/1G42EiXmlRp9ZeTvFA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.3.tgz", + "integrity": "sha512-VJdknTaYw+TqXzlh9c7vaVMh/fV2sU8Khfk4a9vAdYXJawpjf6z3U1k7vDWx2IQ9ZOPoOPxgVpDfYOYhxD7QUA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.3.tgz", + "integrity": "sha512-SUDXU5YabLAMl86FpupSQQEWzVG8X0HM+Q/famnJusbPiUgQnTGuSxtxg4UAYgv1ZmRV1nioYYXsgtSokU/7+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.3.tgz", + "integrity": "sha512-ezmqknOUFgZMN6wW+Avlo4sXF3Frswd+ncrwMz4duyZ5Eqd+dAYgJ+A1MY+12LNZ7XDhCiijJceueYvtnzdviw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.3.tgz", + "integrity": "sha512-1YfXoUEE++gIW66zNB9Twd0Ua5xCXpfYppFUxVT/Io5ZT3fO6Se+C/Jvmh3usaIHHyi53t3kpfjydO2GAy5eBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.3.tgz", + "integrity": "sha512-Iok2YA3PvC163rVZf2Zy81A0g88IUcSPeU5pOilcbICXre2EP1mxn1Db/l09Z/SK1vdSLtpJXAnwGuMOyf5O9g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.3.tgz", + "integrity": "sha512-HwHCH5GQTOeGYP5wBEBXFVhfQecwRl24Rugoqhh8YwGarsU09bHhOKuqlyW4ZolZCan3eTUax7UJbGSmKSM51A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.1.8", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", + "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" + } + }, + "node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/lucide-react": { + "version": "0.525.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.525.0.tgz", + "integrity": "sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.45.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.3.tgz", + "integrity": "sha512-STwyHZF3G+CrmZhB+qDiROq9s8B5PrOCYN6dtmOvwz585XBnyeHk1GTEhHJtUVb355/9uZhOazyVclTt5uahzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.45.3", + "@rollup/rollup-android-arm64": "4.45.3", + "@rollup/rollup-darwin-arm64": "4.45.3", + "@rollup/rollup-darwin-x64": "4.45.3", + "@rollup/rollup-freebsd-arm64": "4.45.3", + "@rollup/rollup-freebsd-x64": "4.45.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.45.3", + "@rollup/rollup-linux-arm-musleabihf": "4.45.3", + "@rollup/rollup-linux-arm64-gnu": "4.45.3", + "@rollup/rollup-linux-arm64-musl": "4.45.3", + "@rollup/rollup-linux-loongarch64-gnu": "4.45.3", + "@rollup/rollup-linux-ppc64-gnu": "4.45.3", + "@rollup/rollup-linux-riscv64-gnu": "4.45.3", + "@rollup/rollup-linux-riscv64-musl": "4.45.3", + "@rollup/rollup-linux-s390x-gnu": "4.45.3", + "@rollup/rollup-linux-x64-gnu": "4.45.3", + "@rollup/rollup-linux-x64-musl": "4.45.3", + "@rollup/rollup-win32-arm64-msvc": "4.45.3", + "@rollup/rollup-win32-ia32-msvc": "4.45.3", + "@rollup/rollup-win32-x64-msvc": "4.45.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/vite": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.6.tgz", + "integrity": "sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.6", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.40.0", + "tinyglobby": "^0.2.14" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..d802780 --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "dependencies": { + "@types/react": "^19.1.8", + "lucide-react": "^0.525.0", + "react": "^19.1.0" + }, + "devDependencies": { + "vite": "^7.0.6" + } +} diff --git a/sustApp/main.py b/sustApp/main.py index f42a1c8..8ecb9c2 100644 --- a/sustApp/main.py +++ b/sustApp/main.py @@ -1,4 +1,4 @@ -from fastapi import FastAPI, HTTPException, Depends +from fastapi import FastAPI, HTTPException, Depends, WebSocket from fastapi.security import OAuth2PasswordBearer from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel @@ -7,6 +7,9 @@ from sustApp.models.models import Base, Members, Payments from sustApp.models.database import engine, SessionLocal from sqlalchemy.orm import Session from datetime import datetime +import asyncio +import logging + #http://127.0.0.1:8000/docs -> swagger like ui #ngrok http http://localhost:8000 @@ -27,6 +30,7 @@ origins = [ "http://localhost", "http://localhost:8080", "http://localhost:3000", + "http://localhost:5173", ] app.add_middleware( @@ -37,6 +41,48 @@ app.add_middleware( allow_headers=["*"], ) +# Store active WebSocket connections +connected_clients = [] + +# Custom logging handler to send logs via WebSocket +class WebSocketHandler(logging.Handler): + def emit(self, record): + log_message = self.format(record) + # Send to all connected clients + asyncio.create_task(broadcast_log(log_message)) + +# Set up logging +logger = logging.getLogger("app") +logger.setLevel(logging.INFO) +ws_handler = WebSocketHandler() +logger.addHandler(ws_handler) + +@app.websocket("/ws/logs") +async def websocket_endpoint(websocket: WebSocket): + await websocket.accept() + connected_clients.append(websocket) + try: + while True: + await websocket.receive_text() + except: + connected_clients.remove(websocket) + +async def broadcast_log(message): + for client in connected_clients[:]: # Copy list to avoid modification during iteration + try: + await client.send_json({ + "type": "log", + "message": message, + "timestamp": asyncio.get_event_loop().time() + }) + except: + connected_clients.remove(client) + +@app.get("/test") +async def test_endpoint(): + logger.info("This is a test log message from backend!") + return {"message": "Check your browser console"} + class User(BaseModel): username: str email: str | None = None @@ -69,6 +115,8 @@ class MemberBase(BaseModel): #payments: Optional[List[PaymentBase]] member_deliveries: Optional[List[DeliveryBase]] #deliveries: Mapped[List["DeliveryBase"]] = relationship() + current_state_code: str | None = None + current_state_name: str | None = None def get_db(): db = SessionLocal() @@ -86,11 +134,21 @@ async def read_members(db: db_dependency, skip: int = 0, limit: int = 100 ): raise HTTPException(status_code=400, detail='No members') return members +@app.get("/activememberscount/", response_model=int) +async def read_activememberscount(db: db_dependency): + count = db.query(Members).filter(Members.member_active == True).count() + logger.info("This is a test log message from backend!") + return count + @app.get("/member/{member_id}") async def read_member(member_id: int, db: db_dependency): #, token: Annotated[str, Depends(oauth2_scheme)]): - result = db.query(Members).filter(Members.id == member_id).first() + result = db.query(Members).join(Members.current_state).filter(Members.id == member_id).first() if not result: raise HTTPException(status_code=400, detail='Member does not exist') + print(f"Member: {result.member_surname}") + print(f"State: {result.current_state.state_name}") + print(f"Description: {result.current_state.description}") + return result @app.get("/payments/{member_id}") diff --git a/sustApp/models/models.py b/sustApp/models/models.py index b982a47..10e2a42 100644 --- a/sustApp/models/models.py +++ b/sustApp/models/models.py @@ -1,11 +1,21 @@ from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, DateTime, Float, Numeric -from sqlalchemy.types import TypeDecorator, VARCHAR, Integer +from sqlalchemy.types import TypeDecorator, VARCHAR, Integer, CHAR from sqlalchemy_utils import EmailType from sqlalchemy.orm import relationship -from models.database import Base +#from models.database import Base +from sustApp.models.database import Base import datetime +class TrimChar(TypeDecorator): + impl = CHAR + cache_key = False + + def process_result_value(self, value, dialect): + if value: + value = value.strip() + return value + class NullCatchingString(TypeDecorator): impl = VARCHAR @@ -50,6 +60,18 @@ class Members(Base): member_deliveries = relationship('Deliveries', backref='members') member_payments = relationship('Payments', backref='members') #member_deliveries: Mapped[List["Deliveries"]] = relationship() + #current_state_code = Column(String, index=True) + #current_state_name = relationship('MemberStates', backref='state_code') + + current_state_code = Column(TrimChar, index=True) # No foreign key + current_state_changed_at = Column(DateTime) + + # Use primaryjoin to specify the join condition + current_state = relationship( + 'MemberStates', + primaryjoin='Members.current_state_code == MemberStates.state_code', + foreign_keys=[current_state_code] + ) def __repr__(self): return f'' @@ -95,4 +117,26 @@ class Payments_CSV(Base): pc_amount = Column(Numeric) pc_currency = Column(String) pc_timestamp = Column(DateTime) - pc_processed = Column(Boolean, default=False) \ No newline at end of file + pc_processed = Column(Boolean, default=False) + +class MemberStates(Base): + __tablename__ = 'member_states' + + state_code = Column(TrimChar, primary_key=True, index=True) + state_name = Column(String(20), nullable=False) + description = Column(String) + created_at = Column(DateTime) + +class MemberStateLog(Base): + __tablename__ = 'member_state_log' + + log_id = Column(Integer, primary_key=True) + member_id = Column(Integer, ForeignKey('members.id'), nullable=False) + state_code = Column(String(1), ForeignKey('member_states.state_code'), nullable=False) + changed_at = Column(DateTime) + changed_by = Column(Integer) # Optional: user ID who made the change + reason = Column(String) + + # Relationships + member = relationship('Members', backref='state_history') + state = relationship('MemberStates') \ No newline at end of file diff --git a/sustApp/react-frontend/index.html b/sustApp/react-frontend/index.html new file mode 100644 index 0000000..84b6a04 --- /dev/null +++ b/sustApp/react-frontend/index.html @@ -0,0 +1,14 @@ + + + + + + FastAPI Dashboard + + + + +
+ + + \ No newline at end of file diff --git a/sustApp/react-frontend/package-lock.json b/sustApp/react-frontend/package-lock.json new file mode 100644 index 0000000..77658b8 --- /dev/null +++ b/sustApp/react-frontend/package-lock.json @@ -0,0 +1,1817 @@ +{ + "name": "react-frontend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "react-frontend", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "lucide-react": "^0.525.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "typescript": "^5.8.3" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.7.0", + "autoprefixer": "^10.4.21", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.11", + "vite": "^7.0.6" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", + "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", + "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.0", + "@babel/types": "^7.28.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz", + "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", + "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz", + "integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz", + "integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz", + "integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz", + "integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz", + "integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz", + "integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz", + "integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz", + "integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz", + "integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz", + "integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz", + "integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz", + "integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz", + "integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz", + "integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz", + "integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz", + "integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz", + "integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz", + "integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz", + "integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz", + "integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.1.8", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", + "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", + "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/browserslist": { + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001727", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", + "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.191", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.191.tgz", + "integrity": "sha512-xcwe9ELcuxYLUFqZZxL19Z6HVKcvNkIwhbHUz7L3us6u12yR+7uY89dSl570f/IqNthx8dAw3tojG7i4Ni4tDA==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.525.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.525.0.tgz", + "integrity": "sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", + "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.45.1", + "@rollup/rollup-android-arm64": "4.45.1", + "@rollup/rollup-darwin-arm64": "4.45.1", + "@rollup/rollup-darwin-x64": "4.45.1", + "@rollup/rollup-freebsd-arm64": "4.45.1", + "@rollup/rollup-freebsd-x64": "4.45.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", + "@rollup/rollup-linux-arm-musleabihf": "4.45.1", + "@rollup/rollup-linux-arm64-gnu": "4.45.1", + "@rollup/rollup-linux-arm64-musl": "4.45.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", + "@rollup/rollup-linux-riscv64-gnu": "4.45.1", + "@rollup/rollup-linux-riscv64-musl": "4.45.1", + "@rollup/rollup-linux-s390x-gnu": "4.45.1", + "@rollup/rollup-linux-x64-gnu": "4.45.1", + "@rollup/rollup-linux-x64-musl": "4.45.1", + "@rollup/rollup-win32-arm64-msvc": "4.45.1", + "@rollup/rollup-win32-ia32-msvc": "4.45.1", + "@rollup/rollup-win32-x64-msvc": "4.45.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", + "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.6.tgz", + "integrity": "sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.6", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.40.0", + "tinyglobby": "^0.2.14" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/sustApp/react-frontend/package.json b/sustApp/react-frontend/package.json new file mode 100644 index 0000000..6f2b04e --- /dev/null +++ b/sustApp/react-frontend/package.json @@ -0,0 +1,30 @@ +{ + "name": "react-frontend", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "serve": "vite preview" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "dependencies": { + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "lucide-react": "^0.525.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "typescript": "^5.8.3" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.7.0", + "autoprefixer": "^10.4.21", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.11", + "vite": "^7.0.6" + } +} diff --git a/sustApp/react-frontend/src/FastAPIFrontend.tsx b/sustApp/react-frontend/src/FastAPIFrontend.tsx new file mode 100644 index 0000000..58aefe6 --- /dev/null +++ b/sustApp/react-frontend/src/FastAPIFrontend.tsx @@ -0,0 +1,748 @@ +import React, { useState, useEffect } from 'react'; +import { + Server, + Database, + Users, + Activity, + RefreshCw, + Plus, + Edit3, + Trash2, + Eye, + AlertCircle, + CheckCircle, + XCircle +} from 'lucide-react'; + +import { useEffect } from 'react'; + +function App() { + useEffect(() => { + const ws = new WebSocket('ws://localhost:8000/ws/logs'); + + ws.onmessage = (event) => { + const data = JSON.parse(event.data); + if (data.type === 'log') { + console.log(`[Backend Log]: ${data.message}`); + } + }; + + ws.onclose = () => { + console.log('WebSocket connection closed'); + }; + + return () => { + ws.close(); + }; + }, []); + + return
Your React App
; +} + +const Dashboard = () => { + const [apiUrl, setApiUrl] = useState('http://127.0.0.1:8000'); + const [connectionStatus, setConnectionStatus] = useState('disconnected'); + const [isLoading, setIsLoading] = useState(false); + const [activeTab, setActiveTab] = useState('dashboard'); + const [members, setMembers] = useState([]); + const [totalMembers, setTotalMembers] = useState(0); + const [loadingData, setLoadingData] = useState(false); + + const testConnection = async () => { + setIsLoading(true); + try { + console.log('Testing connection to:', apiUrl); + // Test with a simple endpoint first - try /member/1 since /member/100 works + const response = await fetch(`${apiUrl}/member/100`); + + if (response.ok) { + setConnectionStatus('connected'); + console.log('Connection successful!'); + // Load members data when connection is successful + await loadMembersCount(); + } else { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + } catch (error) { + console.error('Connection error:', error); + setConnectionStatus('error'); + } + setIsLoading(false); + }; + + const loadMembersCount = async () => { + if (connectionStatus !== 'connected' && !isLoading) return; + + setLoadingData(true); + console.log('Attempting to load active members count...'); + + // const fetchActiveMembersCount = async () => { + try { + const response = await fetch(`${apiUrl}/activememberscount`); + if (response.ok) { + const count = await response.json(); // Single number return + setTotalMembers(count); + console.log('Total active members count:', count); + } else { + console.log('Failed to fetch active members count:', response.statusText); + setTotalMembers(0); + } + } catch (error) { + console.log('Error fetching active members count:', error); + setMembers([]); + setTotalMembers(0); + } + //} + + setLoadingData(false); + }; + + const loadMembers = async () => { + if (connectionStatus !== 'connected' && !isLoading) return; + + setLoadingData(true); + try { + // Since /members/ gives 500 error, let's try different approaches + console.log('Attempting to load active members count...'); + + // Option 1: Try individual member requests (if you know IDs) + // For now, let's try to get a few members by ID + const memberIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // Adjust these IDs as needed + const memberPromises = memberIds.map(async (id) => { + try { + const response = await fetch(`${apiUrl}/member/${id}`); + if (response.ok) { + return await response.json(); + } + return null; + } catch (error) { + console.log(`Member ${id} not found or error:`, error); + return null; + } + }); + + const results = await Promise.all(memberPromises); + const validMembers = results.filter(member => member !== null); + + console.log('Found members:', validMembers); + setMembers(validMembers); + setTotalMembers(validMembers.length); + + if (validMembers.length === 0) { + console.warn('No members found with IDs 1-10. You may need to adjust the ID range or fix the /members/ endpoint.'); + } + + } catch (error) { + console.error('Error loading members:', error); + setMembers([]); + setTotalMembers(0); + } + setLoadingData(false); + }; + + // Load members when component mounts if already connected + useEffect(() => { + if (connectionStatus === 'connected') { + loadMembersCount(); + } + }, [connectionStatus]); + + const getStatusColor = () => { + switch (connectionStatus) { + case 'connected': return '#10b981'; + case 'error': return '#ef4444'; + default: return '#f59e0b'; + } + }; + + const getStatusIcon = () => { + switch (connectionStatus) { + case 'connected': return ; + case 'error': return ; + default: return ; + } + }; + + const getStatusText = () => { + switch (connectionStatus) { + case 'connected': return 'Connected'; + case 'error': return 'Connection Error'; + default: return 'Disconnected'; + } + }; + + const getFullName = (member) => { + const firstName = member.member_firstname || ''; + const lastName = member.member_surname || ''; + const memberSuffix = member.member_suffix || ''; + const fullName = `${firstName} ${lastName} ${memberSuffix}`.trim(); + return fullName || 'N/A'; + }; + + const MEMBER_STATE_COLORS = { + 'A': '#10B981', // Green - Active + 'I': '#F59E0B', // Amber/Orange - Inactive + 'D': '#EF4444' // Red - Deactivated + }; + + const getMemberStateColor = (stateCode) => { + return MEMBER_STATE_COLORS[stateCode] || '#6B7280'; // Gray as fallback + }; + + const styles = { + container: { + minHeight: '100vh', + backgroundColor: '#f9fafb', + fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif' + }, + header: { + backgroundColor: 'white', + boxShadow: '0 1px 3px 0 rgba(0, 0, 0, 0.1)', + borderBottom: '1px solid #e5e7eb' + }, + headerContent: { + maxWidth: '1280px', + margin: '0 auto', + padding: '0 24px', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + paddingTop: '24px', + paddingBottom: '24px' + }, + title: { + fontSize: '30px', + fontWeight: 'bold', + color: '#111827', + margin: 0 + }, + subtitle: { + color: '#6b7280', + marginTop: '4px', + fontSize: '16px' + }, + statusContainer: { + display: 'flex', + alignItems: 'center', + gap: '8px', + color: getStatusColor(), + fontWeight: '500' + }, + mainContent: { + maxWidth: '1280px', + margin: '0 auto', + padding: '32px 24px' + }, + card: { + backgroundColor: 'white', + borderRadius: '8px', + boxShadow: '0 1px 3px 0 rgba(0, 0, 0, 0.1)', + border: '1px solid #e5e7eb', + padding: '24px', + marginBottom: '32px' + }, + cardTitle: { + fontSize: '18px', + fontWeight: '600', + color: '#111827', + marginBottom: '16px' + }, + connectionForm: { + display: 'flex', + alignItems: 'center', + gap: '16px' + }, + input: { + flex: 1, + padding: '8px 12px', + border: '1px solid #d1d5db', + borderRadius: '6px', + fontSize: '14px', + outline: 'none' + }, + button: { + display: 'inline-flex', + alignItems: 'center', + gap: '8px', + padding: '8px 16px', + backgroundColor: '#2563eb', + color: 'white', + border: 'none', + borderRadius: '6px', + fontSize: '14px', + fontWeight: '500', + cursor: 'pointer', + transition: 'background-color 0.2s' + }, + buttonDisabled: { + opacity: 0.5, + cursor: 'not-allowed' + }, + errorAlert: { + marginTop: '16px', + padding: '16px', + backgroundColor: '#fef2f2', + border: '1px solid #fecaca', + borderRadius: '6px', + display: 'flex', + alignItems: 'center', + gap: '8px' + }, + errorText: { + color: '#991b1b' + }, + tabs: { + borderBottom: '1px solid #e5e7eb', + marginBottom: '32px' + }, + tabsNav: { + display: 'flex', + gap: '32px' + }, + tab: { + padding: '8px 4px', + fontSize: '14px', + fontWeight: '500', + border: 'none', + background: 'none', + cursor: 'pointer', + borderBottom: '2px solid transparent', + transition: 'all 0.2s' + }, + tabActive: { + borderBottom: '2px solid #2563eb', + color: '#2563eb' + }, + tabInactive: { + color: '#6b7280' + }, + statsGrid: { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', + gap: '24px', + marginBottom: '32px' + }, + statCard: { + backgroundColor: 'white', + borderRadius: '8px', + boxShadow: '0 1px 3px 0 rgba(0, 0, 0, 0.1)', + border: '1px solid #e5e7eb', + padding: '24px', + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between' + }, + statContent: { + flex: 1 + }, + statLabel: { + fontSize: '14px', + fontWeight: '500', + color: '#6b7280' + }, + statValue: { + fontSize: '24px', + fontWeight: 'bold', + color: '#111827', + marginTop: '8px' + }, + statIcon: { + padding: '12px', + borderRadius: '50%' + }, + iconOrange: { + backgroundColor: '#fed7aa', + color: '#ea580c' + }, + iconBlue: { + backgroundColor: '#dbeafe', + color: '#2563eb' + }, + iconGreen: { + backgroundColor: '#dcfce7', + color: '#16a34a' + }, + iconPurple: { + backgroundColor: '#e9d5ff', + color: '#9333ea' + }, + actionButtons: { + display: 'flex', + flexWrap: 'wrap', + gap: '16px' + }, + actionButton: { + display: 'inline-flex', + alignItems: 'center', + gap: '8px', + padding: '8px 16px', + border: 'none', + borderRadius: '6px', + fontSize: '14px', + fontWeight: '500', + cursor: 'pointer', + transition: 'background-color 0.2s' + }, + actionButtonGray: { + backgroundColor: '#f3f4f6', + color: '#374151' + }, + actionButtonGreen: { + backgroundColor: '#dcfce7', + color: '#166534' + }, + actionButtonBlue: { + backgroundColor: '#dbeafe', + color: '#1e40af' + }, + emptyState: { + textAlign: 'center', + padding: '32px' + }, + emptyStateIcon: { + margin: '0 auto 16px', + color: '#9ca3af' + }, + emptyStateText: { + color: '#6b7280' + }, + cardHeader: { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: '20px' + }, + + refreshButton: { + display: 'flex', + alignItems: 'center', + padding: '8px 16px', + backgroundColor: '#3B82F6', + color: 'white', + border: 'none', + borderRadius: '6px', + cursor: 'pointer', + fontSize: '14px', + fontWeight: '500' + }, + + loadingState: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + padding: '40px', + color: '#6B7280' + }, + + loadingIcon: { + marginBottom: '10px', + color: '#3B82F6' + }, + + dataContainer: { + marginTop: '20px' + }, + + dataHeader: { + marginBottom: '16px', + padding: '12px', + backgroundColor: '#F3F4F6', + borderRadius: '6px' + }, + + dataCount: { + margin: 0, + fontWeight: '600', + color: '#374151' + }, + + tableContainer: { + overflowX: 'auto', + border: '1px solid #E5E7EB', + borderRadius: '8px' + }, + + table: { + width: '100%', + borderCollapse: 'collapse' + }, + + tableHeaderRow: { + backgroundColor: '#F9FAFB' + }, + + tableHeader: { + padding: '12px', + textAlign: 'left', + fontWeight: '600', + color: '#374151', + borderBottom: '1px solid #E5E7EB' + }, + + tableRow: { + borderBottom: '1px solid #F3F4F6' + }, + + tableCell: { + padding: '12px', + color: '#6B7280' + }, + + statusBadge: { + padding: '4px 8px', + borderRadius: '12px', + fontSize: '12px', + fontWeight: '500' + }, + }; + + return ( +
+ {/* Header */} +
+
+
+

FastAPI Dashboard

+

Manage your backend data and monitor API status

+
+
+ {getStatusIcon()} + {getStatusText()} +
+
+
+ + {/* Main Content */} +
+ {/* Connection Section */} +
+

API Connection

+
+ setApiUrl(e.target.value)} + style={styles.input} + placeholder="Enter API URL" + /> + +
+ {connectionStatus === 'error' && ( +
+ + NetworkError when attempting to fetch resource. +
+ )} +
+ + {/* Navigation Tabs */} +
+
+ + +
+
+ + {/* Stats Grid */} +
+ {/* API Status */} +
+
+

API Status

+

+ {connectionStatus === 'connected' ? 'Online' : 'Offline'} +

+
+
+ +
+
+ + {/* Active Members */} +
+
+

Active Members

+

{totalMembers}

+
+
+ +
+
+ + {/* Active Users */} +
+
+

Active Users

+

1

+
+
+ +
+
+ + {/* Activity */} +
+
+

Activity

+

Live

+
+
+ +
+
+
+ + {/* Quick Actions */} +
+

Quick Actions

+
+ + + +
+
+ + {/* Content based on active tab */} + {activeTab === 'data' && ( +
+
+

Data Management

+ +
+ + {isLoading ? ( +
+ +

Loading members...

+
+ ) : members.length === 0 ? ( +
+ +

No members found. Click "Load Members" to fetch data.

+
+ ) : ( +
+
+

Total Members: {members.length}

+
+ +
+ + + + + + + + + + + + {members.map((member, index) => ( + + + + + + + + ))} + +
IDNameEmailActiveActions
{member.id}{getFullName(member)}{member.member_email || 'N/A'} + + {member.current_state.state_name} + + + + +
+
+
+ )} +
+ )} + +
+ + +
+ ); +}; + +export default Dashboard; \ No newline at end of file diff --git a/sustApp/react-frontend/src/FastAPIFrontend_old.tsx b/sustApp/react-frontend/src/FastAPIFrontend_old.tsx new file mode 100644 index 0000000..4b8c428 --- /dev/null +++ b/sustApp/react-frontend/src/FastAPIFrontend_old.tsx @@ -0,0 +1,458 @@ +import React, { useState, useEffect } from 'react'; +import { Search, Plus, Edit3, Trash2, RefreshCw, AlertCircle, CheckCircle, Server, Database, Users, Activity } from 'lucide-react'; + +const FastAPIFrontend = () => { + const [apiUrl, setApiUrl] = useState('http://localhost:8000'); + const [isConnected, setIsConnected] = useState(false); + const [loading, setLoading] = useState(false); + const [data, setData] = useState([]); + const [error, setError] = useState(null); + const [activeTab, setActiveTab] = useState('dashboard'); + const [formData, setFormData] = useState({}); + const [showModal, setShowModal] = useState(false); + const [editingItem, setEditingItem] = useState(null); + + // Test API connection + const testConnection = async () => { + setLoading(true); + setError(null); + + try { + const response = await fetch(`${apiUrl}/health`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (response.ok) { + setIsConnected(true); + setError(null); + } else { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + } catch (err) { + setIsConnected(false); + setError(err.message || 'Connection failed'); + } finally { + setLoading(false); + } + }; + + // Fetch data from API + const fetchData = async (endpoint = 'items') => { + if (!isConnected) return; + + setLoading(true); + try { + const response = await fetch(`${apiUrl}/${endpoint}`, { + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (response.ok) { + const result = await response.json(); + setData(Array.isArray(result) ? result : [result]); + } else { + throw new Error(`Failed to fetch data: ${response.statusText}`); + } + } catch (err) { + setError(err.message); + } finally { + setLoading(false); + } + }; + + // Create new item + const createItem = async (itemData) => { + try { + const response = await fetch(`${apiUrl}/items`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(itemData), + }); + + if (response.ok) { + await fetchData(); + setShowModal(false); + setFormData({}); + } else { + throw new Error('Failed to create item'); + } + } catch (err) { + setError(err.message); + } + }; + + // Update item + const updateItem = async (id, itemData) => { + try { + const response = await fetch(`${apiUrl}/items/${id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(itemData), + }); + + if (response.ok) { + await fetchData(); + setShowModal(false); + setEditingItem(null); + setFormData({}); + } else { + throw new Error('Failed to update item'); + } + } catch (err) { + setError(err.message); + } + }; + + // Delete item + const deleteItem = async (id) => { + if (!window.confirm('Are you sure you want to delete this item?')) return; + + try { + const response = await fetch(`${apiUrl}/items/${id}`, { + method: 'DELETE', + }); + + if (response.ok) { + await fetchData(); + } else { + throw new Error('Failed to delete item'); + } + } catch (err) { + setError(err.message); + } + }; + + // Handle form submission + const handleSubmit = (e) => { + e.preventDefault(); + if (editingItem) { + updateItem(editingItem.id, formData); + } else { + createItem(formData); + } + }; + + // Open edit modal + const startEdit = (item) => { + setEditingItem(item); + setFormData(item); + setShowModal(true); + }; + + useEffect(() => { + testConnection(); + }, [apiUrl]); + + useEffect(() => { + if (isConnected && activeTab === 'data') { + fetchData(); + } + }, [isConnected, activeTab]); + + const ConnectionStatus = () => ( +
+
+ + {isConnected ? 'Connected' : 'Disconnected'} + + + {apiUrl} +
+ ); + + const Dashboard = () => ( +
+
+
+
+
+

API Status

+

{isConnected ? 'Online' : 'Offline'}

+
+ +
+
+ +
+
+
+

Total Items

+

{data.length}

+
+ +
+
+ +
+
+
+

Active Users

+

1

+
+ +
+
+ +
+
+
+

Activity

+

Live

+
+ +
+
+
+ +
+

Quick Actions

+
+ + + + + +
+
+
+ ); + + const DataView = () => ( +
+
+

Data Management

+ +
+ + {loading ? ( +
+ + Loading... +
+ ) : ( +
+
+ + + + + + + + + + + {data.length === 0 ? ( + + + + ) : ( + data.map((item, index) => ( + + + + + + + )) + )} + +
IDNameDescriptionActions
+ No data available. Add some items to get started. +
+ {item.id || index + 1} + + {item.name || item.title || 'N/A'} + + {item.description || item.content || 'No description'} + +
+ + +
+
+
+
+ )} +
+ ); + + const Modal = () => ( + showModal && ( +
+
+

+ {editingItem ? 'Edit Item' : 'Add New Item'} +

+ +
+
+ + setFormData({...formData, name: e.target.value})} + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="Enter name" + /> +
+ +
+ +