Tarea entregada - Primer Commit
This commit is contained in:
269
CitationsView.qml
Normal file
269
CitationsView.qml
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtQuick.Controls.Material 2.15
|
||||||
|
|
||||||
|
Item {
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 20
|
||||||
|
spacing: 20
|
||||||
|
|
||||||
|
// Lista de citas
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
border.color: Material.color(Material.Grey)
|
||||||
|
border.width: 1
|
||||||
|
radius: 5
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 10
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "Lista de Citas"
|
||||||
|
font.pixelSize: 18
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: citationsListView
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
clip: true
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
model: window.citationsData
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
width: citationsListView.width
|
||||||
|
height: 110
|
||||||
|
color: mouseArea.containsMouse ? Material.color(Material.Grey, Material.Shade100) : "white"
|
||||||
|
border.color: Material.color(Material.Grey, Material.Shade300)
|
||||||
|
radius: 5
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 10
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "Cita #" + modelData.cita_id
|
||||||
|
font.pixelSize: 16
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "Detalles: " + (modelData.detalles || "Sin detalles")
|
||||||
|
font.pixelSize: 13
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "Médico ID: " + modelData.medico_id
|
||||||
|
font.pixelSize: 12
|
||||||
|
color: Material.color(Material.Blue)
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "Paciente ID: " + modelData.paciente_id
|
||||||
|
font.pixelSize: 12
|
||||||
|
color: Material.color(Material.Teal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "✏️"
|
||||||
|
flat: true
|
||||||
|
onClicked: {
|
||||||
|
citationForm.editMode = true
|
||||||
|
citationForm.currentId = modelData.cita_id
|
||||||
|
citationForm.detallesField.text = modelData.detalles
|
||||||
|
citationForm.medicoIdField.text = modelData.medico_id.toString()
|
||||||
|
citationForm.pacienteIdField.text = modelData.paciente_id.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "🗑️"
|
||||||
|
flat: true
|
||||||
|
Material.foreground: Material.Red
|
||||||
|
onClicked: {
|
||||||
|
deleteDialog.citationId = modelData.cita_id
|
||||||
|
deleteDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBar {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formulario de citas
|
||||||
|
Rectangle {
|
||||||
|
id: citationForm
|
||||||
|
Layout.preferredWidth: 350
|
||||||
|
Layout.fillHeight: true
|
||||||
|
border.color: Material.color(Material.Grey)
|
||||||
|
border.width: 1
|
||||||
|
radius: 5
|
||||||
|
|
||||||
|
property bool editMode: false
|
||||||
|
property int currentId: -1
|
||||||
|
property alias detallesField: detallesInput
|
||||||
|
property alias medicoIdField: medicoIdInput
|
||||||
|
property alias pacienteIdField: pacienteIdInput
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 20
|
||||||
|
spacing: 15
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: citationForm.editMode ? "Editar Cita" : "Nueva Cita"
|
||||||
|
font.pixelSize: 18
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "Seleccionar Médico:"
|
||||||
|
font.pixelSize: 14
|
||||||
|
}
|
||||||
|
|
||||||
|
ComboBox {
|
||||||
|
id: medicoCombo
|
||||||
|
Layout.fillWidth: true
|
||||||
|
model: window.medicsData
|
||||||
|
textRole: "nombre"
|
||||||
|
displayText: currentIndex >= 0 ? window.medicsData[currentIndex].nombre + " " + window.medicsData[currentIndex].apellido : "Seleccione médico"
|
||||||
|
Material.accent: Material.Teal
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "Seleccionar Paciente:"
|
||||||
|
font.pixelSize: 14
|
||||||
|
}
|
||||||
|
|
||||||
|
ComboBox {
|
||||||
|
id: pacienteCombo
|
||||||
|
Layout.fillWidth: true
|
||||||
|
model: window.patientsData
|
||||||
|
textRole: "nombre"
|
||||||
|
displayText: currentIndex >= 0 ? window.patientsData[currentIndex].nombre + " " + window.patientsData[currentIndex].apellido : "Seleccione paciente"
|
||||||
|
Material.accent: Material.Teal
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: detallesInput
|
||||||
|
Layout.fillWidth: true
|
||||||
|
placeholderText: "Detalles de la cita"
|
||||||
|
Material.accent: Material.Teal
|
||||||
|
}
|
||||||
|
|
||||||
|
// Campos ocultos para modo edición
|
||||||
|
TextField {
|
||||||
|
id: medicoIdInput
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: pacienteIdInput
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Button {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: citationForm.editMode ? "Actualizar" : "Agregar"
|
||||||
|
Material.background: Material.Teal
|
||||||
|
Material.foreground: "white"
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
var medicoId = citationForm.editMode ?
|
||||||
|
parseInt(medicoIdInput.text) :
|
||||||
|
(medicoCombo.currentIndex >= 0 ? window.medicsData[medicoCombo.currentIndex].medico_id : -1)
|
||||||
|
|
||||||
|
var pacienteId = citationForm.editMode ?
|
||||||
|
parseInt(pacienteIdInput.text) :
|
||||||
|
(pacienteCombo.currentIndex >= 0 ? window.patientsData[pacienteCombo.currentIndex].paciente_id : -1)
|
||||||
|
|
||||||
|
if (detallesInput.text && medicoId > 0 && pacienteId > 0) {
|
||||||
|
if (citationForm.editMode) {
|
||||||
|
apiManager.updateCitation(
|
||||||
|
citationForm.currentId,
|
||||||
|
detallesInput.text,
|
||||||
|
medicoId,
|
||||||
|
pacienteId
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
apiManager.addCitation(
|
||||||
|
detallesInput.text,
|
||||||
|
medicoId,
|
||||||
|
pacienteId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
detallesInput.text = ""
|
||||||
|
medicoCombo.currentIndex = -1
|
||||||
|
pacienteCombo.currentIndex = -1
|
||||||
|
citationForm.editMode = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "Cancelar"
|
||||||
|
visible: citationForm.editMode
|
||||||
|
onClicked: {
|
||||||
|
detallesInput.text = ""
|
||||||
|
medicoCombo.currentIndex = -1
|
||||||
|
pacienteCombo.currentIndex = -1
|
||||||
|
citationForm.editMode = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dialog de confirmación de eliminación
|
||||||
|
Dialog {
|
||||||
|
id: deleteDialog
|
||||||
|
anchors.centerIn: parent
|
||||||
|
title: "Confirmar eliminación"
|
||||||
|
modal: true
|
||||||
|
standardButtons: Dialog.Yes | Dialog.No
|
||||||
|
|
||||||
|
property int citationId: -1
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "¿Está seguro que desea eliminar esta cita?"
|
||||||
|
}
|
||||||
|
|
||||||
|
onAccepted: {
|
||||||
|
apiManager.deleteCitation(deleteDialog.citationId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
231
MedicsView.qml
Normal file
231
MedicsView.qml
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtQuick.Controls.Material 2.15
|
||||||
|
|
||||||
|
Item {
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 20
|
||||||
|
spacing: 20
|
||||||
|
|
||||||
|
// Lista de médicos
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
border.color: Material.color(Material.Grey)
|
||||||
|
border.width: 1
|
||||||
|
radius: 5
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 10
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "Lista de Médicos"
|
||||||
|
font.pixelSize: 18
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: medicsListView
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
clip: true
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
model: window.medicsData
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
width: medicsListView.width
|
||||||
|
height: 90
|
||||||
|
color: mouseArea.containsMouse ? Material.color(Material.Grey, Material.Shade100) : "white"
|
||||||
|
border.color: Material.color(Material.Grey, Material.Shade300)
|
||||||
|
radius: 5
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 10
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: modelData.nombre + " " + modelData.apellido
|
||||||
|
font.pixelSize: 16
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "Especialidad: " + (modelData.especialidad || "N/A")
|
||||||
|
font.pixelSize: 14
|
||||||
|
color: Material.color(Material.Teal)
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "ID: " + modelData.medico_id
|
||||||
|
font.pixelSize: 12
|
||||||
|
color: Material.color(Material.Grey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "✏️"
|
||||||
|
flat: true
|
||||||
|
onClicked: {
|
||||||
|
medicForm.editMode = true
|
||||||
|
medicForm.currentId = modelData.medico_id
|
||||||
|
medicForm.nombreField.text = modelData.nombre
|
||||||
|
medicForm.apellidoField.text = modelData.apellido
|
||||||
|
medicForm.especialidadField.text = modelData.especialidad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "🗑️"
|
||||||
|
flat: true
|
||||||
|
Material.foreground: Material.Red
|
||||||
|
onClicked: {
|
||||||
|
deleteDialog.medicId = modelData.medico_id
|
||||||
|
deleteDialog.medicName = modelData.nombre + " " + modelData.apellido
|
||||||
|
deleteDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBar {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formulario de médicos
|
||||||
|
Rectangle {
|
||||||
|
id: medicForm
|
||||||
|
Layout.preferredWidth: 350
|
||||||
|
Layout.fillHeight: true
|
||||||
|
border.color: Material.color(Material.Grey)
|
||||||
|
border.width: 1
|
||||||
|
radius: 5
|
||||||
|
|
||||||
|
property bool editMode: false
|
||||||
|
property int currentId: -1
|
||||||
|
property alias nombreField: nombreInput
|
||||||
|
property alias apellidoField: apellidoInput
|
||||||
|
property alias especialidadField: especialidadInput
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 20
|
||||||
|
spacing: 15
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: medicForm.editMode ? "Editar Médico" : "Nuevo Médico"
|
||||||
|
font.pixelSize: 18
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: nombreInput
|
||||||
|
Layout.fillWidth: true
|
||||||
|
placeholderText: "Nombre"
|
||||||
|
Material.accent: Material.Teal
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: apellidoInput
|
||||||
|
Layout.fillWidth: true
|
||||||
|
placeholderText: "Apellido"
|
||||||
|
Material.accent: Material.Teal
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: especialidadInput
|
||||||
|
Layout.fillWidth: true
|
||||||
|
placeholderText: "Especialidad"
|
||||||
|
Material.accent: Material.Teal
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Button {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: medicForm.editMode ? "Actualizar" : "Agregar"
|
||||||
|
Material.background: Material.Teal
|
||||||
|
Material.foreground: "white"
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (nombreInput.text && apellidoInput.text && especialidadInput.text) {
|
||||||
|
if (medicForm.editMode) {
|
||||||
|
apiManager.updateMedic(
|
||||||
|
medicForm.currentId,
|
||||||
|
nombreInput.text,
|
||||||
|
apellidoInput.text,
|
||||||
|
especialidadInput.text
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
apiManager.addMedic(
|
||||||
|
nombreInput.text,
|
||||||
|
apellidoInput.text,
|
||||||
|
especialidadInput.text
|
||||||
|
)
|
||||||
|
}
|
||||||
|
nombreInput.text = ""
|
||||||
|
apellidoInput.text = ""
|
||||||
|
especialidadInput.text = ""
|
||||||
|
medicForm.editMode = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "Cancelar"
|
||||||
|
visible: medicForm.editMode
|
||||||
|
onClicked: {
|
||||||
|
nombreInput.text = ""
|
||||||
|
apellidoInput.text = ""
|
||||||
|
especialidadInput.text = ""
|
||||||
|
medicForm.editMode = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dialog de confirmación de eliminación
|
||||||
|
Dialog {
|
||||||
|
id: deleteDialog
|
||||||
|
anchors.centerIn: parent
|
||||||
|
title: "Confirmar eliminación"
|
||||||
|
modal: true
|
||||||
|
standardButtons: Dialog.Yes | Dialog.No
|
||||||
|
|
||||||
|
property int medicId: -1
|
||||||
|
property string medicName: ""
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "¿Está seguro que desea eliminar al médico " + deleteDialog.medicName + "?"
|
||||||
|
}
|
||||||
|
|
||||||
|
onAccepted: {
|
||||||
|
apiManager.deleteMedic(deleteDialog.medicId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
218
PatientsView.qml
Normal file
218
PatientsView.qml
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtQuick.Controls.Material 2.15
|
||||||
|
|
||||||
|
Item {
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 20
|
||||||
|
spacing: 20
|
||||||
|
|
||||||
|
// Lista de pacientes
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
border.color: Material.color(Material.Grey)
|
||||||
|
border.width: 1
|
||||||
|
radius: 5
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 10
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "Lista de Pacientes"
|
||||||
|
font.pixelSize: 18
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: patientsListView
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
clip: true
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
model: window.patientsData
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
width: patientsListView.width
|
||||||
|
height: 80
|
||||||
|
color: mouseArea.containsMouse ? Material.color(Material.Grey, Material.Shade100) : "white"
|
||||||
|
border.color: Material.color(Material.Grey, Material.Shade300)
|
||||||
|
radius: 5
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 10
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: modelData.nombre + " " + modelData.apellido
|
||||||
|
font.pixelSize: 16
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "ID: " + modelData.paciente_id
|
||||||
|
font.pixelSize: 12
|
||||||
|
color: Material.color(Material.Grey)
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "Registro: " + (modelData.registro || "N/A")
|
||||||
|
font.pixelSize: 12
|
||||||
|
color: Material.color(Material.Grey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "✏️"
|
||||||
|
flat: true
|
||||||
|
onClicked: {
|
||||||
|
patientForm.editMode = true
|
||||||
|
patientForm.currentId = modelData.paciente_id
|
||||||
|
patientForm.nombreField.text = modelData.nombre
|
||||||
|
patientForm.apellidoField.text = modelData.apellido
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "🗑️"
|
||||||
|
flat: true
|
||||||
|
Material.foreground: Material.Red
|
||||||
|
onClicked: {
|
||||||
|
deleteDialog.patientId = modelData.paciente_id
|
||||||
|
deleteDialog.patientName = modelData.nombre + " " + modelData.apellido
|
||||||
|
deleteDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBar {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formulario de pacientes
|
||||||
|
Rectangle {
|
||||||
|
id: patientForm
|
||||||
|
Layout.preferredWidth: 350
|
||||||
|
Layout.fillHeight: true
|
||||||
|
border.color: Material.color(Material.Grey)
|
||||||
|
border.width: 1
|
||||||
|
radius: 5
|
||||||
|
|
||||||
|
property bool editMode: false
|
||||||
|
property int currentId: -1
|
||||||
|
property alias nombreField: nombreInput
|
||||||
|
property alias apellidoField: apellidoInput
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 20
|
||||||
|
spacing: 15
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: patientForm.editMode ? "Editar Paciente" : "Nuevo Paciente"
|
||||||
|
font.pixelSize: 18
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: nombreInput
|
||||||
|
Layout.fillWidth: true
|
||||||
|
placeholderText: "Nombre"
|
||||||
|
Material.accent: Material.Teal
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: apellidoInput
|
||||||
|
Layout.fillWidth: true
|
||||||
|
placeholderText: "Apellido"
|
||||||
|
Material.accent: Material.Teal
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Button {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: patientForm.editMode ? "Actualizar" : "Agregar"
|
||||||
|
Material.background: Material.Teal
|
||||||
|
Material.foreground: "white"
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (nombreInput.text && apellidoInput.text) {
|
||||||
|
if (patientForm.editMode) {
|
||||||
|
apiManager.updatePatient(
|
||||||
|
patientForm.currentId,
|
||||||
|
nombreInput.text,
|
||||||
|
apellidoInput.text
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
apiManager.addPatient(
|
||||||
|
nombreInput.text,
|
||||||
|
apellidoInput.text
|
||||||
|
)
|
||||||
|
}
|
||||||
|
nombreInput.text = ""
|
||||||
|
apellidoInput.text = ""
|
||||||
|
patientForm.editMode = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "Cancelar"
|
||||||
|
visible: patientForm.editMode
|
||||||
|
onClicked: {
|
||||||
|
nombreInput.text = ""
|
||||||
|
apellidoInput.text = ""
|
||||||
|
patientForm.editMode = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dialog de confirmación de eliminación
|
||||||
|
Dialog {
|
||||||
|
id: deleteDialog
|
||||||
|
anchors.centerIn: parent
|
||||||
|
title: "Confirmar eliminación"
|
||||||
|
modal: true
|
||||||
|
standardButtons: Dialog.Yes | Dialog.No
|
||||||
|
|
||||||
|
property int patientId: -1
|
||||||
|
property string patientName: ""
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "¿Está seguro que desea eliminar al paciente " + deleteDialog.patientName + "?"
|
||||||
|
}
|
||||||
|
|
||||||
|
onAccepted: {
|
||||||
|
apiManager.deletePatient(deleteDialog.patientId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
README.md
Normal file
5
README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
## API CONSUMER PC2
|
||||||
|
|
||||||
|
El consumidor de la API del caso práctico 2.
|
||||||
|
|
||||||
|
Está hecho con Python y Qt
|
||||||
142
main.qml
Normal file
142
main.qml
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtQuick.Controls.Material 2.15
|
||||||
|
|
||||||
|
ApplicationWindow {
|
||||||
|
id: window
|
||||||
|
visible: true
|
||||||
|
width: 1200
|
||||||
|
height: 800
|
||||||
|
title: "Sistema de Gestión Médica"
|
||||||
|
|
||||||
|
Material.theme: Material.Light
|
||||||
|
Material.accent: Material.Teal
|
||||||
|
Material.primary: Material.Blue
|
||||||
|
|
||||||
|
property var patientsData: []
|
||||||
|
property var medicsData: []
|
||||||
|
property var citationsData: []
|
||||||
|
|
||||||
|
// Conexiones con el backend
|
||||||
|
Connections {
|
||||||
|
target: apiManager
|
||||||
|
|
||||||
|
function onDataLoaded(dataType, jsonData) {
|
||||||
|
var data = JSON.parse(jsonData)
|
||||||
|
|
||||||
|
if (dataType === "patients") {
|
||||||
|
patientsData = data
|
||||||
|
} else if (dataType === "medics") {
|
||||||
|
medicsData = data
|
||||||
|
} else if (dataType === "citations") {
|
||||||
|
citationsData = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onOperationComplete(operation, success, message) {
|
||||||
|
messageDialog.title = success ? "Éxito" : "Error"
|
||||||
|
messageDialog.text = message
|
||||||
|
messageDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
apiManager.loadData("patients")
|
||||||
|
apiManager.loadData("medics")
|
||||||
|
apiManager.loadData("citations")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dialog para mensajes
|
||||||
|
Dialog {
|
||||||
|
id: messageDialog
|
||||||
|
anchors.centerIn: parent
|
||||||
|
modal: true
|
||||||
|
standardButtons: Dialog.Ok
|
||||||
|
|
||||||
|
property alias text: messageLabel.text
|
||||||
|
|
||||||
|
Label {
|
||||||
|
id: messageLabel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header
|
||||||
|
header: ToolBar {
|
||||||
|
Material.background: Material.Blue
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: 10
|
||||||
|
anchors.rightMargin: 10
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "🏥 Sistema de Gestión Médica"
|
||||||
|
font.pixelSize: 20
|
||||||
|
font.bold: true
|
||||||
|
color: "white"
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "🔄 Actualizar"
|
||||||
|
flat: true
|
||||||
|
Material.foreground: "white"
|
||||||
|
onClicked: {
|
||||||
|
apiManager.loadData("patients")
|
||||||
|
apiManager.loadData("medics")
|
||||||
|
apiManager.loadData("citations")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main content
|
||||||
|
TabBar {
|
||||||
|
id: tabBar
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
TabButton {
|
||||||
|
text: "👥 Pacientes"
|
||||||
|
width: implicitWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
TabButton {
|
||||||
|
text: "👨⚕️ Médicos"
|
||||||
|
width: implicitWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
TabButton {
|
||||||
|
text: "📅 Citas"
|
||||||
|
width: implicitWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StackLayout {
|
||||||
|
width: parent.width
|
||||||
|
anchors.top: tabBar.bottom
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
currentIndex: tabBar.currentIndex
|
||||||
|
|
||||||
|
// TAB 1: Pacientes
|
||||||
|
Item {
|
||||||
|
PatientsView {
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TAB 2: Médicos
|
||||||
|
Item {
|
||||||
|
MedicsView {
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TAB 3: Citas
|
||||||
|
Item {
|
||||||
|
CitationsView {
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
188
main_ui.py
Normal file
188
main_ui.py
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
import sys
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import datetime
|
||||||
|
import time
|
||||||
|
from PySide6.QtCore import QObject, Slot, Signal, Property, QTimer
|
||||||
|
from PySide6.QtQml import QQmlApplicationEngine
|
||||||
|
from PySide6.QtWidgets import QApplication
|
||||||
|
from PySide6.QtQuickControls2 import QQuickStyle
|
||||||
|
|
||||||
|
# API Configuration
|
||||||
|
URI = 'http://localhost:5244/api/'
|
||||||
|
CITATION = URI + 'Cita'
|
||||||
|
PATIENTS = URI + 'Paciente'
|
||||||
|
MEDICS = URI + 'Medico'
|
||||||
|
HEADERS = {"content-type": "application/json"}
|
||||||
|
|
||||||
|
|
||||||
|
class ApiManager(QObject):
|
||||||
|
"""Backend manager for API calls"""
|
||||||
|
|
||||||
|
dataLoaded = Signal(str, str) # Signal(dataType, jsonData)
|
||||||
|
operationComplete = Signal(str, bool, str) # Signal(operation, success, message)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
# GET Methods
|
||||||
|
@Slot(str)
|
||||||
|
def loadData(self, dataType):
|
||||||
|
"""Load data from API (patients, medics, or citations)"""
|
||||||
|
try:
|
||||||
|
url_map = {
|
||||||
|
"patients": PATIENTS,
|
||||||
|
"medics": MEDICS,
|
||||||
|
"citations": CITATION
|
||||||
|
}
|
||||||
|
response = requests.get(url_map[dataType], headers=HEADERS)
|
||||||
|
data = response.json()
|
||||||
|
self.dataLoaded.emit(dataType, json.dumps(data))
|
||||||
|
except Exception as e:
|
||||||
|
self.operationComplete.emit(f"load_{dataType}", False, str(e))
|
||||||
|
|
||||||
|
# PATIENTS
|
||||||
|
@Slot(str, str)
|
||||||
|
def addPatient(self, nombre, apellido):
|
||||||
|
try:
|
||||||
|
datat = {
|
||||||
|
"nombre": nombre,
|
||||||
|
"apellido": apellido,
|
||||||
|
"registro": datetime.date.fromtimestamp(time.time()).isoformat()
|
||||||
|
}
|
||||||
|
response = requests.post(PATIENTS, json=datat, headers=HEADERS)
|
||||||
|
self.operationComplete.emit("add_patient", True, "Paciente agregado exitosamente")
|
||||||
|
self.loadData("patients")
|
||||||
|
except Exception as e:
|
||||||
|
self.operationComplete.emit("add_patient", False, str(e))
|
||||||
|
|
||||||
|
@Slot(int, str, str)
|
||||||
|
def updatePatient(self, pat_id, nombre, apellido):
|
||||||
|
try:
|
||||||
|
datat = {
|
||||||
|
"paciente_id": pat_id,
|
||||||
|
"nombre": nombre,
|
||||||
|
"apellido": apellido,
|
||||||
|
"registro": datetime.date.fromtimestamp(time.time()).isoformat()
|
||||||
|
}
|
||||||
|
response = requests.put(f"{PATIENTS}/{pat_id}", json=datat, headers=HEADERS)
|
||||||
|
self.operationComplete.emit("update_patient", True, "Paciente actualizado exitosamente")
|
||||||
|
self.loadData("patients")
|
||||||
|
except Exception as e:
|
||||||
|
self.operationComplete.emit("update_patient", False, str(e))
|
||||||
|
|
||||||
|
@Slot(int)
|
||||||
|
def deletePatient(self, pat_id):
|
||||||
|
try:
|
||||||
|
response = requests.delete(f"{PATIENTS}/{pat_id}", headers=HEADERS)
|
||||||
|
self.operationComplete.emit("delete_patient", True, "Paciente eliminado exitosamente")
|
||||||
|
self.loadData("patients")
|
||||||
|
except Exception as e:
|
||||||
|
self.operationComplete.emit("delete_patient", False, str(e))
|
||||||
|
|
||||||
|
# MEDICS
|
||||||
|
@Slot(str, str, str)
|
||||||
|
def addMedic(self, nombre, apellido, especialidad):
|
||||||
|
try:
|
||||||
|
datat = {
|
||||||
|
"nombre": nombre,
|
||||||
|
"apellido": apellido,
|
||||||
|
"especialidad": especialidad,
|
||||||
|
"registro": datetime.date.fromtimestamp(time.time()).isoformat()
|
||||||
|
}
|
||||||
|
response = requests.post(MEDICS, json=datat, headers=HEADERS)
|
||||||
|
self.operationComplete.emit("add_medic", True, "Médico agregado exitosamente")
|
||||||
|
self.loadData("medics")
|
||||||
|
except Exception as e:
|
||||||
|
self.operationComplete.emit("add_medic", False, str(e))
|
||||||
|
|
||||||
|
@Slot(int, str, str, str)
|
||||||
|
def updateMedic(self, med_id, nombre, apellido, especialidad):
|
||||||
|
try:
|
||||||
|
datat = {
|
||||||
|
"medico_id": med_id,
|
||||||
|
"nombre": nombre,
|
||||||
|
"apellido": apellido,
|
||||||
|
"especialidad": especialidad,
|
||||||
|
"registro": datetime.date.fromtimestamp(time.time()).isoformat()
|
||||||
|
}
|
||||||
|
response = requests.put(f"{MEDICS}/{med_id}", json=datat, headers=HEADERS)
|
||||||
|
self.operationComplete.emit("update_medic", True, "Médico actualizado exitosamente")
|
||||||
|
self.loadData("medics")
|
||||||
|
except Exception as e:
|
||||||
|
self.operationComplete.emit("update_medic", False, str(e))
|
||||||
|
|
||||||
|
@Slot(int)
|
||||||
|
def deleteMedic(self, med_id):
|
||||||
|
try:
|
||||||
|
response = requests.delete(f"{MEDICS}/{med_id}", headers=HEADERS)
|
||||||
|
self.operationComplete.emit("delete_medic", True, "Médico eliminado exitosamente")
|
||||||
|
self.loadData("medics")
|
||||||
|
except Exception as e:
|
||||||
|
self.operationComplete.emit("delete_medic", False, str(e))
|
||||||
|
|
||||||
|
# CITATIONS
|
||||||
|
@Slot(str, int, int)
|
||||||
|
def addCitation(self, detalles, med_id, pat_id):
|
||||||
|
try:
|
||||||
|
datat = {
|
||||||
|
"detalles": detalles,
|
||||||
|
"medico_id": med_id,
|
||||||
|
"paciente_id": pat_id
|
||||||
|
}
|
||||||
|
response = requests.post(CITATION, json=datat, headers=HEADERS)
|
||||||
|
self.operationComplete.emit("add_citation", True, "Cita agregada exitosamente")
|
||||||
|
self.loadData("citations")
|
||||||
|
except Exception as e:
|
||||||
|
self.operationComplete.emit("add_citation", False, str(e))
|
||||||
|
|
||||||
|
@Slot(int, str, int, int)
|
||||||
|
def updateCitation(self, cit_id, detalles, med_id, pat_id):
|
||||||
|
try:
|
||||||
|
datat = {
|
||||||
|
"cita_id": cit_id,
|
||||||
|
"detalles": detalles,
|
||||||
|
"medico_id": med_id,
|
||||||
|
"paciente_id": pat_id
|
||||||
|
}
|
||||||
|
response = requests.put(f"{CITATION}/{cit_id}", json=datat, headers=HEADERS)
|
||||||
|
self.operationComplete.emit("update_citation", True, "Cita actualizada exitosamente")
|
||||||
|
self.loadData("citations")
|
||||||
|
except Exception as e:
|
||||||
|
self.operationComplete.emit("update_citation", False, str(e))
|
||||||
|
|
||||||
|
@Slot(int)
|
||||||
|
def deleteCitation(self, cit_id):
|
||||||
|
try:
|
||||||
|
response = requests.delete(f"{CITATION}/{cit_id}", headers=HEADERS)
|
||||||
|
self.operationComplete.emit("delete_citation", True, "Cita eliminada exitosamente")
|
||||||
|
self.loadData("citations")
|
||||||
|
except Exception as e:
|
||||||
|
self.operationComplete.emit("delete_citation", False, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
QQuickStyle.setStyle("Material")
|
||||||
|
|
||||||
|
# Create API manager
|
||||||
|
api_manager = ApiManager()
|
||||||
|
|
||||||
|
# Create QML engine
|
||||||
|
engine = QQmlApplicationEngine()
|
||||||
|
|
||||||
|
# Expose API manager to QML
|
||||||
|
engine.rootContext().setContextProperty("apiManager", api_manager)
|
||||||
|
|
||||||
|
# Load QML file
|
||||||
|
qml_file = "main.qml"
|
||||||
|
engine.load(qml_file)
|
||||||
|
|
||||||
|
if not engine.rootObjects():
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
sys.exit(app.exec())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user