diff --git a/eap_backend/eap_api/migrations/0016_auto_20240613_1028.py b/eap_backend/eap_api/migrations/0016_auto_20240613_1028.py new file mode 100644 index 00000000..1855fd3b --- /dev/null +++ b/eap_backend/eap_api/migrations/0016_auto_20240613_1028.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.8 on 2024-06-13 10:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('eap_api', '0015_githubrepository'), + ] + + operations = [ + migrations.AlterField( + model_name='strategy', + name='long_description', + field=models.CharField(max_length=3000), + ), + migrations.AlterField( + model_name='strategy', + name='short_description', + field=models.CharField(max_length=1000), + ), + ] diff --git a/eap_backend/eap_api/serializers.py b/eap_backend/eap_api/serializers.py index 4bc39bb8..24a63244 100644 --- a/eap_backend/eap_api/serializers.py +++ b/eap_backend/eap_api/serializers.py @@ -70,8 +70,12 @@ class EAPGroupSerializer(serializers.ModelSerializer): owner_id = serializers.PrimaryKeyRelatedField( source="owner", queryset=EAPUser.objects.all() ) - viewable_cases = serializers.PrimaryKeyRelatedField(many=True, read_only=True) - editable_cases = serializers.PrimaryKeyRelatedField(many=True, read_only=True) + viewable_cases = serializers.PrimaryKeyRelatedField( + many=True, read_only=True + ) + editable_cases = serializers.PrimaryKeyRelatedField( + many=True, read_only=True + ) class Meta: model = EAPGroup @@ -131,9 +135,13 @@ class TopLevelNormativeGoalSerializer(serializers.ModelSerializer): source="assurance_case", queryset=AssuranceCase.objects.all() ) context = serializers.PrimaryKeyRelatedField(many=True, read_only=True) - property_claims = serializers.PrimaryKeyRelatedField(many=True, read_only=True) + property_claims = serializers.PrimaryKeyRelatedField( + many=True, read_only=True + ) strategies = serializers.PrimaryKeyRelatedField(many=True, read_only=True) - type = serializers.CharField(default="TopLevelNormativeGoal", read_only=True) + type = serializers.CharField( + default="TopLevelNormativeGoal", read_only=True + ) class Meta: model = TopLevelNormativeGoal @@ -150,6 +158,8 @@ class Meta: "strategies", ) + extra_kwargs = {"name": {"allow_null": True, "required": False}} + class ContextSerializer(serializers.ModelSerializer): goal_id = serializers.PrimaryKeyRelatedField( @@ -169,6 +179,8 @@ class Meta: "goal_id", ) + extra_kwargs = {"name": {"allow_null": True, "required": False}} + class PropertyClaimSerializer(serializers.ModelSerializer): goal_id = serializers.PrimaryKeyRelatedField( @@ -189,7 +201,9 @@ class PropertyClaimSerializer(serializers.ModelSerializer): level = serializers.IntegerField(read_only=True) claim_type = serializers.CharField(default="Project claim") - property_claims = serializers.PrimaryKeyRelatedField(many=True, read_only=True) + property_claims = serializers.PrimaryKeyRelatedField( + many=True, read_only=True + ) # Use SerializerMethodField to handle the possibility of property_claim being None evidence = serializers.PrimaryKeyRelatedField(many=True, read_only=True) @@ -212,6 +226,8 @@ class Meta: "strategy_id", ) + extra_kwargs = {"name": {"allow_null": True, "required": False}} + class EvidenceSerializer(serializers.ModelSerializer): property_claim_id = serializers.PrimaryKeyRelatedField( @@ -233,6 +249,8 @@ class Meta: "property_claim_id", ) + extra_kwargs = {"name": {"allow_null": True, "required": False}} + class StrategySerializer(serializers.ModelSerializer): goal_id = serializers.PrimaryKeyRelatedField( @@ -241,7 +259,9 @@ class StrategySerializer(serializers.ModelSerializer): required=False, ) - property_claims = serializers.PrimaryKeyRelatedField(many=True, read_only=True) + property_claims = serializers.PrimaryKeyRelatedField( + many=True, read_only=True + ) class Meta: model = Strategy @@ -253,3 +273,5 @@ class Meta: "goal_id", "property_claims", ) + + extra_kwargs = {"name": {"allow_null": True, "required": False}} diff --git a/eap_backend/eap_api/urls.py b/eap_backend/eap_api/urls.py index 6dc47428..4aece7ab 100644 --- a/eap_backend/eap_api/urls.py +++ b/eap_backend/eap_api/urls.py @@ -16,8 +16,16 @@ path("goals//", views.goal_detail, name="goal_detail"), path("contexts/", views.context_list, name="context_list"), path("contexts//", views.context_detail, name="context_detail"), - path("propertyclaims/", views.property_claim_list, name="property_claim_list"), - path("comments//", views.comment_list, name="comment_list"), + path( + "propertyclaims/", + views.property_claim_list, + name="property_claim_list", + ), + path( + "comments//", + views.comment_list, + name="comment_list", + ), path( "comments//reply/", views.reply_to_comment, @@ -38,9 +46,13 @@ views.parents, name="parents", ), - path("api-auth/", include("rest_framework.urls", namespace="rest_framework")), + path( + "api-auth/", include("rest_framework.urls", namespace="rest_framework") + ), path("strategies/", views.strategies_list, name="strategies_list"), - path("strategies//", views.strategy_detail, name="strategy_detail"), + path( + "strategies//", views.strategy_detail, name="strategy_detail" + ), path("auth/github/", views.GithubSocialAuthView.as_view()), path( "users//github_repositories/", diff --git a/eap_backend/eap_api/views.py b/eap_backend/eap_api/views.py index 9760978e..f74cd3cd 100644 --- a/eap_backend/eap_api/views.py +++ b/eap_backend/eap_api/views.py @@ -1,3 +1,5 @@ +from typing import Optional + from django.http import HttpResponse, JsonResponse from django.views.decorators.csrf import csrf_exempt from rest_framework import generics, status @@ -6,9 +8,12 @@ from rest_framework.parsers import JSONParser from rest_framework.permissions import AllowAny from rest_framework.response import Response +from django.db.models.query import QuerySet +from typing import cast from .models import ( AssuranceCase, + CaseItem, Comment, Context, EAPGroup, @@ -38,6 +43,7 @@ filter_by_case_id, get_allowed_cases, get_allowed_groups, + get_case_id, get_case_permissions, get_json_tree, make_case_summary, @@ -47,7 +53,7 @@ @csrf_exempt -def user_list(request): +def user_list(request) -> Optional[HttpResponse]: """ List all users, or make a new user """ @@ -194,6 +200,42 @@ def case_list(request): return None +# @csrf_exempt +# @api_view(["GET", "POST", "PUT", "DELETE"]) +# def case_detail(request, pk): +# """ +# Retrieve, update, or delete an AssuranceCase, by primary key +# """ +# try: +# case = AssuranceCase.objects.get(pk=pk) +# except AssuranceCase.DoesNotExist: +# return HttpResponse(status=404) +# permissions = get_case_permissions(case, request.user) +# if not permissions: +# return HttpResponse(status=403) +# if request.method == "GET": +# serializer = AssuranceCaseSerializer(case) +# case_data = serializer.data +# goals = get_json_tree(case_data["goals"], "goals") +# case_data["goals"] = goals +# case_data["permissions"] = permissions +# return JsonResponse(case_data) +# elif request.method == "PUT": +# if permissions not in ["manage", "edit"]: +# return HttpResponse(status=403) +# data = JSONParser().parse(request) +# serializer = AssuranceCaseSerializer(case, data=data, partial=True) +# if serializer.is_valid(): +# serializer.save() +# return JsonResponse(serializer.data) +# return JsonResponse(serializer.errors, status=400) +# elif request.method == "DELETE": +# if permissions not in ["manage", "edit"]: +# return HttpResponse(status=403) +# case.delete() +# return HttpResponse(status=204) + +# return None @csrf_exempt @api_view(["GET", "POST", "PUT", "DELETE"]) def case_detail(request, pk): @@ -245,12 +287,15 @@ def goal_list(request): return JsonResponse(summaries, safe=False) elif request.method == "POST": data = JSONParser().parse(request) - assurance_case_id = AssuranceCase.objects.get(id=data["assurance_case_id"]) + assurance_case_id = AssuranceCase.objects.get( + id=data["assurance_case_id"] + ) data["assurance_case"] = assurance_case_id serializer = TopLevelNormativeGoalSerializer(data=data) if serializer.is_valid(): serializer.save() summary = make_summary(serializer.data) + update_identifiers(get_case_id(serializer.instance)) return JsonResponse(summary, status=201) return JsonResponse(serializer.errors, status=400) return None @@ -277,7 +322,9 @@ def goal_detail(request, pk): return JsonResponse(data) elif request.method == "PUT": data = JSONParser().parse(request) - serializer = TopLevelNormativeGoalSerializer(goal, data=data, partial=True) + serializer = TopLevelNormativeGoalSerializer( + goal, data=data, partial=True + ) if serializer.is_valid(): serializer.save() data = serializer.data @@ -286,6 +333,7 @@ def goal_detail(request, pk): return JsonResponse(serializer.errors, status=400) elif request.method == "DELETE": goal.delete() + update_identifiers(get_case_id(goal)) return HttpResponse(status=204) return None @@ -307,6 +355,7 @@ def context_list(request): if serializer.is_valid(): serializer.save() summary = make_summary(serializer.data) + update_identifiers(get_case_id(serializer.instance)) return JsonResponse(summary, status=201) return JsonResponse(serializer.errors, status=400) return None @@ -339,6 +388,7 @@ def context_detail(request, pk): return JsonResponse(serializer.errors, status=400) elif request.method == "DELETE": context.delete() + update_identifiers(get_case_id(context)) return HttpResponse(status=204) return None @@ -360,6 +410,7 @@ def property_claim_list(request): if serializer.is_valid(): serializer.save() summary = make_summary(serializer.data) + update_identifiers(get_case_id(serializer.instance)) return JsonResponse(summary, status=201) return JsonResponse(serializer.errors, status=400) return None @@ -392,6 +443,7 @@ def property_claim_detail(request, pk): return JsonResponse(serializer.errors, status=400) elif request.method == "DELETE": claim.delete() + update_identifiers(get_case_id(claim)) return HttpResponse(status=204) return None @@ -413,6 +465,7 @@ def evidence_list(request): if serializer.is_valid(): serializer.save() summary = make_summary(serializer.data) + update_identifiers(get_case_id(serializer.instance)) return JsonResponse(summary, status=201) return JsonResponse(serializer.errors, status=400) return None @@ -444,7 +497,9 @@ def evidence_detail(request, pk): return JsonResponse(data) return JsonResponse(serializer.errors, status=400) elif request.method == "DELETE": + case_id: Optional[int] = get_case_id(evidence) evidence.delete() + update_identifiers(case_id) return HttpResponse(status=204) return None @@ -483,6 +538,7 @@ def strategies_list(request): if serializer.is_valid(): serializer.save() summary = make_summary(serializer.data) + update_identifiers(get_case_id(serializer.instance)) return JsonResponse(summary, status=201) return JsonResponse(serializer.errors, status=400) return None @@ -510,6 +566,7 @@ def strategy_detail(request, pk): elif request.method == "DELETE": strategy.delete() + update_identifiers(get_case_id(strategy)) return HttpResponse(status=204) return None @@ -598,11 +655,80 @@ def reply_to_comment(request, comment_id): if request.method == "POST": data = JSONParser().parse(request) data["parent"] = comment_id - data["author"] = request.user.id # Ensure the author is set to the current user + data[ + "author" + ] = request.user.id # Ensure the author is set to the current user data["assurance_case"] = parent_comment.assurance_case_id serializer = CommentSerializer(data=data) if serializer.is_valid(): serializer.save() - return JsonResponse(serializer.data, status=status.HTTP_201_CREATED) - return JsonResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + return JsonResponse( + serializer.data, status=status.HTTP_201_CREATED + ) + return JsonResponse( + serializer.errors, status=status.HTTP_400_BAD_REQUEST + ) return JsonResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + +def update_identifiers(case_id: Optional[int] = None): + + if case_id is None: + raise ValueError("Assurance Case ID not provided.") + + if TopLevelNormativeGoal.objects.filter( + assurance_case_id=case_id + ).exists(): + goal_id: int = TopLevelNormativeGoal.objects.get( + assurance_case_id=case_id + ).pk + + update_sequential_identifiers( + TopLevelNormativeGoal.objects.filter(id=goal_id), "G" + ) + update_sequential_identifiers( + Context.objects.filter(goal_id=goal_id), "C" + ) + update_sequential_identifiers( + Strategy.objects.filter(goal_id=goal_id), "S" + ) + + case_claim_ids: list[int] = [ + claim.pk + for claim in PropertyClaim.objects.all() + if get_case_id(claim) == case_id + ] + + update_sequential_identifiers( + Evidence.objects.filter(property_claim__id__in=case_claim_ids), "E" + ) + + parent_property_claims: QuerySet = PropertyClaim.objects.filter( + pk__in=case_claim_ids + ).filter(property_claim_id=None) + + update_sequential_identifiers(parent_property_claims, "P") + for _, property_claim in enumerate(parent_property_claims): + update_property_claim_identifiers(property_claim) + + +def update_sequential_identifiers(query_set: QuerySet, prefix: str): + for model_index, model in enumerate(query_set): + model.name = f"{prefix}{model_index + 1}" + model.save() + + +def update_property_claim_identifiers(parent_property_claim: PropertyClaim): + child_property_claims = PropertyClaim.objects.filter( + property_claim_id=parent_property_claim.pk + ) + + if len(child_property_claims) == 0: + return + else: + for index, child_property_claim in enumerate(child_property_claims): + child_property_claim.name = ( + f"{parent_property_claim.name}.{index + 1}" + ) + child_property_claim.save() + update_property_claim_identifiers(child_property_claim) diff --git a/eap_backend/eap_backend/settings.py b/eap_backend/eap_backend/settings.py index d1340648..616d4182 100644 --- a/eap_backend/eap_backend/settings.py +++ b/eap_backend/eap_backend/settings.py @@ -21,6 +21,8 @@ # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ +# TODO(cgavidia): Move DEBUG and SECRET_KEY to environment variables. + # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = "django-insecure-)@nls8m9den@jbfjkee2h343^=a8#jzq+@^nweds$s#%_1ia_g" @@ -32,7 +34,7 @@ GITHUB_CLIENT_SECRET = os.environ.get("GITHUB_CLIENT_SECRET") REDIRECT_URI = "http://localhost:3000/login" - +# TODO(cgavidia): This should also come from environment variables. ALLOWED_HOSTS = ["*"] @@ -40,11 +42,11 @@ INSTALLED_APPS = [ "eap_api.apps.ApiConfig", - "django.contrib.admin", + "django.contrib.admin", # TODO(cgavidia): Apparently, we don't need this. "django.contrib.auth", "django.contrib.contenttypes", - "django.contrib.sessions", - "django.contrib.messages", + "django.contrib.sessions", # TODO(cgavidia): Apparently, we don't need this. + "django.contrib.messages", # TODO(cgavidia): Apparently, we don't need this. "django.contrib.staticfiles", "rest_framework", "rest_framework.authtoken", @@ -61,13 +63,13 @@ MIDDLEWARE = [ "corsheaders.middleware.CorsMiddleware", - "django.middleware.common.CommonMiddleware", + "django.middleware.common.CommonMiddleware", # TODO(cgavidia): This line is duplicated. "django.middleware.security.SecurityMiddleware", - "django.contrib.sessions.middleware.SessionMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", # TODO(cgavidia): Apparently, we don't need this. "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", - "django.contrib.auth.middleware.AuthenticationMiddleware", - "django.contrib.messages.middleware.MessageMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", # TODO(cgavidia): Apparently, we don't need this. + "django.contrib.messages.middleware.MessageMiddleware", # TODO(cgavidia): Apparently, we don't need this. "django.middleware.clickjacking.XFrameOptionsMiddleware", "corsheaders.middleware.CorsMiddleware", ] @@ -104,6 +106,7 @@ REST_FRAMEWORK = { # Use Django's standard `django.contrib.auth` permissions, # or allow read-only access for unauthenticated users. + # TODO(cgavidia): Can we use IsAuthenticated instead? "DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.AllowAny"], # "DEFAULT_PERMISSION_CLASSES": [ # "rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly" @@ -210,4 +213,4 @@ ACCOUNT_SESSION_REMEMBER = True ACCOUNT_AUTHENTICATION_METHOD = "username" ACCOUNT_UNIQUE_EMAIL = False -ACCOUNT_UNIQUE_USERNAME = True +ACCOUNT_UNIQUE_USERNAME = True \ No newline at end of file diff --git a/next_frontend/.env.local b/next_frontend/.env.local index b7b9d84c..1de45caf 100644 --- a/next_frontend/.env.local +++ b/next_frontend/.env.local @@ -1,5 +1,5 @@ -# NEXT_PUBLIC_API_URL=http://localhost:8000 -NEXT_PUBLIC_API_URL=https://eap-backend.azurewebsites.net +NEXT_PUBLIC_API_URL=http://localhost:8000 +# NEXT_PUBLIC_API_URL=https://eap-backend.azurewebsites.net NEXT_PUBLIC_STORAGESASTOKEN=sv=2022-11-02&ss=bfqt&srt=c&sp=rwdlacupiytfx&se=2025-05-06T03:07:05Z&st=2024-05-05T19:07:05Z&spr=https&sig=vUQsHpRHQ%2BZAiE9k3riG4qmXJubVd0jMdbBFgM0fpuY%3D NEXT_PUBLIC_STORAGESOURCENAME=teamedia diff --git a/next_frontend/components/cases/ContextNode.tsx b/next_frontend/components/cases/ContextNode.tsx index 4623bd26..2b9d9ca9 100644 --- a/next_frontend/components/cases/ContextNode.tsx +++ b/next_frontend/components/cases/ContextNode.tsx @@ -6,14 +6,14 @@ import { Handle, Position } from 'reactflow'; function ContextNode({ isConnectable, data }: any) { return ( -
-
+
+
{data.name}
-
{data.description}
+

{data.description}

diff --git a/next_frontend/components/cases/EvidenceNode.tsx b/next_frontend/components/cases/EvidenceNode.tsx index 2c2d9578..edc19fba 100644 --- a/next_frontend/components/cases/EvidenceNode.tsx +++ b/next_frontend/components/cases/EvidenceNode.tsx @@ -6,14 +6,14 @@ import { Handle, Position } from 'reactflow'; function EvidenceNode({ data }: any) { return ( -
-
+
+
{data.name}
-
{data.description}
+

{data.description}

diff --git a/next_frontend/components/cases/GoalNode.tsx b/next_frontend/components/cases/GoalNode.tsx index 123ce9e2..31e38662 100644 --- a/next_frontend/components/cases/GoalNode.tsx +++ b/next_frontend/components/cases/GoalNode.tsx @@ -8,17 +8,25 @@ import ToggleButton from './ToggleButton'; function GoalNode({ data, ...props }: NodeProps) { const [hidden, setHidden] = useState(true) + // Debug: Log the data.description value + console.log(data.description); + return ( -
-
-
+
+
+
{data.name}
-
{data.description}
+ {/* Ensure there is a value */} + {data.description ? ( +

{data.description}

+ ) : ( +

No description available.

+ )}
- +
{/* diff --git a/next_frontend/components/cases/PropertyNode.tsx b/next_frontend/components/cases/PropertyNode.tsx index 607c14cd..84e136d6 100644 --- a/next_frontend/components/cases/PropertyNode.tsx +++ b/next_frontend/components/cases/PropertyNode.tsx @@ -7,14 +7,14 @@ import ToggleButton from './ToggleButton'; function PropertyNode({ data, ...props}: NodeProps) { return ( -
-
+
+
{data.name}
-
{data.description}
+

{data.description}

diff --git a/next_frontend/components/cases/StrategyNode.tsx b/next_frontend/components/cases/StrategyNode.tsx index 0c90c26b..abe4cd0c 100644 --- a/next_frontend/components/cases/StrategyNode.tsx +++ b/next_frontend/components/cases/StrategyNode.tsx @@ -7,14 +7,14 @@ import ToggleButton from './ToggleButton'; function StrategyNode({ data, ...props }: NodeProps) { return ( -
-
+
+
{data.name}
-
{data.description}
+

{data.description}

diff --git a/next_frontend/components/common/CreateForm.tsx b/next_frontend/components/common/CreateForm.tsx index f9d936ab..47682281 100644 --- a/next_frontend/components/common/CreateForm.tsx +++ b/next_frontend/components/common/CreateForm.tsx @@ -48,7 +48,7 @@ const CreateForm: React.FC = ({ onClose }) => { const identifier = await setNodeIdentifier(null, 'goal') const newGoal = { - "name": `G${identifier}`, + "name": `G1`, "short_description": values.description, "long_description": "N/A", "keywords": "N/A", @@ -70,8 +70,9 @@ const CreateForm: React.FC = ({ onClose }) => { goals: [ result.data ] } - setAssuranceCase(updatedAssuranceCase) + // setAssuranceCase(updatedAssuranceCase) onClose() + window.location.reload() } return ( diff --git a/next_frontend/components/common/EditForm.tsx b/next_frontend/components/common/EditForm.tsx index 344db586..baf30817 100644 --- a/next_frontend/components/common/EditForm.tsx +++ b/next_frontend/components/common/EditForm.tsx @@ -72,7 +72,8 @@ const EditForm: React.FC = ({ // Assurance Case Update const updatedAssuranceCase = await updateAssuranceCase(node.type, assuranceCase, updateItem, node.data.id, node) if(updatedAssuranceCase) { - setAssuranceCase(updatedAssuranceCase) + // setAssuranceCase(updatedAssuranceCase) + window.location.reload() onClose() } } diff --git a/next_frontend/components/common/NewLinkForm.tsx b/next_frontend/components/common/NewLinkForm.tsx index 167b219a..8a5220f5 100644 --- a/next_frontend/components/common/NewLinkForm.tsx +++ b/next_frontend/components/common/NewLinkForm.tsx @@ -82,8 +82,9 @@ const NewLinkForm: React.FC = ({ } // Update Assurance Case in state - setAssuranceCase(updatedAssuranceCase) + // setAssuranceCase(updatedAssuranceCase) reset() + window.location.reload() } /** Function used to handle creation of a strategy node linked to a goal */ @@ -121,8 +122,9 @@ const NewLinkForm: React.FC = ({ } // Update Assurance Case in state - setAssuranceCase(updatedAssuranceCase) + // setAssuranceCase(updatedAssuranceCase) reset() + window.location.reload() } /** Function used to create a property claim, whether its parent is a goal, strategy or another propery claim */ @@ -193,8 +195,9 @@ const NewLinkForm: React.FC = ({ }); // Update Assurance Case in state - setAssuranceCase(updatedAssuranceCase); + // setAssuranceCase(updatedAssuranceCase); reset() + window.location.reload() } } @@ -214,8 +217,9 @@ const NewLinkForm: React.FC = ({ ] } - setAssuranceCase(updatedAssuranceCase) + // setAssuranceCase(updatedAssuranceCase) reset() + window.location.reload() } if(node.type === 'goal') { @@ -235,8 +239,9 @@ const NewLinkForm: React.FC = ({ } // Update Assurance Case in state - setAssuranceCase(updatedAssuranceCase) + // setAssuranceCase(updatedAssuranceCase) reset() + window.location.reload() } } @@ -275,8 +280,9 @@ const NewLinkForm: React.FC = ({ ] } - setAssuranceCase(updatedAssuranceCase) + // setAssuranceCase(updatedAssuranceCase) reset() + window.location.reload() } const form = useForm>({ diff --git a/next_frontend/components/common/NodeEdit.tsx b/next_frontend/components/common/NodeEdit.tsx index f963629a..d01ef0b0 100644 --- a/next_frontend/components/common/NodeEdit.tsx +++ b/next_frontend/components/common/NodeEdit.tsx @@ -7,15 +7,21 @@ import { CloudFog, Plus, Trash2 } from "lucide-react" import EditForm from "./EditForm"; import useStore from '@/data/store'; import { Autour_One } from "next/font/google"; -import { addEvidenceToClaim, addPropertyClaimToNested, createAssuranceCaseNode, deleteAssuranceCaseNode, setNodeIdentifier } from "@/lib/case-helper"; +import { addEvidenceToClaim, addPropertyClaimToNested, createAssuranceCaseNode, deleteAssuranceCaseNode, listPropertyClaims, setNodeIdentifier, updateAssuranceCaseNode } from "@/lib/case-helper"; import { useLoginToken } from "@/hooks/useAuth"; import NewLinkForm from "./NewLinkForm"; import { AlertModal } from "../modals/alertModal"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" interface NodeEditProps { node: Node | any isOpen: boolean - // onClose: () => void setEditOpen: Dispatch> } @@ -29,8 +35,18 @@ const NodeEdit = ({ node, isOpen, setEditOpen } : NodeEditProps ) => { const [alertOpen, setAlertOpen] = useState(false) const [loading, setLoading] = useState(false) + const [selectedClaimMove, setSelectedClaimMove] = useState(null); // State for selected strategy + const [selectedEvidenceMove, setSelectedEvidenceMove] = useState(null); // State for selected strategy + const [token] = useLoginToken(); + let claims, strategies = [] + + if(assuranceCase.goals[0]) { + strategies = assuranceCase.goals[0].strategies + claims = listPropertyClaims(assuranceCase.goals) + } + useEffect(() => { setIsMounted(true); }, []); @@ -72,6 +88,32 @@ const NodeEdit = ({ node, isOpen, setEditOpen } : NodeEditProps ) => { } }; + const handleMove = async () => { + // Add your move logic here + if (selectedClaimMove) { + console.log(`Move Property to Strategy with ID: ${selectedClaimMove}`); + const updateItem = { + strategy_id: selectedClaimMove, + } + const updated = await updateAssuranceCaseNode('property', node.data.id, token, updateItem) + if(updated) { + window.location.reload() + } + console.log('Something went wrong updating') + } + if (selectedEvidenceMove) { + console.log(`Move Evidence to Property Claim with ID: ${selectedEvidenceMove}`); + const updateItem = { + property_claim_id: [selectedEvidenceMove], + } + const updated = await updateAssuranceCaseNode('evidence', node.data.id, token, updateItem) + if(updated) { + window.location.reload() + } + console.log('Something went wrong updating') + } + } + return ( { {/* Node specific form buttons */} - {node.type != 'context' && ( + {node.type != 'context' && node.type != 'evidence' && (

Link to {node.data.name}

@@ -107,17 +149,66 @@ const NodeEdit = ({ node, isOpen, setEditOpen } : NodeEditProps ) => { )} - {node.type === 'evidence' && ( - <> - TODO: Property Links - - )}
)} + {/* Moving element options */} + {node.type === 'property' || node.type === 'evidence' ? ( +
+

Move {node.type}

+
+ {node.type === 'property' && + + } + {node.type === 'evidence' && + + } + +
+
+ ) : null} + {/* Handle Delete */} -
+