main
Bryan Cerrati 2024-11-01 13:08:24 -04:00
commit 295f9e9700
22 changed files with 727 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

20
Dockerfile Normal file
View File

@ -0,0 +1,20 @@
# Use the official Python image from the Docker Hub
FROM python:3.8-slim
# Set the working directory
WORKDIR /app
# Copy the current directory contents into the container
COPY . /app
# Ensure instance directory exists and is writable
RUN mkdir -p /app/instance && chmod -R 777 /app/instance
# Install any needed packages specified in requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
# Make port 5000 available to the world outside this container
EXPOSE 5005
# Run app.py when the container launches
CMD ["python", "app.py"]

Binary file not shown.

Binary file not shown.

Binary file not shown.

44
app.py Normal file
View File

@ -0,0 +1,44 @@
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import CSRFProtect
from models import db, Company
import os
from routes import register_routes
app = Flask(__name__)
# Construct the full path for the SQLite database
database_path = os.path.join(app.root_path, 'instance', 'daily_overview.sqlite')
# Ensure the instance directory exists
instance_path = os.path.join(app.root_path, 'instance')
if not os.path.exists(instance_path):
os.makedirs(instance_path)
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{database_path}'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECRET_KEY'] = 'your_secret_key'
db.init_app(app)
csrf = CSRFProtect(app)
print(f"Instance path: {instance_path}")
print(f"Database path: {database_path}")
print(f"Database URI: {app.config['SQLALCHEMY_DATABASE_URI']}")
with app.app_context():
try:
db.create_all()
if Company.query.count() == 0:
company1 = Company(name="Hartford Funding")
company2 = Company(name="Alpha Tech Lending")
db.session.add(company1)
db.session.add(company2)
db.session.commit()
except Exception as e:
print(f"Error creating database: {e}")
register_routes(app)
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True, port=5005)

10
docker-compose.yml Normal file
View File

@ -0,0 +1,10 @@
version: '3'
services:
web:
build: .
ports:
- "5005:5005"
volumes:
- .:/app
environment:
FLASK_ENV: development

36
forms.py Normal file
View File

@ -0,0 +1,36 @@
from flask_wtf import FlaskForm
from wtforms import SelectField, StringField, TextAreaField, SubmitField
from wtforms.validators import DataRequired
from models import Company
class TicketForm(FlaskForm):
company = SelectField('Company', coerce=int, validators=[DataRequired()])
name = StringField('Name', validators=[DataRequired()])
subject = StringField('Subject', validators=[DataRequired()])
ticket_number = StringField('Ticket Number', validators=[DataRequired()])
status = SelectField('Status', choices=[('Open', 'Open'), ('Closed', 'Closed')], validators=[DataRequired()])
submit = SubmitField('Submit')
class NoteForm(FlaskForm):
note = TextAreaField('Note', validators=[DataRequired()])
submit = SubmitField('Submit')
class CompanyForm(FlaskForm):
name = StringField('Company Name', validators=[DataRequired()])
submit = SubmitField('Add Company')
class OnsiteForm(FlaskForm):
company = SelectField('Company', coerce=int, validators=[DataRequired()])
day_of_week = SelectField('Day of the Week', choices=[
('Monday', 'Monday'),
('Tuesday', 'Tuesday'),
('Wednesday', 'Wednesday'),
('Thursday', 'Thursday'),
('Friday', 'Friday')
], validators=[DataRequired()])
note = TextAreaField('Note', validators=[DataRequired()])
submit = SubmitField('Submit')
def __init__(self, *args, **kwargs):
super(OnsiteForm, self).__init__(*args, **kwargs)
self.company.choices = [(company.id, company.name) for company in Company.query.all()]

BIN
instance/.DS_Store vendored Normal file

Binary file not shown.

35
models.py Normal file
View File

@ -0,0 +1,35 @@
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
db = SQLAlchemy()
class Company(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False, unique=True)
tickets = db.relationship('Ticket', backref='company', lazy=True)
onsites = db.relationship('Onsite', backref='company', lazy=True)
class Ticket(db.Model):
id = db.Column(db.Integer, primary_key=True)
company_id = db.Column(db.Integer, db.ForeignKey('company.id'), nullable=False)
name = db.Column(db.String(100), nullable=False)
subject = db.Column(db.String(100), nullable=False)
ticket_number = db.Column(db.String(100), nullable=False)
status = db.Column(db.String(10), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
closed_at = db.Column(db.DateTime, nullable=True)
archived = db.Column(db.Boolean, default=False, nullable=False)
class Note(db.Model):
id = db.Column(db.Integer, primary_key=True)
ticket_id = db.Column(db.Integer, db.ForeignKey('ticket.id'), nullable=False)
note = db.Column(db.Text, nullable=False)
archive = db.Column(db.Boolean, default=False, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
ticket = db.relationship('Ticket', backref=db.backref('notes', lazy=True))
class Onsite(db.Model):
id = db.Column(db.Integer, primary_key=True)
company_id = db.Column(db.Integer, db.ForeignKey('company.id'), nullable=False)
day_of_week = db.Column(db.String(10), nullable=False)
note = db.Column(db.Text, nullable=False)

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
Flask>=1.0
Flask-SQLAlchemy
Flask-WTF

151
routes.py Normal file
View File

@ -0,0 +1,151 @@
from flask import render_template, redirect, url_for, request, jsonify
from models import db, Ticket, Note, Company, Onsite
from forms import TicketForm, NoteForm, CompanyForm, OnsiteForm
from datetime import datetime, date
def register_routes(app):
@app.route('/')
def index():
today = date.today()
open_tickets = Ticket.query.filter(Ticket.status == 'Open', Ticket.archived == False).all()
closed_tickets = Ticket.query.filter(Ticket.status == 'Closed', Ticket.archived == False).all()
onsites = {day: Onsite.query.filter_by(day_of_week=day).all() for day in ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']}
return render_template('index.html', open_tickets=open_tickets, closed_tickets=closed_tickets, onsites=onsites)
@app.route('/overview')
def overview():
today = date.today()
open_tickets = Ticket.query.filter(Ticket.status == 'Open', Ticket.archived == False).all()
closed_tickets = Ticket.query.filter(Ticket.status == 'Closed', Ticket.archived == False).all()
onsites = {day: Onsite.query.filter_by(day_of_week=day).all() for day in ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']}
return render_template('overview.html', open_tickets=open_tickets, closed_tickets=closed_tickets, onsites=onsites)
@app.route('/add_ticket', methods=['GET', 'POST'])
def add_ticket():
form = TicketForm()
form.company.choices = [(company.id, company.name) for company in Company.query.all()]
if form.validate_on_submit():
new_ticket = Ticket(
company_id=form.company.data,
name=form.name.data,
subject=form.subject.data,
ticket_number=form.ticket_number.data,
status=form.status.data,
created_at=datetime.utcnow()
)
db.session.add(new_ticket)
db.session.commit()
return redirect(url_for('index'))
return render_template('add_ticket.html', form=form)
@app.route('/add_note/<int:ticket_id>', methods=['GET', 'POST'])
def add_note(ticket_id):
form = NoteForm()
ticket = Ticket.query.get(ticket_id)
if form.validate_on_submit():
new_note = Note(ticket_id=ticket_id, note=form.note.data, created_at=datetime.utcnow())
db.session.add(new_note)
db.session.commit()
return redirect(url_for('index'))
return render_template('add_note.html', form=form, ticket_id=ticket_id, ticket=ticket)
@app.route('/close_ticket/<int:ticket_id>', methods=['GET'])
def close_ticket(ticket_id):
ticket = Ticket.query.get(ticket_id)
if ticket:
ticket.status = 'Closed'
ticket.closed_at = datetime.utcnow()
db.session.commit()
return redirect(url_for('index'))
@app.route('/archive_ticket/<int:ticket_id>', methods=['GET'])
def archive_ticket(ticket_id):
ticket = Ticket.query.get(ticket_id)
if ticket:
ticket.archived = True
db.session.commit()
return redirect(url_for('index'))
@app.route('/archive_note/<int:note_id>', methods=['GET'])
def archive_note(note_id):
note = Note.query.get(note_id)
if note:
note.archive = True
db.session.commit()
return redirect(url_for('index'))
@app.route('/delete_ticket/<int:ticket_id>', methods=['GET'])
def delete_ticket(ticket_id):
ticket = Ticket.query.get(ticket_id)
if ticket:
db.session.delete(ticket)
db.session.commit()
return redirect(url_for('all_tickets'))
@app.route('/all_tickets')
def all_tickets():
closed_tickets = Ticket.query.filter_by(status='Closed').all()
open_tickets = Ticket.query.filter_by(status='Open').all()
return render_template('all_tickets.html', closed_tickets=closed_tickets, open_tickets=open_tickets)
@app.route('/companies', methods=['GET', 'POST'])
def companies():
form = CompanyForm()
if form.validate_on_submit():
new_company = Company(name=form.name.data)
db.session.add(new_company)
db.session.commit()
return redirect(url_for('companies'))
all_companies = Company.query.all()
return render_template('companies.html', form=form, companies=all_companies)
@app.route('/add_onsite', methods=['GET', 'POST'])
def add_onsite():
form = OnsiteForm()
if form.validate_on_submit():
new_onsite = Onsite(
company_id=form.company.data,
day_of_week=form.day_of_week.data,
note=form.note.data
)
db.session.add(new_onsite)
db.session.commit()
return redirect(url_for('index'))
return render_template('add_onsite.html', form=form)
@app.route('/delete_onsite/<int:onsite_id>', methods=['GET'])
def delete_onsite(onsite_id):
onsite = Onsite.query.get(onsite_id)
if onsite:
db.session.delete(onsite)
db.session.commit()
return redirect(url_for('index'))
@app.route('/update_ticket/<int:ticket_id>', methods=['POST'])
def update_ticket(ticket_id):
data = request.get_json()
ticket = Ticket.query.get(ticket_id)
if ticket:
if data['field'] == 'company_id':
company = Company.query.filter_by(name=data['value']).first()
if company:
ticket.company_id = company.id
elif data['field'] == 'name':
ticket.name = data['value']
elif data['field'] == 'subject':
ticket.subject = data['value']
elif data['field'] == 'ticket_number':
ticket.ticket_number = data['value']
db.session.commit()
return jsonify(success=True)
return jsonify(success=False), 400
@app.route('/reopen_ticket/<int:ticket_id>', methods=['GET'])
def reopen_ticket(ticket_id):
ticket = Ticket.query.get(ticket_id)
if ticket:
ticket.status = 'Open'
ticket.archived = False
db.session.commit()
return redirect(url_for('index'))

30
static/styles.css Normal file
View File

@ -0,0 +1,30 @@
body {
font-family: Arial, sans-serif;
}
header, nav, main {
padding: 1em;
}
header {
background-color: #f8f8f8;
border-bottom: 1px solid #ddd;
}
nav ul {
list-style: none;
padding: 0;
}
nav ul li {
display: inline;
margin-right: 10px;
}
h1 {
font-size: 2em;
}
h2 {
font-size: 1.5em;
}

12
templates/add_note.html Normal file
View File

@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block title %}Add Note{% endblock %}
{% block content %}
<h1>Add Note to {{ ticket.company.name }} - {{ ticket.name }} - {{ ticket.subject }} - {{ ticket.ticket_number }}</h1>
<form method="POST" action="">
{{ form.hidden_tag() }}
{{ form.note.label }} {{ form.note() }}<br>
{{ form.submit() }}
</form>
{% endblock %}

23
templates/add_onsite.html Normal file
View File

@ -0,0 +1,23 @@
{% extends "base.html" %}
{% block title %}Add Onsite{% endblock %}
{% block content %}
<h2>Add Onsite</h2>
<form method="POST" action="">
{{ form.hidden_tag() }}
<p>
{{ form.company.label }}<br>
{{ form.company }}
</p>
<p>
{{ form.day_of_week.label }}<br>
{{ form.day_of_week }}
</p>
<p>
{{ form.note.label }}<br>
{{ form.note }}
</p>
<p>{{ form.submit }}</p>
</form>
{% endblock %}

31
templates/add_ticket.html Normal file
View File

@ -0,0 +1,31 @@
{% extends "base.html" %}
{% block title %}Add Ticket{% endblock %}
{% block content %}
<h2>Add Ticket</h2>
<form method="POST" action="">
{{ form.hidden_tag() }}
<p>
{{ form.company.label }}<br>
{{ form.company }}
</p>
<p>
{{ form.name.label }}<br>
{{ form.name }}
</p>
<p>
{{ form.subject.label }}<br>
{{ form.subject }}
</p>
<p>
{{ form.ticket_number.label }}<br>
{{ form.ticket_number }}
</p>
<p>
{{ form.status.label }}<br>
{{ form.status }}
</p>
<p>{{ form.submit }}</p>
</form>
{% endblock %}

View File

@ -0,0 +1,85 @@
{% extends "base.html" %}
{% block title %}All Tickets{% endblock %}
{% block content %}
<h2>Closed Tickets</h2>
<ul>
{% for ticket in closed_tickets %}
<li>
<span contenteditable="true" onblur="updateField({{ ticket.id }}, 'company_id', this)">
{{ ticket.company.name }}
</span> -
<span contenteditable="true" onblur="updateField({{ ticket.id }}, 'name', this)">
{{ ticket.name }}
</span> -
<span contenteditable="true" onblur="updateField({{ ticket.id }}, 'subject', this)">
{{ ticket.subject }}
</span> -
<span contenteditable="true" onblur="updateField({{ ticket.id }}, 'ticket_number', this)">
{{ ticket.ticket_number }}
</span>
<a href="{{ url_for('delete_ticket', ticket_id=ticket.id) }}">(Delete)</a>
<a href="{{ url_for('reopen_ticket', ticket_id=ticket.id) }}">(Reopen)</a>
<ul>
{% for note in ticket.notes %}
<li>
{{ note.note }} - {{ note.created_at.strftime('%Y-%m-%d %H:%M:%S') }}
</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
<h2>Open Tickets</h2>
<ul>
{% for ticket in open_tickets %}
<li>
<span contenteditable="true" onblur="updateField({{ ticket.id }}, 'company_id', this)">
{{ ticket.company.name }}
</span> -
<span contenteditable="true" onblur="updateField({{ ticket.id }}, 'name', this)">
{{ ticket.name }}
</span> -
<span contenteditable="true" onblur="updateField({{ ticket.id }}, 'subject', this)">
{{ ticket.subject }}
</span> -
<span contenteditable="true" onblur="updateField({{ ticket.id }}, 'ticket_number', this)">
{{ ticket.ticket_number }}
</span>
<a href="{{ url_for('delete_ticket', ticket_id=ticket.id) }}">(Delete)</a>
<a href="{{ url_for('close_ticket', ticket_id=ticket.id) }}">(Close)</a>
<ul>
{% for note in ticket.notes %}
<li>
{{ note.note }} - {{ note.created_at.strftime('%Y-%m-%d %H:%M:%S') }}
</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
<script>
function updateField(ticketId, fieldName, element) {
const newValue = element.innerText;
fetch(`/update_ticket/${ticketId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token() }}'
},
body: JSON.stringify({ field: fieldName, value: newValue })
}).then(response => {
if (!response.ok) {
alert('Failed to update the field.');
}
});
}
</script>
{% endblock %}

21
templates/base.html Normal file
View File

@ -0,0 +1,21 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{% block title %}Daily Overview{% endblock %}</title>
</head>
<body>
<header>
<h1>Daily Overview</h1>
<nav>
<a href="{{ url_for('index') }}">Home</a> |
<a href="{{ url_for('all_tickets') }}">All Tickets</a> |
<a href="{{ url_for('add_ticket') }}">Add Ticket</a> |
<a href="{{ url_for('companies') }}">Companies</a>
</nav>
</header>
<main>
{% block content %}{% endblock %}
</main>
</body>
</html>

18
templates/companies.html Normal file
View File

@ -0,0 +1,18 @@
{% extends "base.html" %}
{% block title %}Companies{% endblock %}
{% block content %}
<h1>Companies</h1>
<ul>
{% for company in companies %}
<li>{{ company.name }}</li>
{% endfor %}
</ul>
<h2>Add a New Company</h2>
<form method="POST" action="">
{{ form.hidden_tag() }}
{{ form.name.label }} {{ form.name() }}<br>
{{ form.submit() }}
</form>
{% endblock %}

68
templates/index.html Normal file
View File

@ -0,0 +1,68 @@
{% extends "base.html" %}
{% block title %}Daily Overview - Home{% endblock %}
{% block content %}
<h2>Open Tickets</h2>
<ul>
{% for ticket in open_tickets %}
<li>
{{ ticket.company.name }} - {{ ticket.name }} - {{ ticket.subject }} - {{ ticket.ticket_number }}
<a href="{{ url_for('close_ticket', ticket_id=ticket.id) }}">(Close)</a>
<ul>
{% for note in ticket.notes %}
{% if not note.archive %}
<li>
<a href="{{ url_for('archive_note', note_id=note.id) }}">X</a>
{{ note.note }}
</li>
{% endif %}
{% endfor %}
</ul>
<a href="{{ url_for('add_note', ticket_id=ticket.id) }}">Add Note</a>
</li><br>
{% endfor %}
</ul>
<h2>Closed Tickets</h2>
<ul>
{% for ticket in closed_tickets %}
<li>
{{ ticket.company.name }} - {{ ticket.name }} - {{ ticket.subject }} - {{ ticket.ticket_number }}
<a href="{{ url_for('archive_ticket', ticket_id=ticket.id) }}">(Archive)</a>
<a href="{{ url_for('reopen_ticket', ticket_id=ticket.id) }}">(Reopen)</a>
<ul>
{% for note in ticket.notes %}
{% if not note.archive %}
<li>
{{ note.note }}
</li>
{% endif %}
{% endfor %}
</ul>
<a href="{{ url_for('add_note', ticket_id=ticket.id) }}">Add Note</a>
</li><br>
{% endfor %}
</ul>
<h2>Onsite Schedule</h2>
<p>
<a href="{{ url_for('add_onsite') }}">Add Onsite</a>
</p>
{% for day in ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'] %}
{% if onsites[day]|length > 0 %}
<h3>{{ day }}</h3>
<ul>
{% for onsite in onsites[day] %}
<li>
<a href="{{ url_for('delete_onsite', onsite_id=onsite.id) }}">X</a>
{{ onsite.company.name }} - {{ onsite.note }}
</li>
{% endfor %}
</ul>
{% endif %}
{% endfor %}
<p>
<a href="{{ url_for('overview') }}">Printable Overview</a>
</p>
{% endblock %}

55
templates/overview.html Normal file
View File

@ -0,0 +1,55 @@
{% extends "base.html" %}
{% block title %}Printable Overview{% endblock %}
{% block content %}
<h2>Open Tickets</h2>
<ul>
{% for ticket in open_tickets %}
<li>
{{ ticket.company.name }} - {{ ticket.name }} - {{ ticket.subject }} - {{ ticket.ticket_number }}
<ul>
{% for note in ticket.notes %}
{% if not note.archive %}
<li>
{{ note.note }}
</li>
{% endif %}
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
<h2>Closed Tickets</h2>
<ul>
{% for ticket in closed_tickets %}
<li>
{{ ticket.company.name }} - {{ ticket.name }} - {{ ticket.subject }} - {{ ticket.ticket_number }}
<ul>
{% for note in ticket.notes %}
{% if not note.archive %}
<li>
{{ note.note }}
</li>
{% endif %}
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
<h2>Onsite Schedule</h2>
{% for day in ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'] %}
{% if onsites[day]|length > 0 %}
<h3>{{ day }}</h3>
<ul>
{% for onsite in onsites[day] %}
<li>
{{ onsite.company.name }} - {{ onsite.note }}
</li>
{% endfor %}
</ul>
{% endif %}
{% endfor %}
{% endblock %}

85
templates/v2_index.html Normal file
View File

@ -0,0 +1,85 @@
{% extends "base.html" %}
{% block title %}Daily Overview - Home{% endblock %}
{% block content %}
<div class="container py-5">
<h2 class="mb-4">Open Tickets</h2>
<ul class="list-group mb-5">
{% for ticket in open_tickets %}
<li class="list-group-item">
<div class="d-flex justify-content-between align-items-center">
<div>
<strong>{{ ticket.company.name }}</strong> -
<span class="text-muted">{{ ticket.name }}</span> -
{{ ticket.subject }} -
<span class="badge bg-primary">{{ ticket.ticket_number }}</span>
</div>
<a href="{{ url_for('close_ticket', ticket_id=ticket.id) }}" class="btn btn-sm btn-danger">Close</a>
</div>
<ul class="mt-2 ps-3">
{% for note in ticket.notes %}
{% if not note.archive %}
<li class="d-flex align-items-center mb-2">
<a href="{{ url_for('archive_note', note_id=note.id) }}" class="text-danger me-2">X</a>
{{ note.note }}
</li>
{% endif %}
{% endfor %}
</ul>
<a href="{{ url_for('add_note', ticket_id=ticket.id) }}" class="btn btn-link btn-sm">Add Note</a>
</li>
{% endfor %}
</ul>
<h2 class="mb-4">Closed Tickets</h2>
<ul class="list-group mb-5">
{% for ticket in closed_tickets %}
<li class="list-group-item">
<div class="d-flex justify-content-between align-items-center">
<div>
<strong>{{ ticket.company.name }}</strong> -
<span class="text-muted">{{ ticket.name }}</span> -
{{ ticket.subject }} -
<span class="badge bg-primary">{{ ticket.ticket_number }}</span>
</div>
<div class="btn-group">
<a href="{{ url_for('archive_ticket', ticket_id=ticket.id) }}" class="btn btn-sm btn-warning">Archive</a>
<a href="{{ url_for('reopen_ticket', ticket_id=ticket.id) }}" class="btn btn-sm btn-success">Reopen</a>
</div>
</div>
<ul class="mt-2 ps-3">
{% for note in ticket.notes %}
{% if not note.archive %}
<li class="text-muted">{{ note.note }}</li>
{% endif %}
{% endfor %}
</ul>
<a href="{{ url_for('add_note', ticket_id=ticket.id) }}" class="btn btn-link btn-sm">Add Note</a>
</li>
{% endfor %}
</ul>
<h2 class="mb-4">Onsite Schedule</h2>
<p class="mb-3">
<a href="{{ url_for('add_onsite') }}" class="btn btn-primary">Add Onsite</a>
</p>
{% for day in ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'] %}
{% if onsites[day]|length > 0 %}
<h3 class="h5">{{ day }}</h3>
<ul class="list-group mb-3">
{% for onsite in onsites[day] %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<span>{{ onsite.company.name }} - {{ onsite.note }}</span>
<a href="{{ url_for('delete_onsite', onsite_id=onsite.id) }}" class="text-danger">X</a>
</li>
{% endfor %}
</ul>
{% endif %}
{% endfor %}
<p class="mt-4">
<a href="{{ url_for('overview') }}" class="btn btn-outline-secondary">Printable Overview</a>
</p>
</div>
{% endblock %}